diff options
Diffstat (limited to 'lib/dolce/dialogs')
| -rw-r--r-- | lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx | 77 | ||||
| -rw-r--r-- | lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx | 690 | ||||
| -rw-r--r-- | lib/dolce/dialogs/b4-bulk-upload-dialog.tsx | 7 |
3 files changed, 736 insertions, 38 deletions
diff --git a/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx b/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx index 87819693..673d48d6 100644 --- a/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx +++ b/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx @@ -164,18 +164,21 @@ export function AddAndModifyDetailDrawingDialog({ toast.error(t("addDetailDialog.selectRegisterKindError")); return; } - if (!revision.trim()) { - toast.error(t("addDetailDialog.selectRevisionError")); - setRevisionError(t("addDetailDialog.revisionRequired")); - return; - } - // Revision 형식 검증 - const revisionValidationError = validateRevision(revision); - if (revisionValidationError) { - toast.error(revisionValidationError); - setRevisionError(revisionValidationError); - return; + if (drawingUsage !== "CMT") { + if (!revision.trim()) { + toast.error(t("addDetailDialog.selectRevisionError")); + setRevisionError(t("addDetailDialog.revisionRequired")); + return; + } + + // Revision 형식 검증 + const revisionValidationError = validateRevision(revision); + if (revisionValidationError) { + toast.error(revisionValidationError); + setRevisionError(revisionValidationError); + return; + } } // Add 모드일 때만 파일 필수 @@ -219,7 +222,7 @@ export function AddAndModifyDetailDrawingDialog({ RegisterGroupId: drawing.RegisterGroupId, RegisterSerialNo: 0, // 자동 증가 RegisterKind: registerKind, - DrawingRevNo: revision, + DrawingRevNo: drawingUsage === "CMT" ? null : revision, Category: "TS", // To SHI (벤더가 SHI에게 제출) Receiver: null, Manager: "", @@ -293,7 +296,7 @@ export function AddAndModifyDetailDrawingDialog({ RegisterGroupId: detailDrawing.RegisterGroupId, RegisterSerialNo: detailDrawing.RegisterSerialNo, RegisterKind: registerKind, - DrawingRevNo: revision, + DrawingRevNo: drawingUsage === "CMT" ? null : revision, Category: detailDrawing.Category, Receiver: detailDrawing.Receiver, Manager: detailDrawing.Manager, @@ -345,12 +348,10 @@ export function AddAndModifyDetailDrawingDialog({ const isFormValid = mode === "add" ? drawingUsage.trim() !== "" && registerKind.trim() !== "" && - revision.trim() !== "" && - !revisionError && + (drawingUsage === "CMT" || (revision.trim() !== "" && !revisionError)) && files.length > 0 : registerKind.trim() !== "" && - revision.trim() !== "" && - !revisionError; + (drawingUsage === "CMT" || (revision.trim() !== "" && !revisionError)); return ( <Dialog open={open} onOpenChange={onOpenChange}> @@ -429,26 +430,28 @@ export function AddAndModifyDetailDrawingDialog({ </div> {/* Revision 입력 */} - <div className="space-y-2"> - <Label>{t("addDetailDialog.revisionLabel")}</Label> - <Input - value={revision} - onChange={(e) => handleRevisionChange(e.target.value)} - placeholder={t("addDetailDialog.revisionPlaceholder")} - disabled={!registerKind} - className={revisionError ? "border-red-500 focus-visible:ring-red-500" : ""} - /> - {revisionError && ( - <p className="text-sm text-red-500 flex items-center gap-1"> - {revisionError} - </p> - )} - {!revisionError && revision && ( - <p className="text-sm text-green-600 flex items-center gap-1"> - {t("addDetailDialog.revisionValid")} - </p> - )} - </div> + {drawingUsage !== "CMT" && ( + <div className="space-y-2"> + <Label>{t("addDetailDialog.revisionLabel")}</Label> + <Input + value={revision} + onChange={(e) => handleRevisionChange(e.target.value)} + placeholder={t("addDetailDialog.revisionPlaceholder")} + disabled={!registerKind} + className={revisionError ? "border-red-500 focus-visible:ring-red-500" : ""} + /> + {revisionError && ( + <p className="text-sm text-red-500 flex items-center gap-1"> + {revisionError} + </p> + )} + {!revisionError && revision && ( + <p className="text-sm text-green-600 flex items-center gap-1"> + {t("addDetailDialog.revisionValid")} + </p> + )} + </div> + )} {/* Comment 입력 */} <div className="space-y-2"> diff --git a/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx b/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx new file mode 100644 index 00000000..ea955420 --- /dev/null +++ b/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx @@ -0,0 +1,690 @@ +"use client"; + +import * as React from "react"; +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { FolderOpen, Loader2, ChevronRight, ChevronLeft, CheckCircle2 } from "lucide-react"; +import { toast } from "sonner"; +import { Progress } from "@/components/ui/progress"; +import { useTranslation } from "@/i18n/client"; +import { + validateB4FileName, + B4UploadValidationDialog, + type FileValidationResult, +} from "./b4-upload-validation-dialog"; +import { + checkB4MappingStatus, + saveB4MappingBatch, + type MappingCheckResult, + type B4BulkUploadResult, + type B4MappingSaveItem, +} from "../actions"; +import { uploadFilesWithProgress } from "../utils/upload-with-progress"; +import { FileUploadProgressList } from "../components/file-upload-progress-list"; +import type { FileUploadProgress } from "../hooks/use-file-upload-with-progress"; +import { v4 as uuidv4 } from "uuid"; + +interface B4BulkUploadDialogV3Props { + open: boolean; + onOpenChange: (open: boolean) => void; + projectNo: string; + userId: string; + userName: string; + userEmail: string; + vendorCode: string; + onUploadComplete?: () => void; + lng: string; +} + +type UploadStep = "files" | "validation" | "uploading" | "complete"; + +export function B4BulkUploadDialogV3({ + open, + onOpenChange, + projectNo, + userId, + userName, + userEmail, + vendorCode, + onUploadComplete, + lng, +}: B4BulkUploadDialogV3Props) { + const { t } = useTranslation(lng, "dolce"); + const [currentStep, setCurrentStep] = useState<UploadStep>("files"); + const [selectedFiles, setSelectedFiles] = useState<File[]>([]); + const [isUploading, setIsUploading] = useState(false); + const [validationResults, setValidationResults] = useState<FileValidationResult[]>([]); + const [mappingResultsMap, setMappingResultsMap] = useState<Map<string, MappingCheckResult>>(new Map()); + const [showValidationDialog, setShowValidationDialog] = useState(false); + const [isDragging, setIsDragging] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); + const [uploadResult, setUploadResult] = useState<B4BulkUploadResult | null>(null); + const [fileProgresses, setFileProgresses] = useState<FileUploadProgress[]>([]); + + // Reset on close + React.useEffect(() => { + if (!open) { + setCurrentStep("files"); + setSelectedFiles([]); + setValidationResults([]); + setMappingResultsMap(new Map()); + setShowValidationDialog(false); + setIsDragging(false); + setUploadProgress(0); + setUploadResult(null); + setFileProgresses([]); + } + }, [open]); + + // File Selection Handler + const handleFilesChange = (files: File[]) => { + if (files.length === 0) return; + + const existingNames = new Set(selectedFiles.map((f) => f.name)); + const newFiles = files.filter((f) => !existingNames.has(f.name)); + + if (newFiles.length === 0) { + toast.error(t("bulkUpload.duplicateFileError")); + return; + } + + setSelectedFiles((prev) => [...prev, ...newFiles]); + toast.success(t("bulkUpload.filesSelectedSuccess", { count: newFiles.length })); + }; + + // Drag & Drop Handlers + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragging(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.currentTarget === e.target) { + setIsDragging(false); + } + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = "copy"; + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragging(false); + + const droppedFiles = Array.from(e.dataTransfer.files); + if (droppedFiles.length > 0) { + handleFilesChange(droppedFiles); + } + }; + + const handleRemoveFile = (index: number) => { + setSelectedFiles((prev) => prev.filter((_, i) => i !== index)); + }; + + // Step 1 Next: Validation + const handleFilesNext = () => { + if (selectedFiles.length === 0) { + toast.error(t("bulkUpload.selectFilesError")); + return; + } + setCurrentStep("validation"); + handleValidate(); + }; + + // Validation Process (V3) + const handleValidate = async () => { + try { + console.log("[V3 Dialog] Validation started"); + + // 1. Parse Filenames + const parseResults: FileValidationResult[] = selectedFiles.map((file) => { + const validation = validateB4FileName(file.name); + return { + file, + valid: validation.valid, + parsed: validation.parsed, + error: validation.error, + }; + }); + + const parsedFiles = parseResults.filter((r) => r.valid && r.parsed); + + // If no files parsed correctly, show dialog immediately with errors + if (parsedFiles.length === 0) { + setValidationResults(parseResults); + setShowValidationDialog(true); + return; + } + + // 2. Call MatchBatchFileDwg to check mapping status + const mappingCheckItems = parsedFiles.map((r) => ({ + DrawingNo: r.parsed!.drawingNo, + RevNo: r.parsed!.revNo, + FileNm: r.file.name, + })); + + console.log(`[V3 Dialog] Checking mapping for ${mappingCheckItems.length} files`); + + const mappingResults = await checkB4MappingStatus( + projectNo, + mappingCheckItems + ); + + // Store mapping results for later use (upload/save) + const newMappingResultsMap = new Map<string, MappingCheckResult>(); + mappingResults.forEach((result) => { + newMappingResultsMap.set(result.FileNm, result); + }); + setMappingResultsMap(newMappingResultsMap); + + // 3. Merge results + const finalResults: FileValidationResult[] = parseResults.map((parseResult) => { + if (!parseResult.valid || !parseResult.parsed) { + return parseResult; + } + + const mappingResult = newMappingResultsMap.get(parseResult.file.name); + + if (!mappingResult) { + return { + ...parseResult, + mappingStatus: "not_found" as const, + error: t("validation.notFound"), + }; + } + + // According to prompt: "API 응답에서 매핑되지 않은 경우는, 파일명으로부터 파싱된 도면이 없는 경우임." + // Also "MappingYN 의 값이 현재 매핑이 되어있는지를 나타냄. Y인 건들은 저장 가능" + if (mappingResult.MappingYN !== "Y") { + return { + ...parseResult, + mappingStatus: "not_found" as const, + error: t("validation.notRegistered"), // Or specific message for MappingYN=N + }; + } + + // Check DrawingMoveGbn = "도면입수" (implied by requirements to use MatchBatchFileDwg with 도면입수) + if (mappingResult.DrawingMoveGbn !== "도면입수") { + return { + ...parseResult, + mappingStatus: "not_found" as const, + error: t("validation.notGttDeliverables"), + }; + } + + return { + ...parseResult, + mappingStatus: "available" as const, + drawingName: mappingResult.DrawingName || undefined, + registerGroupId: mappingResult.RegisterGroupId, + }; + }); + + console.log("[V3 Dialog] Validation complete"); + setValidationResults(finalResults); + setShowValidationDialog(true); + } catch (error) { + console.error("[V3 Dialog] Validation failed:", error); + toast.error( + error instanceof Error ? error.message : t("bulkUpload.validationError") + ); + // Go back to files step if validation crashes completely + setCurrentStep("files"); + } + }; + + // Confirm Upload & Save (V3) + const handleConfirmUpload = async (validFiles: FileValidationResult[]) => { + setIsUploading(true); + setCurrentStep("uploading"); + setShowValidationDialog(false); + + try { + console.log(`[V3 Dialog] Upload started: ${validFiles.length} files`); + + // 0. Initialize progress + const initialProgresses: FileUploadProgress[] = validFiles.map((fileResult) => ({ + file: fileResult.file, + progress: 0, + status: "pending" as const, + })); + setFileProgresses(initialProgresses); + + // 1. Group by DrawingNo + RevNo (to share UploadId if needed) + const uploadGroups = new Map< + string, + Array<{ + file: File; + fileIndex: number; // Index in validFiles + mappingData: MappingCheckResult; + }> + >(); + + // Pre-process groups + validFiles.forEach((fileResult, index) => { + const mappingData = mappingResultsMap.get(fileResult.file.name); + if (!mappingData) return; // Should not happen for valid files + + const groupKey = `${mappingData.DrawingNo}_${mappingData.RevNo}`; + if (!uploadGroups.has(groupKey)) { + uploadGroups.set(groupKey, []); + } + uploadGroups.get(groupKey)!.push({ + file: fileResult.file, + fileIndex: index, + mappingData + }); + }); + + let successCount = 0; + let failCount = 0; + let completedGroups = 0; + const results: B4BulkUploadResult["results"] = []; + + // 2. Process each group + for (const [groupKey, groupItems] of uploadGroups.entries()) { + // Reuse UploadId from the first item's mapping data if available, else generate new + const firstItemMapping = groupItems[0].mappingData; + // Reuse existing UploadId if present in API response, otherwise generate new one + // The prompt says: "UploadId는 있으면 재활용하고, 없으면 UUID로 만들어줌" + const uploadId = firstItemMapping.UploadId || uuidv4(); + + console.log(`[V3 Dialog] Processing group ${groupKey}, UploadId: ${uploadId}`); + + try { + // Update status to uploading + setFileProgresses((prev) => + prev.map((fp, idx) => + groupItems.some(item => item.fileIndex === idx) + ? { ...fp, status: "uploading" as const } + : fp + ) + ); + + // A. Upload Files (Physical Upload) + const uploadResult = await uploadFilesWithProgress({ + uploadId: uploadId, + userId: userId, + files: groupItems.map(item => item.file), + callbacks: { + onProgress: (fileIndexInGroup, progress) => { + const globalFileIndex = groupItems[fileIndexInGroup].fileIndex; + setFileProgresses((prev) => + prev.map((fp, idx) => + idx === globalFileIndex + ? { ...fp, progress, status: "uploading" as const } + : fp + ) + ); + + // Overall progress approximation + const groupProgress = (completedGroups / uploadGroups.size) * 100; + const currentGroupProgress = (progress / 100) * (100 / uploadGroups.size); + setUploadProgress(Math.round(groupProgress + currentGroupProgress)); + }, + onFileComplete: (fileIndexInGroup) => { + const globalFileIndex = groupItems[fileIndexInGroup].fileIndex; + setFileProgresses((prev) => + prev.map((fp, idx) => + idx === globalFileIndex + ? { ...fp, progress: 100, status: "completed" as const } + : fp + ) + ); + }, + onFileError: (fileIndexInGroup, error) => { + const globalFileIndex = groupItems[fileIndexInGroup].fileIndex; + console.error(`[V3 Dialog] File upload error:`, error); + setFileProgresses((prev) => + prev.map((fp, idx) => + idx === globalFileIndex + ? { ...fp, status: "error" as const, error } + : fp + ) + ); + } + } + }); + + if (!uploadResult.success) { + throw new Error(uploadResult.error || "File upload failed"); + } + + // B. Save Metadata (MatchBatchFileDwgEdit) + // Construct payload from mappingData + generated UploadId + hardcoded values as per prompt + const mappingSaveLists: B4MappingSaveItem[] = groupItems.map(item => { + const m = item.mappingData; + return { + CGbn: m.CGbn, + Category: "TS", // Hardcoded as per prompt + CheckBox: "0", + DGbn: m.DGbn, + DegreeGbn: m.DegreeGbn, + DeptGbn: m.DeptGbn, + Discipline: m.Discipline, + DrawingKind: "B4", + DrawingMoveGbn: "도면입수", + DrawingName: m.DrawingName, + DrawingNo: m.DrawingNo, + DrawingUsage: "입수용", + FileNm: item.file.name, + JGbn: m.JGbn, + Manager: m.Manager || "970043", // Fallback/Default + MappingYN: "Y", + NewOrNot: "N", + ProjectNo: projectNo, + RegisterGroup: 0, + RegisterGroupId: m.RegisterGroupId, + RegisterKindCode: m.RegisterKindCode, + RegisterSerialNo: m.RegisterSerialNo, + RevNo: m.RevNo, + SGbn: m.SGbn, + UploadId: uploadId // Used for all files in this group + }; + }); + + await saveB4MappingBatch(mappingSaveLists, { + userId, + userName, + vendorCode, + email: userEmail, + }); + + console.log(`[V3 Dialog] Group ${groupKey} complete`); + successCount += groupItems.length; + + groupItems.forEach(item => { + results.push({ + drawingNo: item.mappingData.DrawingNo, + revNo: item.mappingData.RevNo || "", + fileName: item.file.name, + success: true + }); + }); + + } catch (error) { + console.error(`[V3 Dialog] Group ${groupKey} failed:`, error); + failCount += groupItems.length; + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + + groupItems.forEach(item => { + results.push({ + drawingNo: item.mappingData.DrawingNo, + revNo: item.mappingData.RevNo || "", + fileName: item.file.name, + success: false, + error: errorMessage + }); + }); + } + + completedGroups++; + setUploadProgress(Math.round((completedGroups / uploadGroups.size) * 100)); + } + + // Finalize + const result: B4BulkUploadResult = { + success: successCount > 0, + successCount, + failCount, + results, + }; + + setUploadResult(result); + setCurrentStep("complete"); + + if (result.success) { + toast.success( + t("bulkUpload.uploadSuccessToast", { + successCount: result.successCount, + total: validFiles.length, + }) + ); + } else { + toast.error(result.error || t("bulkUpload.uploadError")); + } + + } catch (error) { + console.error("[V3 Dialog] Upload process failed:", error); + toast.error( + error instanceof Error ? error.message : t("bulkUpload.uploadError") + ); + setCurrentStep("files"); + } finally { + setIsUploading(false); + } + }; + + return ( + <> + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="max-w-2xl"> + <DialogHeader> + <DialogTitle>{t("bulkUpload.title")} (V3)</DialogTitle> + <DialogDescription> + {currentStep === "files" && t("bulkUpload.stepFiles")} + {currentStep === "validation" && t("bulkUpload.stepValidation")} + </DialogDescription> + </DialogHeader> + + <div className="space-y-4"> + {/* Step 1: Files */} + {currentStep === "files" && ( + <> + <div + className={`border-2 border-dashed rounded-lg p-8 transition-all duration-200 ${ + isDragging + ? "border-primary bg-primary/5 scale-[1.02]" + : "border-muted-foreground/30 hover:border-muted-foreground/50" + }`} + onDragEnter={handleDragEnter} + onDragLeave={handleDragLeave} + onDragOver={handleDragOver} + onDrop={handleDrop} + > + <input + type="file" + multiple + accept=".pdf,.doc,.docx,.xls,.xlsx,.dwg,.dxf,.zip" + onChange={(e) => handleFilesChange(Array.from(e.target.files || []))} + className="hidden" + id="b4-file-upload-v3" + /> + <label + htmlFor="b4-file-upload-v3" + className="flex flex-col items-center justify-center cursor-pointer" + > + <FolderOpen + className={`h-12 w-12 mb-3 transition-colors ${ + isDragging ? "text-primary" : "text-muted-foreground" + }`} + /> + <p + className={`text-sm transition-colors ${ + isDragging + ? "text-primary font-medium" + : "text-muted-foreground" + }`} + > + {isDragging + ? t("bulkUpload.fileDropHere") + : t("bulkUpload.fileSelectArea")} + </p> + <p className="text-xs text-muted-foreground mt-1"> + {t("bulkUpload.fileTypes")} + </p> + </label> + </div> + + {selectedFiles.length > 0 && ( + <div className="border rounded-lg p-4"> + <div className="flex items-center justify-between mb-3"> + <h4 className="text-sm font-medium"> + {t("bulkUpload.selectedFiles", { count: selectedFiles.length })} + </h4> + <Button + variant="ghost" + size="sm" + onClick={() => setSelectedFiles([])} + > + {t("bulkUpload.removeAll")} + </Button> + </div> + <div className="max-h-60 overflow-y-auto space-y-2"> + {selectedFiles.map((file, index) => ( + <div + key={index} + className="flex items-center justify-between p-2 rounded bg-muted/50" + > + <div className="flex-1 min-w-0"> + <p className="text-sm truncate">{file.name}</p> + <p className="text-xs text-muted-foreground"> + {(file.size / 1024 / 1024).toFixed(2)} MB + </p> + </div> + <Button + variant="ghost" + size="sm" + onClick={() => handleRemoveFile(index)} + > + {t("bulkUpload.removeFile")} + </Button> + </div> + ))} + </div> + </div> + )} + </> + )} + + {/* Loading Indicator */} + {currentStep === "validation" && ( + <div className="flex flex-col items-center justify-center py-12"> + <Loader2 className="h-12 w-12 animate-spin text-primary mb-4" /> + <p className="text-sm text-muted-foreground"> + {t("bulkUpload.validating")} + </p> + </div> + )} + + {/* Uploading Progress */} + {currentStep === "uploading" && ( + <div className="space-y-6 py-4"> + <div className="flex flex-col items-center"> + <Loader2 className="h-12 w-12 animate-spin text-primary mb-4" /> + <h3 className="text-lg font-semibold mb-2">{t("bulkUpload.uploading")}</h3> + <p className="text-sm text-muted-foreground"> + {t("bulkUpload.uploadingWait")} + </p> + </div> + + <div className="space-y-2"> + <div className="flex justify-between text-sm"> + <span>{t("bulkUpload.uploadProgress")}</span> + <span>{uploadProgress}%</span> + </div> + <Progress value={uploadProgress} className="h-2" /> + </div> + + {fileProgresses.length > 0 && ( + <div className="border rounded-lg p-4 max-h-96 overflow-y-auto"> + <FileUploadProgressList fileProgresses={fileProgresses} /> + </div> + )} + </div> + )} + + {/* Completion Screen */} + {currentStep === "complete" && uploadResult && ( + <div className="space-y-6 py-8"> + <div className="flex flex-col items-center"> + <CheckCircle2 className="h-16 w-16 text-green-500 mb-4" /> + <h3 className="text-lg font-semibold mb-2">{t("bulkUpload.uploadComplete")}</h3> + <p className="text-sm text-muted-foreground"> + {t("bulkUpload.uploadSuccessMessage", { count: uploadResult.successCount })} + </p> + </div> + + {uploadResult.failCount && uploadResult.failCount > 0 && ( + <div className="bg-yellow-50 dark:bg-yellow-950/30 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4"> + <p className="text-sm text-yellow-800 dark:text-yellow-200"> + {t("bulkUpload.uploadFailMessage", { count: uploadResult.failCount })} + </p> + </div> + )} + + <div className="flex justify-center"> + <Button + onClick={() => { + onOpenChange(false); + onUploadComplete?.(); + }} + > + {t("bulkUpload.confirmButton")} + </Button> + </div> + </div> + )} + </div> + + {/* Footer */} + {currentStep !== "uploading" && currentStep !== "complete" && currentStep !== "validation" && ( + <DialogFooter> + {currentStep === "files" && ( + <> + <Button + variant="outline" + onClick={() => onOpenChange(false)} + > + {t("bulkUpload.cancelButton")} + </Button> + <Button + onClick={handleFilesNext} + disabled={selectedFiles.length === 0} + > + {t("bulkUpload.validateButton")} + <ChevronRight className="ml-2 h-4 w-4" /> + </Button> + </> + )} + </DialogFooter> + )} + </DialogContent> + </Dialog> + + {/* Validation Dialog */} + <B4UploadValidationDialog + open={showValidationDialog} + onOpenChange={(open) => { + setShowValidationDialog(open); + if (!open && currentStep !== "uploading" && currentStep !== "complete") { + // If canceled during validation view (and not proceeding to upload), go back to file selection or close? + // Usually just close the validation dialog allows user to fix files in the main dialog, + // but here the main dialog is in "validation" state which is just a loader. + // So we should reset main dialog to "files" step. + setCurrentStep("files"); + } + }} + validationResults={validationResults} + onConfirmUpload={handleConfirmUpload} + isUploading={isUploading} + /> + </> + ); +} + diff --git a/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx b/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx index b7b25fca..64e67b8c 100644 --- a/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx +++ b/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx @@ -440,7 +440,12 @@ export function B4BulkUploadDialog({ UploadId: uploadId, }; - await saveB4MappingBatch([mappingSaveItem], userId); + await saveB4MappingBatch([mappingSaveItem], { + userId, + userName, + vendorCode, + email: userEmail, + }); console.log(`[B4 업로드] 그룹 ${groupKey} 매핑 정보 저장 완료`); |
