Progres Upload
Dropup menyediakan pelacakan progres detail untuk semua upload.
Pelacakan Progres
Progres File Individual
Setiap file memiliki properti progress (0-100):
const { files } = useDropup({
upload: { url: '/api/upload' },
});
{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
{file.status === 'uploading' && (
<progress value={file.progress} max={100} />
)}
</div>
))}
Progres Keseluruhan
Lacak progres di semua file:
const { state } = useDropup();
// state.progress - Rata-rata progres semua file (0-100)
// state.isUploading - Apakah ada upload yang sedang berlangsung
<div>
{state.isUploading && (
<p>Mengupload: {state.progress}%</p>
)}
</div>
Callback Progres
Progres Per-File
useDropup({
upload: { url: '/api/upload' },
onUploadProgress: (file, progress) => {
console.log(`${file.name}: ${progress}%`);
},
});
Event Upload
useDropup({
upload: { url: '/api/upload' },
onUploadStart: (file) => {
console.log(`Dimulai: ${file.name}`);
},
onUploadProgress: (file, progress) => {
console.log(`Progres: ${file.name} - ${progress}%`);
},
onUploadComplete: (file, response) => {
console.log(`Selesai: ${file.name}`);
console.log(`URL: ${file.uploadedUrl}`);
},
onUploadError: (file, error) => {
console.error(`Gagal: ${file.name}`, error);
},
onAllComplete: (files) => {
console.log(`Semua selesai! ${files.length} file terupload`);
},
});
Membangun UI Progres
Progress Bar Sederhana
function ProgressBar({ value }: { value: number }) {
return (
<div style={{
width: '100%',
height: 8,
backgroundColor: '#e0e0e0',
borderRadius: 4,
}}>
<div style={{
width: `${value}%`,
height: '100%',
backgroundColor: '#4caf50',
borderRadius: 4,
transition: 'width 0.2s',
}} />
</div>
);
}
// Penggunaan
{file.status === 'uploading' && (
<ProgressBar value={file.progress} />
)}
Progres Melingkar
function CircularProgress({ value, size = 40 }: { value: number; size?: number }) {
const strokeWidth = 4;
const radius = (size - strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (value / 100) * circumference;
return (
<svg width={size} height={size}>
<circle
stroke="#e0e0e0"
fill="transparent"
strokeWidth={strokeWidth}
r={radius}
cx={size / 2}
cy={size / 2}
/>
<circle
stroke="#4caf50"
fill="transparent"
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={offset}
r={radius}
cx={size / 2}
cy={size / 2}
style={{
transform: 'rotate(-90deg)',
transformOrigin: '50% 50%',
transition: 'stroke-dashoffset 0.2s',
}}
/>
<text
x="50%"
y="50%"
textAnchor="middle"
dy=".3em"
fontSize={size / 4}
>
{value}%
</text>
</svg>
);
}
Kartu Upload File Lengkap
function FileUploadCard({ file }: { file: DropupFile }) {
const getStatusIcon = () => {
switch (file.status) {
case 'idle': return '⏳';
case 'uploading': return '📤';
case 'complete': return '✅';
case 'error': return '❌';
case 'paused': return '⏸️';
default: return '📄';
}
};
const getStatusColor = () => {
switch (file.status) {
case 'uploading': return '#2196f3';
case 'complete': return '#4caf50';
case 'error': return '#f44336';
default: return '#9e9e9e';
}
};
return (
<div style={{
display: 'flex',
alignItems: 'center',
padding: 12,
border: '1px solid #e0e0e0',
borderRadius: 8,
marginBottom: 8,
}}>
{/* Preview atau Ikon */}
{file.preview ? (
<img
src={file.preview}
alt=""
style={{ width: 48, height: 48, objectFit: 'cover', borderRadius: 4 }}
/>
) : (
<span style={{ fontSize: 32 }}>{getStatusIcon()}</span>
)}
{/* Info File */}
<div style={{ flex: 1, marginLeft: 12 }}>
<div style={{ fontWeight: 500 }}>{file.name}</div>
<div style={{ fontSize: 12, color: '#666' }}>
{(file.size / 1024).toFixed(1)} KB
</div>
{/* Progress Bar */}
{file.status === 'uploading' && (
<div style={{
marginTop: 4,
height: 4,
backgroundColor: '#e0e0e0',
borderRadius: 2,
}}>
<div style={{
width: `${file.progress}%`,
height: '100%',
backgroundColor: getStatusColor(),
borderRadius: 2,
}} />
</div>
)}
{/* Pesan Error */}
{file.error && (
<div style={{ color: '#f44336', fontSize: 12, marginTop: 4 }}>
{file.error.message}
</div>
)}
</div>
{/* Badge Status */}
<span style={{
padding: '4px 8px',
borderRadius: 4,
fontSize: 12,
color: 'white',
backgroundColor: getStatusColor(),
}}>
{file.status === 'uploading' ? `${file.progress}%` : file.status}
</span>
</div>
);
}
Hitungan Status
Lacak hitungan status upload:
const { files, state } = useDropup();
const counts = {
total: files.length,
pending: files.filter(f => f.status === 'idle').length,
uploading: files.filter(f => f.status === 'uploading').length,
complete: files.filter(f => f.status === 'complete').length,
failed: files.filter(f => f.status === 'error').length,
};
// Tampilkan
<div>
<p>Total: {counts.total}</p>
<p>Mengupload: {counts.uploading}</p>
<p>Selesai: {counts.complete}</p>
<p>Gagal: {counts.failed}</p>
</div>
Kecepatan Upload & Estimasi Waktu
Hitung kecepatan upload dan estimasi waktu:
import { useState, useRef, useEffect } from 'react';
function useUploadSpeed(file: DropupFile) {
const [speed, setSpeed] = useState(0);
const [eta, setEta] = useState<number | null>(null);
const lastProgress = useRef(0);
const lastTime = useRef(Date.now());
useEffect(() => {
if (file.status !== 'uploading') return;
const interval = setInterval(() => {
const now = Date.now();
const elapsed = (now - lastTime.current) / 1000;
const progressDiff = file.progress - lastProgress.current;
const bytesDiff = (progressDiff / 100) * file.size;
if (elapsed > 0) {
const currentSpeed = bytesDiff / elapsed;
setSpeed(currentSpeed);
const remaining = file.size * (1 - file.progress / 100);
if (currentSpeed > 0) {
setEta(remaining / currentSpeed);
}
}
lastProgress.current = file.progress;
lastTime.current = now;
}, 1000);
return () => clearInterval(interval);
}, [file.status, file.progress, file.size]);
return { speed, eta };
}
// Penggunaan
function FileProgress({ file }: { file: DropupFile }) {
const { speed, eta } = useUploadSpeed(file);
return (
<div>
<p>Kecepatan: {formatSpeed(speed)}</p>
<p>Estimasi: {formatTime(eta)}</p>
</div>
);
}
function formatSpeed(bytesPerSecond: number): string {
if (bytesPerSecond < 1024) return `${bytesPerSecond.toFixed(0)} B/s`;
if (bytesPerSecond < 1024 * 1024) return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MB/s`;
}
function formatTime(seconds: number | null): string {
if (!seconds) return '--';
if (seconds < 60) return `${Math.ceil(seconds)}d`;
return `${Math.ceil(seconds / 60)}m`;
}