Skip to main content

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

ModeDescriptionBest For
thumbnailGrid layout dengan preview thumbnailsImage galleries, photo uploads
rowTable layout dengan file detailsDocument 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

PropTypeDefaultDescription
labelstring-Required. Label untuk field
valuearray[]Current files (paths or File objects)
onFilesChangefunction-Required. Handler untuk file upload
tokenstring-Required. Authentication token
requiredbooleanfalseField requirement indicator
errorstring-Error message display
maxFileSizenumber5Maximum file size in MB
acceptedFileTypesstring[]Default typesAllowed file MIME types
viewModeViewMode'thumbnail'Display mode for files
colsnumber3Grid columns for thumbnail mode
enableImageCompressionbooleantrueEnable image compression
removeFilePermanentlybooleantrueDelete files from storage
compressionOptionsobjectDefault settingsImage compression settings

🎨 Examples

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:

  1. Threshold Check: Files > 50KB akan dikompres
  2. Dimension Resize: Resize ke maxWidth/maxHeight jika perlu
  3. Quality Adjustment: Kurangi quality untuk mencapai target size
  4. 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

CategoryTypesPreview Support
Imagesimage/jpeg, image/png, image/gif, image/webpFull preview
Documentsapplication/pdfPDF preview
Videosvideo/mp4, video/webmVideo preview
Office.doc, .docx, .xls, .xlsxIcon 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}
/>
);
}