Saltar al contenido principal

Progreso de carga

Dropup proporciona seguimiento detallado del progreso para todas las cargas.

Seguimiento de progreso

Progreso de archivo individual

Cada archivo tiene una propiedad 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>
))}

Progreso general

Rastrea el progreso de todos los archivos:

const { state } = useDropup();

// state.progress - Progreso promedio de todos los archivos (0-100)
// state.isUploading - Si alguna carga está en progreso

<div>
{state.isUploading && (
<p>Cargando: {state.progress}%</p>
)}
</div>

Callbacks de progreso

Progreso por archivo

useDropup({
upload: { url: '/api/upload' },
onUploadProgress: (file, progress) => {
console.log(`${file.name}: ${progress}%`);
},
});

Eventos de carga

useDropup({
upload: { url: '/api/upload' },

onUploadStart: (file) => {
console.log(`Iniciado: ${file.name}`);
},

onUploadProgress: (file, progress) => {
console.log(`Progreso: ${file.name} - ${progress}%`);
},

onUploadComplete: (file, response) => {
console.log(`Completado: ${file.name}`);
console.log(`URL: ${file.uploadedUrl}`);
},

onUploadError: (file, error) => {
console.error(`Falló: ${file.name}`, error);
},

onAllComplete: (files) => {
console.log(`¡Todo listo! ${files.length} archivos cargados`);
},
});

Construyendo UI de progreso

Barra de progreso simple

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>
);
}

// Uso
{file.status === 'uploading' && (
<ProgressBar value={file.progress} />
)}

Progreso circular

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>
);
}

Tarjeta de carga de archivo completa

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,
}}>
{/* Vista previa o ícono */}
{file.preview ? (
<img
src={file.preview}
alt=""
style={{ width: 48, height: 48, objectFit: 'cover', borderRadius: 4 }}
/>
) : (
<span style={{ fontSize: 32 }}>{getStatusIcon()}</span>
)}

{/* Info del archivo */}
<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>

{/* Barra de progreso */}
{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>
)}

{/* Mensaje de error */}
{file.error && (
<div style={{ color: '#f44336', fontSize: 12, marginTop: 4 }}>
{file.error.message}
</div>
)}
</div>

{/* Badge de estado */}
<span style={{
padding: '4px 8px',
borderRadius: 4,
fontSize: 12,
color: 'white',
backgroundColor: getStatusColor(),
}}>
{file.status === 'uploading' ? `${file.progress}%` : file.status}
</span>
</div>
);
}

Conteos de estado

Rastrea conteos de estado de carga:

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,
};

// Mostrar
<div>
<p>Total: {counts.total}</p>
<p>Cargando: {counts.uploading}</p>
<p>Completos: {counts.complete}</p>
<p>Fallidos: {counts.failed}</p>
</div>

Velocidad de carga y tiempo estimado

Calcula velocidad de carga y tiempo estimado:

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 };
}

// Uso
function FileProgress({ file }: { file: DropupFile }) {
const { speed, eta } = useUploadSpeed(file);

return (
<div>
<p>Velocidad: {formatSpeed(speed)}</p>
<p>Tiempo estimado: {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)}s`;
return `${Math.ceil(seconds / 60)}m`;
}