"use client"; import * as React from "react"; import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Checkbox } from "@/components/ui/checkbox"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Loader2, RefreshCw, CheckCircle2, XCircle, FileText, FolderInput } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "@/i18n/client"; import { fetchProjectPendingSyncItems, syncDolceItem, PendingSyncItemDetail } from "@/lib/dolce-v2/actions"; import { format } from "date-fns"; interface SyncItemsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; projectNo: string; userId: string; vendorCode: string; onSyncComplete: () => void; lng: string; } // UI 표시용 Row 타입 (파일 단위로 확장) interface DisplayRow { rowId: string; // 유니크 키 (syncId + fileIndex) syncId: string; // Sync Item ID (체크박스 그룹핑용) type: string; createdAt: Date; userName?: string; status: "pending" | "syncing" | "success" | "error"; errorMessage?: string; // 표시 정보 drawingNo: string; drawingName: string; discipline: string; revision: string; registerKind: string; fileName: string; fileSize: string; } export function SyncItemsDialog({ open, onOpenChange, projectNo, userId, vendorCode, onSyncComplete, lng, }: SyncItemsDialogProps) { const { t } = useTranslation(lng, "dolce"); const [isLoading, setIsLoading] = useState(false); const [isSyncing, setIsSyncing] = useState(false); const [myRows, setMyRows] = useState([]); const [otherRows, setOtherRows] = useState([]); // 선택된 Sync Item ID들 (파일 단위가 아니라 Sync Item 단위로 선택) const [selectedSyncIds, setSelectedSyncIds] = useState>(new Set()); // 데이터 변환 헬퍼 const convertToDisplayRows = (items: PendingSyncItemDetail[], defaultStatus: DisplayRow["status"] = "pending"): DisplayRow[] => { return items.flatMap((item) => { // 파일이 없으면 메타데이터만 있는 1개 행 생성 if (!item.files || item.files.length === 0) { return [{ rowId: `${item.id}_meta`, syncId: item.id, type: item.type, createdAt: item.createdAt, userName: item.userName, status: defaultStatus, drawingNo: item.drawingNo, drawingName: item.drawingName, discipline: item.discipline, revision: item.revision, registerKind: item.registerKind, fileName: "(Metadata Only)", fileSize: "-", }]; } // 파일이 있으면 파일별로 행 생성 return item.files.map((file, idx) => ({ rowId: `${item.id}_file_${idx}`, syncId: item.id, type: item.type, createdAt: item.createdAt, userName: item.userName, status: defaultStatus, drawingNo: item.drawingNo, drawingName: item.drawingName, discipline: item.discipline, revision: item.revision, registerKind: item.registerKind, fileName: file.name, fileSize: (file.size / 1024 / 1024).toFixed(2) + " MB", })); }); }; // 데이터 로드 const loadData = async () => { if (!open) return; setIsLoading(true); try { const { myItems, otherItems } = await fetchProjectPendingSyncItems({ projectNo, currentUserId: userId, currentVendorCode: vendorCode, }); setMyRows(convertToDisplayRows(myItems)); setOtherRows(convertToDisplayRows(otherItems)); // 기본적으로 내 아이템 모두 선택 setSelectedSyncIds(new Set(myItems.map(item => item.id))); } catch (error) { console.error("Failed to load sync items:", error); toast.error("Failed to load synchronization items."); } finally { setIsLoading(false); } }; useEffect(() => { if (open) { loadData(); } }, [open, projectNo, userId, vendorCode]); // 체크박스 핸들러 (Sync Item 단위로 토글) const toggleSelect = (syncId: string) => { const newSelected = new Set(selectedSyncIds); if (newSelected.has(syncId)) { newSelected.delete(syncId); } else { newSelected.add(syncId); } setSelectedSyncIds(newSelected); }; const toggleSelectAll = () => { // 현재 화면에 표시된 myRows에 포함된 모든 unique syncId 수집 const allSyncIds = new Set(myRows.map(r => r.syncId)); if (selectedSyncIds.size === allSyncIds.size) { setSelectedSyncIds(new Set()); } else { setSelectedSyncIds(allSyncIds); } }; // 동기화 실행 const handleSync = async () => { if (selectedSyncIds.size === 0) return; setIsSyncing(true); // 선택된 ID 목록 const idsToSync = Array.from(selectedSyncIds); let successCount = 0; let failCount = 0; for (const id of idsToSync) { // 상태: 동기화 중 (해당 syncId를 가진 모든 Row 업데이트) setMyRows(prev => prev.map(r => r.syncId === id ? { ...r, status: "syncing" } : r)); try { await syncDolceItem(id); // 상태: 성공 setMyRows(prev => prev.map(r => r.syncId === id ? { ...r, status: "success" } : r)); successCount++; } catch (error) { console.error(`Sync failed for ${id}:`, error); // 상태: 실패 setMyRows(prev => prev.map(r => r.syncId === id ? { ...r, status: "error", errorMessage: error instanceof Error ? error.message : "Unknown error" } : r)); failCount++; } } setIsSyncing(false); if (successCount > 0) { toast.success(`Successfully synced ${successCount} items.`); onSyncComplete(); // 부모에게 알림 (카운트 갱신 등) } if (failCount > 0) { toast.error(`Failed to sync ${failCount} items. Check the list for details.`); } }; return ( !isSyncing && onOpenChange(v)}> Server Synchronization Upload locally saved items to the external server.
{/* 내 아이템 (동기화 대상) */}

My Pending Items ({new Set(myRows.map(r => r.syncId)).size} items, {myRows.length} files)

0 && selectedSyncIds.size === new Set(myRows.map(r => r.syncId)).size} onCheckedChange={toggleSelectAll} disabled={isSyncing || myRows.length === 0} /> Drawing No Drawing Name Discipline Rev Kind File Name Size Date Status {isLoading ? ( ) : myRows.length === 0 ? ( No pending items found. ) : ( myRows.map((row) => ( toggleSelect(row.syncId)} disabled={isSyncing || row.status === "success"} /> {row.drawingNo || "-"} {row.drawingName || "-"} {row.discipline || "-"} {row.revision || "-"} {row.registerKind || "-"} {row.fileName} {row.fileSize} {format(new Date(row.createdAt), "yyyy-MM-dd HH:mm")} {row.status === "pending" && Pending} {row.status === "syncing" && } {row.status === "success" && } {row.status === "error" && } {row.errorMessage && row.status === "error" && ( {row.errorMessage} )} )) )}
{/* 다른 사용자 아이템 (참고용) */} {otherRows.length > 0 && (

Other Users' Pending Items (Same Vendor) - {otherRows.length} files

User Drawing No File Name Size Date {otherRows.map((row) => ( {row.userName} {row.drawingNo} {row.fileName} {row.fileSize} {format(new Date(row.createdAt), "yyyy-MM-dd HH:mm")} ))}
)}
); }