Check for and display existing operations on settings page load
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
@@ -58,6 +58,217 @@ export function Settings() {
|
||||
purchaseOrders: 10000
|
||||
});
|
||||
|
||||
// Helper to connect to event source
|
||||
const connectToEventSource = useCallback((type: 'update' | 'import' | 'reset') => {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
}
|
||||
|
||||
console.log('Connecting to event source:', type); // Debug log
|
||||
const source = new EventSource(`${config.apiUrl}/csv/${type}/progress`, {
|
||||
withCredentials: true
|
||||
});
|
||||
setEventSource(source);
|
||||
|
||||
source.onopen = () => {
|
||||
console.log('Event source connected:', type); // Debug log
|
||||
};
|
||||
|
||||
source.onerror = () => {
|
||||
console.log('Event source error:', type, source.readyState); // Debug log
|
||||
if (source.readyState === EventSource.CLOSED) {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
|
||||
// Only reset states if we're not in a completed state
|
||||
const progress = type === 'update' ? updateProgress :
|
||||
type === 'import' ? importProgress :
|
||||
resetProgress;
|
||||
|
||||
if (progress?.status !== 'complete') {
|
||||
// Reset the appropriate state based on type
|
||||
if (type === 'update') {
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
} else if (type === 'import') {
|
||||
setIsImporting(false);
|
||||
setImportProgress(null);
|
||||
} else if (type === 'reset') {
|
||||
setIsResetting(false);
|
||||
setResetProgress(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
source.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Event source message:', type, data); // Debug log
|
||||
let progressData = data.progress ?
|
||||
(typeof data.progress === 'string' ? JSON.parse(data.progress) : data.progress)
|
||||
: data;
|
||||
|
||||
// Update the appropriate progress state based on type
|
||||
const setProgress = type === 'update' ? setUpdateProgress :
|
||||
type === 'import' ? setImportProgress :
|
||||
setResetProgress;
|
||||
|
||||
// Also set the operation state if we're getting progress
|
||||
if (progressData.status === 'running') {
|
||||
if (type === 'update') setIsUpdating(true);
|
||||
else if (type === 'import') setIsImporting(true);
|
||||
else if (type === 'reset') setIsResetting(true);
|
||||
}
|
||||
|
||||
setProgress(prev => {
|
||||
// If we're getting a new operation, clear out old messages
|
||||
if (progressData.operation && progressData.operation !== prev?.operation) {
|
||||
return {
|
||||
status: progressData.status || 'running',
|
||||
operation: progressData.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : undefined,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : undefined,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : undefined,
|
||||
percentage: progressData.percentage,
|
||||
elapsed: progressData.elapsed,
|
||||
remaining: progressData.remaining,
|
||||
message: progressData.message,
|
||||
error: progressData.error,
|
||||
added: progressData.added,
|
||||
updated: progressData.updated,
|
||||
skipped: progressData.skipped,
|
||||
duration: progressData.duration
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise update existing state
|
||||
return {
|
||||
...prev,
|
||||
status: progressData.status || prev?.status || 'running',
|
||||
operation: progressData.operation || prev?.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : prev?.current,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : prev?.total,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : prev?.rate,
|
||||
percentage: progressData.percentage !== undefined ? progressData.percentage : prev?.percentage,
|
||||
elapsed: progressData.elapsed || prev?.elapsed,
|
||||
remaining: progressData.remaining || prev?.remaining,
|
||||
error: progressData.error || prev?.error,
|
||||
message: progressData.message || prev?.message,
|
||||
added: progressData.added !== undefined ? progressData.added : prev?.added,
|
||||
updated: progressData.updated !== undefined ? progressData.updated : prev?.updated,
|
||||
skipped: progressData.skipped !== undefined ? progressData.skipped : prev?.skipped,
|
||||
duration: progressData.duration || prev?.duration
|
||||
};
|
||||
});
|
||||
|
||||
if (progressData.status === 'complete') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
|
||||
// Reset the appropriate state based on type
|
||||
if (type === 'update') {
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
} else if (type === 'import') {
|
||||
setIsImporting(false);
|
||||
setImportProgress(null);
|
||||
} else if (type === 'reset') {
|
||||
setIsResetting(false);
|
||||
setResetProgress(null);
|
||||
}
|
||||
|
||||
if (!progressData.operation?.includes('cancelled')) {
|
||||
handleComplete(`${type.charAt(0).toUpperCase() + type.slice(1)}`);
|
||||
}
|
||||
} else if (progressData.status === 'error') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
|
||||
// Reset the appropriate state based on type
|
||||
if (type === 'update') {
|
||||
setIsUpdating(false);
|
||||
} else if (type === 'import') {
|
||||
setIsImporting(false);
|
||||
} else if (type === 'reset') {
|
||||
setIsResetting(false);
|
||||
}
|
||||
|
||||
handleError(`${type.charAt(0).toUpperCase() + type.slice(1)}`, progressData.error || 'Unknown error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing event data:', error); // Debug log
|
||||
}
|
||||
};
|
||||
}, []); // Remove dependencies that might prevent initial connection
|
||||
|
||||
// Check for active operations on mount
|
||||
useEffect(() => {
|
||||
console.log('Checking status on mount'); // Debug log
|
||||
const checkStatus = async () => {
|
||||
try {
|
||||
const response = await fetch(`${config.apiUrl}/csv/status`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log('Status check response:', data); // Debug log
|
||||
|
||||
if (data.active) {
|
||||
// Try to determine the operation type from progress if available
|
||||
let operationType: 'update' | 'import' | 'reset' | null = null;
|
||||
|
||||
if (data.progress?.operation) {
|
||||
const operation = data.progress.operation.toLowerCase();
|
||||
if (operation.includes('update')) operationType = 'update';
|
||||
else if (operation.includes('import')) operationType = 'import';
|
||||
else if (operation.includes('reset')) operationType = 'reset';
|
||||
} else {
|
||||
// If no progress data, try to connect to import stream by default
|
||||
// since that's the most common long-running operation
|
||||
operationType = 'import';
|
||||
}
|
||||
|
||||
if (operationType) {
|
||||
// Set initial state
|
||||
if (operationType === 'update') {
|
||||
setIsUpdating(true);
|
||||
if (data.progress) {
|
||||
setUpdateProgress({
|
||||
...data.progress,
|
||||
status: data.progress.status || 'running'
|
||||
});
|
||||
}
|
||||
} else if (operationType === 'import') {
|
||||
setIsImporting(true);
|
||||
if (data.progress) {
|
||||
setImportProgress({
|
||||
...data.progress,
|
||||
status: data.progress.status || 'running'
|
||||
});
|
||||
}
|
||||
} else if (operationType === 'reset') {
|
||||
setIsResetting(true);
|
||||
if (data.progress) {
|
||||
setResetProgress({
|
||||
...data.progress,
|
||||
status: data.progress.status || 'running'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to the appropriate event source
|
||||
console.log('Connecting to event source for active operation:', operationType);
|
||||
connectToEventSource(operationType);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check operation status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
checkStatus();
|
||||
}, []); // Remove connectToEventSource dependency to ensure it runs on mount
|
||||
|
||||
// Clean up function to reset state
|
||||
|
||||
const handleCancel = async () => {
|
||||
@@ -96,104 +307,22 @@ export function Settings() {
|
||||
setUpdateProgress({ status: 'running', operation: 'Starting CSV update' });
|
||||
|
||||
try {
|
||||
// Set up SSE connection for progress updates first
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
setEventSource(null);
|
||||
}
|
||||
// Connect to SSE for progress updates
|
||||
connectToEventSource('update');
|
||||
|
||||
// Set up SSE connection for progress updates
|
||||
const source = new EventSource(`${config.apiUrl}/csv/update/progress`, {
|
||||
withCredentials: true
|
||||
});
|
||||
setEventSource(source);
|
||||
|
||||
// Add event listeners for all SSE events
|
||||
source.onopen = () => {};
|
||||
|
||||
source.onerror = () => {
|
||||
if (source.readyState === EventSource.CLOSED) {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsUpdating(false);
|
||||
// Only show connection error if we're not in a cancelled state
|
||||
if (!updateProgress?.operation?.includes('cancelled')) {
|
||||
setUpdateProgress(prev => ({
|
||||
...prev,
|
||||
status: 'error',
|
||||
error: 'Connection to server lost'
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
source.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
let progressData = data.progress ?
|
||||
(typeof data.progress === 'string' ? JSON.parse(data.progress) : data.progress)
|
||||
: data;
|
||||
|
||||
setUpdateProgress(prev => {
|
||||
// If we're getting a new operation, clear out old messages
|
||||
if (progressData.operation && progressData.operation !== prev?.operation) {
|
||||
return {
|
||||
status: progressData.status || 'running',
|
||||
operation: progressData.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : undefined,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : undefined,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : undefined,
|
||||
percentage: progressData.percentage,
|
||||
elapsed: progressData.elapsed,
|
||||
remaining: progressData.remaining,
|
||||
message: progressData.message,
|
||||
error: progressData.error
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise update existing state
|
||||
return {
|
||||
...prev,
|
||||
status: progressData.status || prev?.status || 'running',
|
||||
operation: progressData.operation || prev?.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : prev?.current,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : prev?.total,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : prev?.rate,
|
||||
percentage: progressData.percentage !== undefined ? progressData.percentage : prev?.percentage,
|
||||
elapsed: progressData.elapsed || prev?.elapsed,
|
||||
remaining: progressData.remaining || prev?.remaining,
|
||||
error: progressData.error || prev?.error,
|
||||
message: progressData.message || prev?.message
|
||||
};
|
||||
});
|
||||
|
||||
if (progressData.status === 'complete') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
if (!progressData.operation?.includes('cancelled')) {
|
||||
handleComplete('CSV update');
|
||||
}
|
||||
} else if (progressData.status === 'error') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsUpdating(false);
|
||||
handleError('CSV update', progressData.error || 'Unknown error');
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle parsing errors
|
||||
}
|
||||
};
|
||||
|
||||
// Now make the update request
|
||||
// Make the update request
|
||||
const response = await fetch(`${config.apiUrl}/csv/update`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to update CSV files: ${response.status} ${response.statusText}`);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (data.error === 'Import already in progress') {
|
||||
// If there's already an import, just let the SSE connection handle showing progress
|
||||
return;
|
||||
}
|
||||
throw new Error(data.error || `Failed to update CSV files: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (eventSource) {
|
||||
@@ -211,99 +340,10 @@ export function Settings() {
|
||||
setImportProgress({ status: 'running', operation: 'Starting import process' });
|
||||
|
||||
try {
|
||||
// Set up SSE connection for progress updates first
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
setEventSource(null);
|
||||
}
|
||||
// Connect to SSE for progress updates
|
||||
connectToEventSource('import');
|
||||
|
||||
// Set up SSE connection for progress updates
|
||||
const source = new EventSource(`${config.apiUrl}/csv/import/progress`, {
|
||||
withCredentials: true
|
||||
});
|
||||
setEventSource(source);
|
||||
|
||||
// Add event listeners for all SSE events
|
||||
source.onopen = () => {};
|
||||
|
||||
source.onerror = () => {
|
||||
if (source.readyState === EventSource.CLOSED) {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsImporting(false);
|
||||
// Only show connection error if we're not in a cancelled state
|
||||
if (!importProgress?.operation?.includes('cancelled') && importProgress?.status !== 'complete') {
|
||||
setImportProgress(prev => ({
|
||||
...prev,
|
||||
status: 'error',
|
||||
error: 'Connection to server lost'
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
source.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
let progressData = data.progress ?
|
||||
(typeof data.progress === 'string' ? JSON.parse(data.progress) : data.progress)
|
||||
: data;
|
||||
|
||||
setImportProgress(prev => {
|
||||
// If we're getting a new operation, clear out old messages
|
||||
if (progressData.operation && progressData.operation !== prev?.operation) {
|
||||
return {
|
||||
status: progressData.status || 'running',
|
||||
operation: progressData.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : undefined,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : undefined,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : undefined,
|
||||
percentage: progressData.percentage,
|
||||
elapsed: progressData.elapsed,
|
||||
remaining: progressData.remaining,
|
||||
message: progressData.message,
|
||||
error: progressData.error
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise update existing state
|
||||
return {
|
||||
...prev,
|
||||
status: progressData.status || prev?.status || 'running',
|
||||
operation: progressData.operation || prev?.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : prev?.current,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : prev?.total,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : prev?.rate,
|
||||
percentage: progressData.percentage !== undefined ? progressData.percentage : prev?.percentage,
|
||||
elapsed: progressData.elapsed || prev?.elapsed,
|
||||
remaining: progressData.remaining || prev?.remaining,
|
||||
error: progressData.error || prev?.error,
|
||||
message: progressData.message || prev?.message
|
||||
};
|
||||
});
|
||||
|
||||
if (progressData.status === 'complete') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsUpdating(false);
|
||||
setIsImporting(false);
|
||||
setImportProgress(null);
|
||||
if (!progressData.operation?.includes('cancelled')) {
|
||||
handleComplete('Data import');
|
||||
}
|
||||
} else if (progressData.status === 'error') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsUpdating(false);
|
||||
setIsImporting(false);
|
||||
handleError('Data import', progressData.error || 'Unknown error');
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle parsing errors
|
||||
}
|
||||
};
|
||||
|
||||
// Now make the import request
|
||||
// Make the import request
|
||||
const response = await fetch(`${config.apiUrl}/csv/import`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -314,7 +354,12 @@ export function Settings() {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to start CSV import');
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (data.error === 'Import already in progress') {
|
||||
// If there's already an import, just let the SSE connection handle showing progress
|
||||
return;
|
||||
}
|
||||
throw new Error(data.error || 'Failed to start CSV import');
|
||||
}
|
||||
} catch (error) {
|
||||
if (eventSource) {
|
||||
@@ -332,104 +377,22 @@ export function Settings() {
|
||||
setResetProgress({ status: 'running', operation: 'Starting database reset' });
|
||||
|
||||
try {
|
||||
// Set up SSE connection for progress updates first
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
setEventSource(null);
|
||||
}
|
||||
// Connect to SSE for progress updates
|
||||
connectToEventSource('reset');
|
||||
|
||||
// Set up SSE connection for progress updates
|
||||
const source = new EventSource(`${config.apiUrl}/csv/reset/progress`, {
|
||||
withCredentials: true
|
||||
});
|
||||
setEventSource(source);
|
||||
|
||||
// Add event listeners for all SSE events
|
||||
source.onopen = () => {};
|
||||
|
||||
source.onerror = () => {
|
||||
if (source.readyState === EventSource.CLOSED) {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsResetting(false);
|
||||
// Only show connection error if we're not in a cancelled state
|
||||
if (!resetProgress?.operation?.includes('cancelled')) {
|
||||
setResetProgress(prev => ({
|
||||
...prev,
|
||||
status: 'error',
|
||||
error: 'Connection to server lost'
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
source.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
let progressData = data.progress ?
|
||||
(typeof data.progress === 'string' ? JSON.parse(data.progress) : data.progress)
|
||||
: data;
|
||||
|
||||
setResetProgress(prev => {
|
||||
// If we're getting a new operation, clear out old messages
|
||||
if (progressData.operation && progressData.operation !== prev?.operation) {
|
||||
return {
|
||||
status: progressData.status || 'running',
|
||||
operation: progressData.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : undefined,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : undefined,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : undefined,
|
||||
percentage: progressData.percentage,
|
||||
elapsed: progressData.elapsed,
|
||||
remaining: progressData.remaining,
|
||||
message: progressData.message,
|
||||
error: progressData.error
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise update existing state
|
||||
return {
|
||||
...prev,
|
||||
status: progressData.status || prev?.status || 'running',
|
||||
operation: progressData.operation || prev?.operation,
|
||||
current: progressData.current !== undefined ? Number(progressData.current) : prev?.current,
|
||||
total: progressData.total !== undefined ? Number(progressData.total) : prev?.total,
|
||||
rate: progressData.rate !== undefined ? Number(progressData.rate) : prev?.rate,
|
||||
percentage: progressData.percentage !== undefined ? progressData.percentage : prev?.percentage,
|
||||
elapsed: progressData.elapsed || prev?.elapsed,
|
||||
remaining: progressData.remaining || prev?.remaining,
|
||||
error: progressData.error || prev?.error,
|
||||
message: progressData.message || prev?.message
|
||||
};
|
||||
});
|
||||
|
||||
if (progressData.status === 'complete') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsResetting(false);
|
||||
setResetProgress(null);
|
||||
if (!progressData.operation?.includes('cancelled')) {
|
||||
handleComplete('Database reset');
|
||||
}
|
||||
} else if (progressData.status === 'error') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsResetting(false);
|
||||
handleError('Database reset', progressData.error || 'Unknown error');
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle parsing errors
|
||||
}
|
||||
};
|
||||
|
||||
// Now make the reset request
|
||||
// Make the reset request
|
||||
const response = await fetch(`${config.apiUrl}/csv/reset`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to reset database');
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (data.error === 'Import already in progress') {
|
||||
// If there's already an import, just let the SSE connection handle showing progress
|
||||
return;
|
||||
}
|
||||
throw new Error(data.error || 'Failed to reset database');
|
||||
}
|
||||
} catch (error) {
|
||||
if (eventSource) {
|
||||
@@ -446,6 +409,7 @@ export function Settings() {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (eventSource) {
|
||||
console.log('Cleaning up event source'); // Debug log
|
||||
eventSource.close();
|
||||
}
|
||||
};
|
||||
@@ -466,13 +430,47 @@ export function Settings() {
|
||||
{percentage !== null && (
|
||||
<>
|
||||
<Progress value={percentage} className="h-2" />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<div className="flex flex-col gap-1 text-xs text-muted-foreground">
|
||||
{progress.current && progress.total && (
|
||||
<div className="flex justify-between">
|
||||
<span>Progress:</span>
|
||||
<span>{progress.current.toLocaleString()} / {progress.total.toLocaleString()} {progress.rate ? `(${Math.round(progress.rate)}/s)` : ''}</span>
|
||||
</div>
|
||||
)}
|
||||
{(progress.elapsed || progress.remaining) && (
|
||||
<div className="flex justify-between">
|
||||
<span>Time:</span>
|
||||
<span>
|
||||
{progress.elapsed && `${progress.elapsed} elapsed`}
|
||||
{progress.elapsed && progress.remaining && ' - '}
|
||||
{progress.remaining && `${progress.remaining} remaining`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{(progress.added !== undefined || progress.updated !== undefined || progress.skipped !== undefined) && (
|
||||
<div className="flex justify-between">
|
||||
<span>Results:</span>
|
||||
<span>
|
||||
{progress.added !== undefined && `${progress.added.toLocaleString()} added`}
|
||||
{progress.added !== undefined && progress.updated !== undefined && ', '}
|
||||
{progress.updated !== undefined && `${progress.updated.toLocaleString()} updated`}
|
||||
{((progress.added !== undefined || progress.updated !== undefined) && progress.skipped !== undefined) && ', '}
|
||||
{progress.skipped !== undefined && `${progress.skipped.toLocaleString()} skipped`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{progress.duration && (
|
||||
<div className="flex justify-between">
|
||||
<span>Duration:</span>
|
||||
<span>{progress.duration}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{progress.message && (
|
||||
<div className="text-xs text-muted-foreground">{progress.message}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user