diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
| commit | 9ecdfb23fe3df6a5df86782385002c562dfc1198 (patch) | |
| tree | 4188cb7e6bf2c862d9c86a59d79946bd41217227 /lib/vendor-document-list/plant/upload/toolbar-actions.tsx | |
| parent | b67861fbb424c7ad47ad1538f75e2945bd8890c5 (diff) | |
(대표님) rfq 히스토리, swp 등
Diffstat (limited to 'lib/vendor-document-list/plant/upload/toolbar-actions.tsx')
| -rw-r--r-- | lib/vendor-document-list/plant/upload/toolbar-actions.tsx | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/lib/vendor-document-list/plant/upload/toolbar-actions.tsx b/lib/vendor-document-list/plant/upload/toolbar-actions.tsx new file mode 100644 index 00000000..072fd72d --- /dev/null +++ b/lib/vendor-document-list/plant/upload/toolbar-actions.tsx @@ -0,0 +1,242 @@ +"use client" + +import * as React from "react" +import { type Table } from "@tanstack/react-table" +import { Download, RefreshCw, Upload, Send, AlertCircle } from "lucide-react" +import { toast } from "sonner" + +import { exportTableToExcel } from "@/lib/export" +import { Button } from "@/components/ui/button" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { StageSubmissionView } from "@/db/schema" +import { DataTableRowAction } from "@/types/table" +import { MultiUploadDialog } from "./components/multi-upload-dialog" +import { useRouter, useSearchParams } from "next/navigation" + +interface StageSubmissionToolbarActionsProps { + table: Table<StageSubmissionView> + rowAction: DataTableRowAction<StageSubmissionView> | null + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<StageSubmissionView> | null>> +} + +export function StageSubmissionToolbarActions({ + table, + rowAction, + setRowAction +}: StageSubmissionToolbarActionsProps) { + const selectedRows = table.getFilteredSelectedRowModel().rows + const router = useRouter() + const searchParams = useSearchParams() + + const projectId = searchParams.get('projectId') + + + const [isSyncing, setIsSyncing] = React.useState(false) + const [showSyncDialog, setShowSyncDialog] = React.useState(false) + const [syncTargets, setSyncTargets] = React.useState<typeof selectedRows>([]) + + const handleUploadComplete = () => { + // Refresh table + router.refresh() + } + + const handleSyncClick = () => { + const rowsRequiringSync = selectedRows.filter( + row => row.original.requiresSync && row.original.latestSubmissionId + ) + setSyncTargets(rowsRequiringSync) + setShowSyncDialog(true) + } + + const handleSyncConfirm = async () => { + setShowSyncDialog(false) + setIsSyncing(true) + + try { + // Extract submission IDs + const submissionIds = syncTargets + .map(row => row.original.latestSubmissionId) + .filter((id): id is number => id !== null) + + if (submissionIds.length === 0) { + toast.error("No submissions to sync.") + return + } + + // API call + const response = await fetch('/api/stage-submissions/sync', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ submissionIds }), + }) + + const result = await response.json() + + if (result.success) { + toast.success(result.message) + + // Display detailed information for successful items + if (result.results?.details) { + const successCount = result.results.details.filter((d: any) => d.success).length + const failedCount = result.results.details.filter((d: any) => !d.success).length + + if (failedCount > 0) { + toast.warning(`${successCount} succeeded, ${failedCount} failed`) + } + } + + // Refresh table + router.refresh() + table.toggleAllPageRowsSelected(false) // Deselect all + } else { + toast.error(result.error || "Sync failed") + } + } catch (error) { + console.error("Sync error:", error) + toast.error("An error occurred during synchronization.") + } finally { + setIsSyncing(false) + } + } + + return ( + <> + <div className="flex items-center gap-2"> + {projectId && ( + <MultiUploadDialog + projectId={parseInt(projectId)} + // projectCode={projectCode} + onUploadComplete={handleUploadComplete} + /> + )} + {selectedRows.length > 0 && ( + <> + {/* Bulk Upload for selected rows that require submission */} + {selectedRows.some(row => row.original.requiresSubmission) && ( + <Button + variant="outline" + size="sm" + onClick={() => { + // Filter selected rows that require submission + const rowsRequiringSubmission = selectedRows.filter( + row => row.original.requiresSubmission + ) + // Open bulk upload dialog + console.log("Bulk upload for:", rowsRequiringSubmission) + }} + className="gap-2" + > + <Upload className="size-4" /> + <span>Upload ({selectedRows.filter(r => r.original.requiresSubmission).length})</span> + </Button> + )} + + {/* Bulk Sync for selected rows that need syncing */} + {selectedRows.some(row => row.original.requiresSync && row.original.latestSubmissionId) && ( + <Button + variant="outline" + size="sm" + onClick={handleSyncClick} + disabled={isSyncing} + className="gap-2" + > + {isSyncing ? ( + <> + <RefreshCw className="size-4 animate-spin" /> + <span>Syncing...</span> + </> + ) : ( + <> + <RefreshCw className="size-4" /> + <span>Sync ({selectedRows.filter(r => r.original.requiresSync && r.original.latestSubmissionId).length})</span> + </> + )} + </Button> + )} + </> + )} + + {/* Export Button */} + <Button + variant="outline" + size="sm" + onClick={() => + exportTableToExcel(table, { + filename: `stage-submissions-${new Date().toISOString().split('T')[0]}`, + excludeColumns: ["select", "actions"], + }) + } + className="gap-2" + > + <Download className="size-4" /> + <span className="hidden sm:inline">Export</span> + </Button> + </div> + + {/* Sync Confirmation Dialog */} + <AlertDialog open={showSyncDialog} onOpenChange={setShowSyncDialog}> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle className="flex items-center gap-2"> + <RefreshCw className="size-5" /> + Sync to Buyer System + </AlertDialogTitle> + <AlertDialogDescription className="space-y-3"> + <div> + Are you sure you want to sync {syncTargets.length} selected submission(s) to the buyer system? + </div> + <div className="space-y-2 rounded-lg bg-muted p-3"> + <div className="text-sm font-medium">Items to sync:</div> + <ul className="text-sm space-y-1"> + {syncTargets.slice(0, 3).map((row, idx) => ( + <li key={idx} className="flex items-center gap-2"> + <span className="text-muted-foreground">•</span> + <span>{row.original.docNumber}</span> + <span className="text-muted-foreground">-</span> + <span>{row.original.stageName}</span> + <span className="text-muted-foreground"> + (Rev.{row.original.latestRevisionNumber}) + </span> + </li> + ))} + {syncTargets.length > 3 && ( + <li className="text-muted-foreground"> + ... and {syncTargets.length - 3} more + </li> + )} + </ul> + </div> + <div className="flex items-start gap-2 text-sm text-amber-600"> + <AlertCircle className="size-4 mt-0.5 shrink-0" /> + <div> + Synchronized files will be sent to the SHI Buyer System and + cannot be recalled after transmission. + </div> + </div> + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel>Cancel</AlertDialogCancel> + <AlertDialogAction + onClick={handleSyncConfirm} + // className="bg-samsung hover:bg-samsung/90" + > + Start Sync + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + </> + ) +}
\ No newline at end of file |
