FieldDropzoneNew Component
FieldDropzoneNew adalah komponen upload file yang advanced dengan drag-and-drop support, built-in image compression, multi-format preview, dan seamless integration dengan file management system.
📋 Overview
🎯 Fungsi Utama
- Drag & Drop: Intuitive drag-and-drop file upload interface
- Image Compression: Otomatis kompres gambar untuk optimal storage
- Multi-format Preview: Support untuk gambar, PDF, video preview
- Dual View Modes: Thumbnail grid dan table row views
- File Validation: Size dan type validation dengan custom rules
- Auto-cleanup: Otomatis hapus file dari storage saat di-remove
🎨 View Modes
| Mode | Description | Best For |
|---|---|---|
| thumbnail | Grid layout dengan preview thumbnails | Image galleries, photo uploads |
| row | Table layout dengan file details | Document management, file lists |
🚀 Quick Start
Basic Usage
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew';
function DocumentUpload() {
const [files, setFiles] = useState([]);
return (
<FieldDropzoneNew
label="Upload Documents"
value={files}
onFilesChange={setFiles}
token="your-auth-token"
/>
);
}
Advanced Configuration
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew';
function AdvancedUpload() {
const [files, setFiles] = useState([]);
const handleFileUpload = async (newFiles) => {
console.log('Files uploaded:', newFiles);
setFiles(prev => [...prev, ...newFiles]);
};
const handleFileRemove = async (filePath, index) => {
console.log('File removed:', filePath);
setFiles(prev => prev.filter((_, i) => i !== index));
};
return (
<FieldDropzoneNew
label="Upload Files (Max 10MB)"
value={files}
onFilesChange={handleFileUpload}
onFileRemove={handleFileRemove}
token="your-auth-token"
maxFileSize={10}
acceptedFileTypes={[
'application/pdf',
'image/jpeg',
'image/png',
'application/msword'
]}
enableImageCompression={true}
compressionOptions={{
maxWidth: 1920,
maxHeight: 1080,
quality: 0.8,
maxSizeKB: 500
}}
viewMode="thumbnail"
cols={3}
/>
);
}
📝 API Reference
Props Interface
interface FieldDropzoneNewProps extends Partial<DropzoneProps> {
// Core Props
label: string; // Required - Field label
value?: (string | File)[]; // Current files
required?: boolean; // Field requirement
error?: string; // Error message
// File Management
onFilesChange: (files: File[]) => void; // File upload handler
onFileRemove?: (filePath: string, index: number) => void; // File removal handler
onDeleteAllFiles?: () => void; // Delete all files handler
token: string; // Auth token for API calls
// Display Options
cols?: StyleProp<number>; // Grid columns for thumbnail
viewMode?: ViewMode; // 'thumbnail' | 'row' (default: 'thumbnail')
// Validation
maxFileSize?: number; // Max file size in MB (default: 5)
acceptedFileTypes?: string[]; // Accepted file types
// Image Compression
enableImageCompression?: boolean; // Enable compression (default: true)
compressionOptions?: { // Compression settings
maxWidth?: number;
maxHeight?: number;
quality?: number;
maxSizeKB?: number;
};
// Behavior
removeFilePermanently?: boolean; // Delete from storage (default: true)
}
type ViewMode = 'thumbnail' | 'row';
Props Breakdown
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Required. Label untuk field |
value | array | [] | Current files (paths or File objects) |
onFilesChange | function | - | Required. Handler untuk file upload |
token | string | - | Required. Authentication token |
required | boolean | false | Field requirement indicator |
error | string | - | Error message display |
maxFileSize | number | 5 | Maximum file size in MB |
acceptedFileTypes | string[] | Default types | Allowed file MIME types |
viewMode | ViewMode | 'thumbnail' | Display mode for files |
cols | number | 3 | Grid columns for thumbnail mode |
enableImageCompression | boolean | true | Enable image compression |
removeFilePermanently | boolean | true | Delete files from storage |
compressionOptions | object | Default settings | Image compression settings |
🎨 Examples
Photo Gallery Upload
import { useState } from 'react';
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew';
function PhotoGalleryUpload() {
const [photos, setPhotos] = useState([]);
const compressionOptions = {
maxWidth: 2048,
maxHeight: 2048,
quality: 0.85,
maxSizeKB: 1000 // 1MB max after compression
};
const acceptedTypes = [
'image/jpeg',
'image/png',
'image/webp'
];
return (
<FieldDropzoneNew
label="Upload Photos"
value={photos}
onFilesChange={setPhotos}
token={userToken}
maxFileSize={20} // 20MB before compression
acceptedFileTypes={acceptedTypes}
enableImageCompression={true}
compressionOptions={compressionOptions}
viewMode="thumbnail"
cols={4}
required
/>
);
}
Document Management
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew';
function DocumentUpload() {
const [documents, setDocuments] = useState([]);
const handleDocumentUpload = async (files) => {
// Process uploaded documents
for (const file of files) {
await saveDocumentMetadata(file);
}
setDocuments(prev => [...prev, ...files]);
};
const handleDocumentRemove = async (filePath, index) => {
// Remove from database and storage
await deleteDocument(filePath);
setDocuments(prev => prev.filter((_, i) => i !== index));
};
const documentTypes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
return (
<FieldDropzoneNew
label="Upload Documents"
value={documents}
onFilesChange={handleDocumentUpload}
onFileRemove={handleDocumentRemove}
token={authToken}
maxFileSize={50}
acceptedFileTypes={documentTypes}
viewMode="row"
enableImageCompression={false} // Disable compression for documents
removeFilePermanently={true}
/>
);
}
Multi-Format File Upload
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew';
function MediaUpload() {
const [mediaFiles, setMediaFiles] = useState([]);
const allSupportedTypes = [
// Images
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
// Documents
'application/pdf',
// Videos (small files only)
'video/mp4',
'video/webm'
];
return (
<FieldDropzoneNew
label="Upload Media Files"
value={mediaFiles}
onFilesChange={setMediaFiles}
token={userToken}
maxFileSize={100}
acceptedFileTypes={allSupportedTypes}
viewMode="thumbnail"
cols={2}
compressionOptions={{
maxWidth: 1280,
maxHeight: 720,
quality: 0.8,
maxSizeKB: 500
}}
onFileRemove={(filePath, index) => {
console.log(`Removed: ${filePath} at index ${index}`);
}}
onDeleteAllFiles={() => {
console.log('All files deleted');
setMediaFiles([]);
}}
/>
);
}
🔧 Advanced Configuration
Custom Validation
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew';
function ValidatedUpload() {
const [files, setFiles] = useState([]);
const [validationError, setValidationError] = useState('');
const validateFiles = (files) => {
const maxTotalSize = 50 * 1024 * 1024; // 50MB total
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
if (totalSize > maxTotalSize) {
setValidationError('Total file size exceeds 50MB limit');
return false;
}
setValidationError('');
return true;
};
const handleFilesChange = (newFiles) => {
if (validateFiles(newFiles)) {
setFiles(newFiles);
}
};
return (
<FieldDropzoneNew
label="Upload with Validation"
value={files}
onFilesChange={handleFilesChange}
token={authToken}
error={validationError}
maxFileSize={10}
/>
);
}
Integration with Form Libraries
import { useForm } from 'react-hook-form';
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew';
function FormWithUpload() {
const { register, setValue, watch, formState: { errors } } = useForm();
const files = watch('attachments') || [];
return (
<form>
<FieldDropzoneNew
label="Attachments"
value={files}
onFilesChange={(newFiles) => setValue('attachments', newFiles)}
token={authToken}
required
error={errors.attachments?.message}
{...register('attachments', {
required: 'Please upload at least one file'
})}
/>
</form>
);
}
🗜️ Image Compression
Compression Configuration
interface CompressionOptions {
maxWidth?: number; // Maximum width in pixels (default: 1920)
maxHeight?: number; // Maximum height in pixels (default: 1080)
quality?: number; // JPEG quality 0-1 (default: 0.8)
maxSizeKB?: number; // Target size in KB (default: 200)
}
Compression Algorithm
Component ini menggunakan compression algorithm berikut:
- Threshold Check: Files > 50KB akan dikompres
- Dimension Resize: Resize ke maxWidth/maxHeight jika perlu
- Quality Adjustment: Kurangi quality untuk mencapai target size
- Multiple Attempts: Iterative compression untuk optimal size/quality
Compression Examples
// High quality compression
const highQualityOptions = {
maxWidth: 2560,
maxHeight: 1440,
quality: 0.95,
maxSizeKB: 2000
};
// Mobile optimized compression
const mobileOptions = {
maxWidth: 1080,
maxHeight: 1920,
quality: 0.7,
maxSizeKB: 300
};
// Thumbnail compression
const thumbnailOptions = {
maxWidth: 400,
maxHeight: 400,
quality: 0.8,
maxSizeKB: 50
};
🎯 File Type Support
Supported MIME Types
| Category | Types | Preview Support |
|---|---|---|
| Images | image/jpeg, image/png, image/gif, image/webp | Full preview |
| Documents | application/pdf | PDF preview |
| Videos | video/mp4, video/webm | Video preview |
| Office | .doc, .docx, .xls, .xlsx | Icon only |
Default Accepted Types
const defaultAcceptedTypes = [
'application/pdf',
'image/png',
'image/jpeg',
'image/jpg'
];
🔄 File Lifecycle
Upload Flow
Remove Flow
🎨 Styling & Theming
Custom CSS Classes
/* Main container */
.dropzone-field {
/* Custom styles */
}
/* Dropzone area */
.dropzone-area {
border: 2px dashed #cbd5e0;
border-radius: 8px;
transition: all 0.2s ease;
}
.dropzone-area:hover {
border-color: #4299e1;
background-color: #ebf8ff;
}
.dropzone-area.accepting {
border-color: #48bb78;
background-color: #f0fff4;
}
/* File preview grid */
.file-grid {
display: grid;
gap: 16px;
}
.file-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.file-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.file-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
Theme Integration
import { useMantineTheme } from '@mantine/core';
function ThemedDropzone() {
const theme = useMantineTheme();
return (
<FieldDropzoneNew
label="Upload Files"
value={files}
onFilesChange={setFiles}
token={token}
styles={{
root: {
borderColor: theme.colors.gray[3]
},
active: {
borderColor: theme.colors.blue[5],
backgroundColor: theme.colors.blue[0]
}
}}
/>
);
}
🔧 Dependencies
Required Dependencies
npm install @mantine/core @mantine/dropzone @mantine/hooks
Internal Dependencies
@/lib/axios/config- API client configuration@/utils/compressImages- Image compression utilities@/utils/fileUtils- File processing utilities
API Endpoints
Component ini membutuhkan API endpoints berikut:
// Upload endpoint
POST /api/upload
Headers: { Authorization: `Bearer ${token}` }
Body: FormData
Response: { filePath: string, fileName: string }
// Delete endpoint
DELETE /api/upload/:filePath
Headers: { Authorization: `Bearer ${token}` }
Response: { success: boolean }
🚨 Error Handling
Common Error Scenarios
function ErrorHandlingDropzone() {
const [uploadError, setUploadError] = useState('');
const [uploadProgress, setUploadProgress] = useState(0);
const handleUploadError = (error) => {
console.error('Upload failed:', error);
setUploadError(getErrorMessage(error));
};
const getErrorMessage = (error) => {
if (error.code === 'file-too-large') {
return 'File size exceeds limit';
}
if (error.code === 'file-invalid-type') {
return 'File type not supported';
}
if (error.response?.status === 401) {
return 'Authentication failed';
}
return 'Upload failed. Please try again.';
};
return (
<FieldDropzoneNew
label="Upload Files"
value={files}
onFilesChange={handleFilesChange}
token={token}
error={uploadError}
onUploadProgress={setUploadProgress}
onUploadError={handleUploadError}
/>
);
}
Retry Mechanism
function ResilientDropzone() {
const [retryCount, setRetryCount] = useState(0);
const handleFilesChangeWithRetry = async (files) => {
try {
await uploadFiles(files);
setRetryCount(0);
} catch (error) {
if (retryCount < 3) {
setRetryCount(prev => prev + 1);
setTimeout(() => {
handleFilesChangeWithRetry(files);
}, 1000 * retryCount);
} else {
console.error('Upload failed after 3 attempts');
}
}
};
return (
<FieldDropzoneNew
label="Upload Files"
value={files}
onFilesChange={handleFilesChangeWithRetry}
token={token}
/>
);
}
📚 Best Practices
1. Performance Optimization
// Debounce upload calls
import { useMemo, useCallback } from 'react';
function OptimizedUpload() {
const debouncedUpload = useMemo(
() => debounce((files) => uploadFiles(files), 1000),
[]
);
const handleFilesChange = useCallback((files) => {
debouncedUpload(files);
}, [debouncedUpload]);
return (
<FieldDropzoneNew
label="Upload Files"
value={files}
onFilesChange={handleFilesChange}
token={token}
/>
);
}
2. Security
function SecureUpload() {
const sanitizedFiles = (files) => {
return files.map(file => {
// Validate file signatures
if (!isValidFileType(file)) {
throw new Error('Invalid file type');
}
return file;
});
};
return (
<FieldDropzoneNew
label="Secure Upload"
value={files}
onFilesChange={sanitizedFiles}
token={token}
maxFileSize={10}
acceptedFileTypes={['image/jpeg', 'image/png', 'application/pdf']}
/>
);
}
3. User Experience
function UserFriendlyUpload() {
const [uploadProgress, setUploadProgress] = useState({});
const handleUploadProgress = (fileIndex, progress) => {
setUploadProgress(prev => ({
...prev,
[fileIndex]: progress
}));
};
return (
<FieldDropzoneNew
label="Upload Files"
value={files}
onFilesChange={handleFilesChange}
token={token}
showProgress={true}
onUploadProgress={handleUploadProgress}
dropzoneProps={{
multiple: true,
maxFiles: 10
}}
/>
);
}
🔍 Troubleshooting
Common Issues
Problem: Files not compressing Solution: Check file size threshold (50KB default) and compression options
Problem: Upload fails with 401 error Solution: Verify token is valid and not expired
Problem: Preview not showing Solution: Check file paths and ensure files are accessible
Problem: Remove action not working Solution: Ensure API endpoint exists and token has delete permissions
Debug Mode
function DebugDropzone() {
const handleFilesChange = (files) => {
console.log('Files received:', files);
files.forEach((file, index) => {
console.log(`File ${index}:`, {
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
});
});
};
return (
<FieldDropzoneNew
label="Debug Upload"
value={files}
onFilesChange={handleFilesChange}
token={token}
debug={true}
/>
);
}
🔗 Related Components
- FileViewer - Display uploaded files
- ModalFileViewer - Modal preview for files