|
@@ -0,0 +1,236 @@
|
|
|
|
|
+'use client';
|
|
|
|
|
+
|
|
|
|
|
+import { useState, useEffect } from 'react';
|
|
|
|
|
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
|
|
|
|
+import { Button } from '@/components/ui/button';
|
|
|
|
|
+import { Badge } from '@/components/ui/badge';
|
|
|
|
|
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
|
|
+import { format } from 'date-fns';
|
|
|
|
|
+import { useToast } from '@/hooks/use-toast';
|
|
|
|
|
+import { getImportById, calculateCintasSummaries } from '@/app/actions/imports';
|
|
|
|
|
+
|
|
|
|
|
+interface CintasSummary {
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ week: string;
|
|
|
|
|
+ trrTotal: number;
|
|
|
|
|
+ fourWkAverages: number;
|
|
|
|
|
+ trrPlus4Wk: number;
|
|
|
|
|
+ powerAdds: number;
|
|
|
|
|
+ weekId: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface ImportDetail {
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ importDate: string;
|
|
|
|
|
+ layout: {
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ sections: Array<{
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ tableName: string;
|
|
|
|
|
+ fields: Array<{
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ importTableColumnName: string;
|
|
|
|
|
+ }>;
|
|
|
|
|
+ }>;
|
|
|
|
|
+ };
|
|
|
|
|
+ cintasSummaries: CintasSummary[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface ImportDetailDialogProps {
|
|
|
|
|
+ open: boolean;
|
|
|
|
|
+ onOpenChange: (open: boolean) => void;
|
|
|
|
|
+ importId: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+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 { toast } = useToast();
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (open && importId) {
|
|
|
|
|
+ loadImportDetail();
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [open, importId]);
|
|
|
|
|
+
|
|
|
|
|
+ async function loadImportDetail() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = await getImportById(importId);
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ setImportDetail(result.data);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ toast({
|
|
|
|
|
+ title: 'Error',
|
|
|
|
|
+ description: result.error || 'Failed to load import details',
|
|
|
|
|
+ variant: 'destructive',
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ toast({
|
|
|
|
|
+ title: 'Error',
|
|
|
|
|
+ description: 'Failed to load import details',
|
|
|
|
|
+ variant: 'destructive',
|
|
|
|
|
+ });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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 (error) {
|
|
|
|
|
+ toast({
|
|
|
|
|
+ title: 'Error',
|
|
|
|
|
+ description: 'Failed to calculate summaries',
|
|
|
|
|
+ variant: 'destructive',
|
|
|
|
|
+ });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setCalculating(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (loading) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
+ <DialogContent className="max-w-4xl max-h-[80vh]">
|
|
|
|
|
+ <div className="flex justify-center py-8">
|
|
|
|
|
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!importDetail) return null;
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
+ <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
|
|
|
|
|
+ <DialogHeader>
|
|
|
|
|
+ <DialogTitle>Import Details</DialogTitle>
|
|
|
|
|
+ <DialogDescription>
|
|
|
|
|
+ View detailed information about this import
|
|
|
|
|
+ </DialogDescription>
|
|
|
|
|
+ </DialogHeader>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="space-y-6">
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>Import Information</CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span className="font-medium">Name:</span>
|
|
|
|
|
+ <span>{importDetail.name}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span className="font-medium">Layout:</span>
|
|
|
|
|
+ <span>{importDetail.layout.name}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span className="font-medium">Import Date:</span>
|
|
|
|
|
+ <span>{format(new Date(importDetail.importDate), 'PPpp')}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>Layout Configuration</CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <div className="space-y-4">
|
|
|
|
|
+ {importDetail.layout.sections.map((section) => (
|
|
|
|
|
+ <div key={section.id} className="border rounded-lg p-4">
|
|
|
|
|
+ <h4 className="font-medium mb-2">{section.name}</h4>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground mb-2">
|
|
|
|
|
+ Table: {section.tableName}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <div className="grid gap-1">
|
|
|
|
|
+ {section.fields.map((field) => (
|
|
|
|
|
+ <div key={field.importTableColumnName} className="text-sm">
|
|
|
|
|
+ <span className="font-medium">{field.name}:</span>
|
|
|
|
|
+ <span className="ml-2 text-muted-foreground">
|
|
|
|
|
+ {field.importTableColumnName}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
|
|
+ <CardTitle>Cintas Summaries</CardTitle>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={handleCalculateSummaries}
|
|
|
|
|
+ disabled={calculating}
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ {calculating ? 'Calculating...' : 'Calculate Summaries'}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </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">
|
|
|
|
|
+ <Button onClick={() => onOpenChange(false)}>Close</Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|