|
|
@@ -6,18 +6,7 @@ import { Button } from '@/components/ui/button';
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
import { format } from 'date-fns';
|
|
|
import { useToast } from '@/hooks/use-toast';
|
|
|
-import { getImportById, calculateCintasSummaries, triggerImportProcess } from '@/app/actions/imports';
|
|
|
-
|
|
|
-interface CintasSummary {
|
|
|
- id: number;
|
|
|
- week: string;
|
|
|
- trrTotal: number;
|
|
|
- fourWkAverages: number;
|
|
|
- trrPlus4Wk: number;
|
|
|
- powerAdds: number;
|
|
|
- weekId: number;
|
|
|
- importId: number;
|
|
|
-}
|
|
|
+import { getImportById, triggerImportProcess, getImportProgress } from '@/app/actions/imports';
|
|
|
|
|
|
interface ImportDetail {
|
|
|
id: number;
|
|
|
@@ -44,13 +33,16 @@ interface ImportDetail {
|
|
|
}>;
|
|
|
}>;
|
|
|
};
|
|
|
- cintasSummaries: CintasSummary[];
|
|
|
file?: {
|
|
|
id: string;
|
|
|
filename: string;
|
|
|
+ mimetype: string;
|
|
|
size: number;
|
|
|
- contentType: string;
|
|
|
- };
|
|
|
+ data: Uint8Array;
|
|
|
+ userId: string | null;
|
|
|
+ createdAt: Date;
|
|
|
+ updatedAt: Date;
|
|
|
+ } | null;
|
|
|
}
|
|
|
|
|
|
interface ImportDetailDialogProps {
|
|
|
@@ -62,9 +54,11 @@ interface ImportDetailDialogProps {
|
|
|
export function ImportDetailDialog({ open, onOpenChange, importId }: ImportDetailDialogProps) {
|
|
|
const [importDetail, setImportDetail] = useState<ImportDetail | null>(null);
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
- const [calculating, setCalculating] = useState(false);
|
|
|
const [processing, setProcessing] = useState(false);
|
|
|
const [importStatus, setImportStatus] = useState<'idle' | 'processing' | 'completed' | 'failed'>('idle');
|
|
|
+ const [progress, setProgress] = useState(0);
|
|
|
+ const [processedRecords, setProcessedRecords] = useState(0);
|
|
|
+ const [totalRecords, setTotalRecords] = useState(0);
|
|
|
const { toast } = useToast();
|
|
|
|
|
|
const loadImportDetail = useCallback(async () => {
|
|
|
@@ -98,34 +92,6 @@ export function ImportDetailDialog({ open, onOpenChange, importId }: ImportDetai
|
|
|
}
|
|
|
}, [open, importId, loadImportDetail]);
|
|
|
|
|
|
- async function handleCalculateSummaries() {
|
|
|
- setCalculating(true);
|
|
|
- try {
|
|
|
- const result = await calculateCintasSummaries(importId);
|
|
|
- if (result.success) {
|
|
|
- toast({
|
|
|
- title: 'Success',
|
|
|
- description: 'Cintas summaries calculated successfully',
|
|
|
- });
|
|
|
- loadImportDetail();
|
|
|
- } else {
|
|
|
- toast({
|
|
|
- title: 'Error',
|
|
|
- description: result.error || 'Failed to calculate summaries',
|
|
|
- variant: 'destructive',
|
|
|
- });
|
|
|
- }
|
|
|
- } catch {
|
|
|
- toast({
|
|
|
- title: 'Error',
|
|
|
- description: 'Failed to calculate summaries',
|
|
|
- variant: 'destructive',
|
|
|
- });
|
|
|
- } finally {
|
|
|
- setCalculating(false);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
async function handleTriggerImport() {
|
|
|
if (!importDetail?.file) {
|
|
|
toast({
|
|
|
@@ -148,17 +114,46 @@ export function ImportDetailDialog({ open, onOpenChange, importId }: ImportDetai
|
|
|
description: result.message || 'Import process started successfully',
|
|
|
});
|
|
|
|
|
|
- // For now, we'll simulate the processing completion
|
|
|
- // In a real implementation, you might use polling or WebSocket
|
|
|
- setTimeout(() => {
|
|
|
- setImportStatus('completed');
|
|
|
- setProcessing(false);
|
|
|
- toast({
|
|
|
- title: 'Import Complete',
|
|
|
- description: 'Import process completed successfully',
|
|
|
- });
|
|
|
- loadImportDetail();
|
|
|
- }, 2000);
|
|
|
+ // Poll for import progress until completion
|
|
|
+ const pollInterval = setInterval(async () => {
|
|
|
+ try {
|
|
|
+ const progressResult = await getImportProgress(importId);
|
|
|
+
|
|
|
+ if (progressResult.success && progressResult.data) {
|
|
|
+ const { status, progress, processedRecords, totalRecords } = progressResult.data;
|
|
|
+
|
|
|
+ // Update progress display
|
|
|
+ setProgress(progress);
|
|
|
+ setProcessedRecords(processedRecords);
|
|
|
+ setTotalRecords(totalRecords);
|
|
|
+
|
|
|
+ if (status === 'completed') {
|
|
|
+ clearInterval(pollInterval);
|
|
|
+ setProcessing(false);
|
|
|
+ setImportStatus('completed');
|
|
|
+ toast({
|
|
|
+ title: 'Import Complete',
|
|
|
+ description: `Successfully imported ${totalRecords} records`,
|
|
|
+ });
|
|
|
+ loadImportDetail();
|
|
|
+ } else if (status === 'failed') {
|
|
|
+ clearInterval(pollInterval);
|
|
|
+ setProcessing(false);
|
|
|
+ setImportStatus('failed');
|
|
|
+ toast({
|
|
|
+ title: 'Import Failed',
|
|
|
+ description: progressResult.data.errorMessage || 'Import processing failed',
|
|
|
+ variant: 'destructive',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error polling import progress:', error);
|
|
|
+ clearInterval(pollInterval);
|
|
|
+ setProcessing(false);
|
|
|
+ setImportStatus('failed');
|
|
|
+ }
|
|
|
+ }, 1000); // Poll every second
|
|
|
|
|
|
} else {
|
|
|
toast({
|
|
|
@@ -269,24 +264,14 @@ export function ImportDetailDialog({ open, onOpenChange, importId }: ImportDetai
|
|
|
<CardHeader>
|
|
|
<div className="flex justify-between items-center">
|
|
|
<CardTitle>Import Actions</CardTitle>
|
|
|
- <div className="flex gap-2">
|
|
|
- <Button
|
|
|
- onClick={handleTriggerImport}
|
|
|
- disabled={processing || !importDetail.file}
|
|
|
- size="sm"
|
|
|
- variant="default"
|
|
|
- >
|
|
|
- {processing ? 'Processing...' : 'Start Import'}
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- onClick={handleCalculateSummaries}
|
|
|
- disabled={calculating || processing}
|
|
|
- size="sm"
|
|
|
- variant="secondary"
|
|
|
- >
|
|
|
- {calculating ? 'Calculating...' : 'Calculate Summaries'}
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
+ <Button
|
|
|
+ onClick={handleTriggerImport}
|
|
|
+ disabled={processing || !importDetail.file}
|
|
|
+ size="sm"
|
|
|
+ variant="default"
|
|
|
+ >
|
|
|
+ {processing ? 'Processing...' : 'Start Import'}
|
|
|
+ </Button>
|
|
|
</div>
|
|
|
</CardHeader>
|
|
|
<CardContent>
|
|
|
@@ -314,41 +299,6 @@ export function ImportDetailDialog({ open, onOpenChange, importId }: ImportDetai
|
|
|
</CardContent>
|
|
|
</Card>
|
|
|
|
|
|
- <Card>
|
|
|
- <CardHeader>
|
|
|
- <CardTitle>Cintas Summaries</CardTitle>
|
|
|
- </CardHeader>
|
|
|
- <CardContent>
|
|
|
- {importDetail.cintasSummaries.length === 0 ? (
|
|
|
- <p className="text-muted-foreground">No summaries calculated yet</p>
|
|
|
- ) : (
|
|
|
- <div className="overflow-x-auto">
|
|
|
- <table className="w-full text-sm">
|
|
|
- <thead>
|
|
|
- <tr className="border-b">
|
|
|
- <th className="text-left py-2">Week</th>
|
|
|
- <th className="text-right py-2">TRR Total</th>
|
|
|
- <th className="text-right py-2">4WK Averages</th>
|
|
|
- <th className="text-right py-2">TRR + 4WK</th>
|
|
|
- <th className="text-right py-2">Power Adds</th>
|
|
|
- </tr>
|
|
|
- </thead>
|
|
|
- <tbody>
|
|
|
- {importDetail.cintasSummaries.map((summary) => (
|
|
|
- <tr key={summary.id} className="border-b">
|
|
|
- <td className="py-2">{summary.week}</td>
|
|
|
- <td className="text-right py-2">{summary.trrTotal}</td>
|
|
|
- <td className="text-right py-2">{summary.fourWkAverages}</td>
|
|
|
- <td className="text-right py-2">{summary.trrPlus4Wk}</td>
|
|
|
- <td className="text-right py-2">{summary.powerAdds}</td>
|
|
|
- </tr>
|
|
|
- ))}
|
|
|
- </tbody>
|
|
|
- </table>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </CardContent>
|
|
|
- </Card>
|
|
|
</div>
|
|
|
|
|
|
<div className="flex justify-end gap-2 mt-6">
|