summaryrefslogtreecommitdiff
path: root/lib/dolce
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dolce')
-rw-r--r--lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx246
-rw-r--r--lib/dolce/table/drawing-list-table-v2.tsx2
2 files changed, 171 insertions, 77 deletions
diff --git a/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx b/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx
index e34b76c7..e7322439 100644
--- a/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx
+++ b/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx
@@ -58,6 +58,7 @@ export function B4BulkUploadDialogV3({
lng,
}: B4BulkUploadDialogV3Props) {
const { t } = useTranslation(lng, "dolce");
+ const dragCounter = React.useRef(0);
const [currentStep, setCurrentStep] = useState<UploadStep>("files");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const [isUploading, setIsUploading] = useState(false);
@@ -81,6 +82,7 @@ export function B4BulkUploadDialogV3({
setUploadProgress(0);
setUploadResult(null);
setFileProgresses([]);
+ dragCounter.current = 0;
}
}, [open]);
@@ -89,28 +91,48 @@ export function B4BulkUploadDialogV3({
if (files.length === 0) return;
const existingNames = new Set(selectedFiles.map((f) => f.name));
- const newFiles = files.filter((f) => !existingNames.has(f.name));
+ const uniqueFiles: File[] = [];
+ const duplicates: string[] = [];
+ const newNames = new Set<string>(); // To check duplicates within the new batch
+
+ files.forEach((file) => {
+ if (existingNames.has(file.name) || newNames.has(file.name)) {
+ duplicates.push(file.name);
+ } else {
+ newNames.add(file.name);
+ uniqueFiles.push(file);
+ }
+ });
- if (newFiles.length === 0) {
+ if (duplicates.length > 0) {
toast.error(t("bulkUpload.duplicateFileError"));
- return;
}
- setSelectedFiles((prev) => [...prev, ...newFiles]);
- toast.success(t("bulkUpload.filesSelectedSuccess", { count: newFiles.length }));
+ if (uniqueFiles.length > 0) {
+ setSelectedFiles((prev) => [...prev, ...uniqueFiles]);
+ toast.success(t("bulkUpload.filesSelectedSuccess", { count: uniqueFiles.length }));
+ }
};
// Drag & Drop Handlers
const handleDragEnter = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
- setIsDragging(true);
+ if (currentStep !== "files") return;
+
+ dragCounter.current++;
+ if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
+ setIsDragging(true);
+ }
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
- if (e.currentTarget === e.target) {
+ if (currentStep !== "files") return;
+
+ dragCounter.current--;
+ if (dragCounter.current === 0) {
setIsDragging(false);
}
};
@@ -118,13 +140,18 @@ export function B4BulkUploadDialogV3({
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
+ if (currentStep !== "files") return;
+
e.dataTransfer.dropEffect = "copy";
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
+ if (currentStep !== "files") return;
+
setIsDragging(false);
+ dragCounter.current = 0;
const droppedFiles = Array.from(e.dataTransfer.files);
if (droppedFiles.length > 0) {
@@ -162,6 +189,18 @@ export function B4BulkUploadDialogV3({
};
});
+ // Check for duplicates ignoring extension
+ const nameMap = new Map<string, number>(); // nameWithoutExt -> count
+ parseResults.forEach(r => {
+ const nameWithoutExt = r.file.name.substring(0, r.file.name.lastIndexOf('.'));
+ nameMap.set(nameWithoutExt, (nameMap.get(nameWithoutExt) || 0) + 1);
+ });
+
+ const duplicateNames = new Set<string>();
+ nameMap.forEach((count, name) => {
+ if (count > 1) duplicateNames.add(name);
+ });
+
// 2. Call MatchBatchFileDwg to check mapping status for ALL files
// Even if local parsing failed, we send the filename to the server
const mappingCheckItems = parseResults.map((r) => ({
@@ -178,11 +217,21 @@ export function B4BulkUploadDialogV3({
);
// Store mapping results for later use (upload/save)
- // Use the original file name from our request list as the key to ensure we can look it up later.
- // The API response 'FileNm' might differ (e.g., missing extension), so we rely on the array index order (1:1).
+ // Use FileNm (without extension) to match response results
+ const responseMap = new Map<string, MappingCheckResult>();
+ if (mappingResults && Array.isArray(mappingResults)) {
+ mappingResults.forEach((res) => {
+ if (res && res.FileNm) {
+ responseMap.set(res.FileNm, res);
+ }
+ });
+ }
+
const newMappingResultsMap = new Map<string, MappingCheckResult>();
- parseResults.forEach((parseResult, index) => {
- const result = mappingResults[index];
+ parseResults.forEach((parseResult) => {
+ // Look up by file name without extension
+ const nameWithoutExt = parseResult.file.name.substring(0, parseResult.file.name.lastIndexOf('.'));
+ const result = responseMap.get(nameWithoutExt);
if (result) {
newMappingResultsMap.set(parseResult.file.name, result);
}
@@ -191,14 +240,36 @@ export function B4BulkUploadDialogV3({
// 3. Merge results
const finalResults: FileValidationResult[] = parseResults.map((parseResult) => {
- // Retrieve by file name (now reliably mapped)
+ const nameWithoutExt = parseResult.file.name.substring(0, parseResult.file.name.lastIndexOf('.'));
+
+ // Priority 1: Check for duplicates first (must be invalid regardless of server response)
+ if (duplicateNames.has(nameWithoutExt)) {
+ return {
+ ...parseResult,
+ valid: false,
+ mappingStatus: "not_found" as const,
+ error: "Duplicate file name",
+ };
+ }
+
+ // Priority 2: If local parsing failed, keep it as invalid (server response doesn't matter)
+ if (!parseResult.valid || !parseResult.parsed) {
+ // Return the original parse error
+ return {
+ ...parseResult,
+ valid: false,
+ mappingStatus: "not_found" as const,
+ };
+ }
+
+ // Priority 3: Check server mapping result for valid local parses only
const mappingResult = newMappingResultsMap.get(parseResult.file.name);
- // If mapping exists and is valid, it overrides local validation errors
+ // If mapping exists and is valid on server
if (mappingResult && mappingResult.MappingYN === "Y" && mappingResult.DrawingMoveGbn === "도면입수") {
return {
file: parseResult.file,
- valid: true, // Valid because server recognized it
+ valid: true,
parsed: {
drawingNo: mappingResult.DrawingNo,
revNo: mappingResult.RevNo || "",
@@ -210,38 +281,38 @@ export function B4BulkUploadDialogV3({
};
}
- // If server didn't validate it, fall back to local validation error or server error
- if (!parseResult.valid || !parseResult.parsed) {
- // It was invalid locally, and server didn't save it
- return parseResult;
- }
-
+ // Server validation failed - provide specific error messages
if (!mappingResult) {
return {
...parseResult,
+ valid: false,
mappingStatus: "not_found" as const,
- error: t("validation.notFound"),
+ error: "No matching drawing number found",
};
}
if (mappingResult.MappingYN !== "Y") {
return {
...parseResult,
+ valid: false,
mappingStatus: "not_found" as const,
- error: t("validation.notRegistered"),
+ error: "Drawing not registered in the project",
};
}
if (mappingResult.DrawingMoveGbn !== "도면입수") {
return {
...parseResult,
+ valid: false,
mappingStatus: "not_found" as const,
- error: t("validation.notGttDeliverables"),
+ error: "Not GTT Deliverables",
};
}
+ // Fallback (should not reach here)
return {
...parseResult,
+ valid: false,
mappingStatus: "available" as const,
drawingName: mappingResult.DrawingName || undefined,
registerGroupId: mappingResult.RegisterGroupId,
@@ -263,6 +334,12 @@ export function B4BulkUploadDialogV3({
// Confirm Upload & Save (V3)
const handleConfirmUpload = async (validFiles: FileValidationResult[]) => {
+ // Prevent duplicate calls
+ if (isUploading) {
+ console.warn("[V3 Dialog] Upload already in progress, ignoring duplicate call");
+ return;
+ }
+
setIsUploading(true);
setCurrentStep("uploading");
setShowValidationDialog(false);
@@ -309,13 +386,69 @@ export function B4BulkUploadDialogV3({
let completedGroups = 0;
const results: B4BulkUploadResult["results"] = [];
- // 2. Process each group
+ // 2. Prepare Metadata for Batch Save (Call ONCE)
+ const allMappingSaveItems: B4MappingSaveItem[] = [];
+ const groupUploadIdMap = new Map<string, string>();
+
+ for (const [groupKey, groupItems] of uploadGroups.entries()) {
+ const firstItemMapping = groupItems[0].mappingData;
+ // Reuse existing UploadId if present in API response, otherwise generate new one
+ const uploadId = firstItemMapping.UploadId || uuidv4();
+ groupUploadIdMap.set(groupKey, uploadId);
+
+ const groupMappingItems = groupItems.map((item) => {
+ const m = item.mappingData;
+ return {
+ CGbn: m.CGbn,
+ Category: "TS", // Hardcoded fixed value is required!
+ CheckBox: m.CheckBox,
+ DGbn: m.DGbn,
+ DegreeGbn: m.DegreeGbn,
+ DeptGbn: m.DeptGbn,
+ Discipline: m.Discipline,
+ DrawingKind: m.DrawingKind,
+ DrawingMoveGbn: m.DrawingMoveGbn,
+ DrawingName: m.DrawingName,
+ DrawingNo: m.DrawingNo,
+ DrawingUsage: m.DrawingUsage,
+ FileNm: item.file.name,
+ JGbn: m.JGbn,
+ Manager: m.Manager,
+ MappingYN: m.MappingYN,
+ NewOrNot: m.NewOrNot,
+ ProjectNo: projectNo,
+ RegisterGroup: m.RegisterGroup,
+ RegisterGroupId: m.RegisterGroupId,
+ RegisterKindCode: m.RegisterKindCode,
+ RegisterSerialNo: m.RegisterSerialNo,
+ RevNo: m.RevNo,
+ SGbn: m.SGbn,
+ UploadId: uploadId,
+ status: "Standby", // Hardcoded fixed value is required!
+ };
+ });
+ allMappingSaveItems.push(...groupMappingItems);
+ }
+
+ // Call API Once
+ try {
+ console.log(`[V3 Dialog] Saving metadata batch for ${allMappingSaveItems.length} items`);
+ await saveB4MappingBatch(allMappingSaveItems, {
+ userId,
+ userName,
+ vendorCode,
+ email: userEmail,
+ });
+ } catch (error) {
+ console.error("[V3 Dialog] Metadata save failed:", error);
+ throw new Error(
+ error instanceof Error ? error.message : t("bulkUpload.uploadError")
+ );
+ }
+
+ // 3. Process each group for Physical Upload
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
- // UploadId는 있으면 재활용하고, 없으면 UUID로 만들어서 사용
- const uploadId = firstItemMapping.UploadId || uuidv4();
+ const uploadId = groupUploadIdMap.get(groupKey)!;
console.log(`[V3 Dialog] Processing group ${groupKey}, UploadId: ${uploadId}`);
@@ -329,7 +462,7 @@ export function B4BulkUploadDialogV3({
)
);
- // A. Upload Files (Physical Upload)
+ // B. Upload Files (Physical Upload)
const uploadResult = await uploadFilesWithProgress({
uploadId: uploadId,
userId: userId,
@@ -378,47 +511,6 @@ export function B4BulkUploadDialogV3({
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 fixed value is required!
- CheckBox: m.CheckBox,
- DGbn: m.DGbn,
- DegreeGbn: m.DegreeGbn,
- DeptGbn: m.DeptGbn,
- Discipline: m.Discipline,
- DrawingKind: m.DrawingKind,
- DrawingMoveGbn: m.DrawingMoveGbn,
- DrawingName: m.DrawingName,
- DrawingNo: m.DrawingNo,
- DrawingUsage: m.DrawingUsage,
- FileNm: item.file.name,
- JGbn: m.JGbn,
- Manager: m.Manager,
- MappingYN: m.MappingYN,
- NewOrNot: m.NewOrNot,
- ProjectNo: projectNo,
- RegisterGroup: m.RegisterGroup,
- RegisterGroupId: m.RegisterGroupId,
- RegisterKindCode: m.RegisterKindCode,
- RegisterSerialNo: m.RegisterSerialNo,
- RevNo: m.RevNo,
- SGbn: m.SGbn,
- UploadId: uploadId, // Used for all files in this group
- status: "Standby", // Hardcoded fixed value is required!
- };
- });
-
- await saveB4MappingBatch(mappingSaveLists, {
- userId,
- userName,
- vendorCode,
- email: userEmail,
- });
-
console.log(`[V3 Dialog] Group ${groupKey} complete`);
successCount += groupItems.length;
@@ -487,7 +579,13 @@ export function B4BulkUploadDialogV3({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
+ <DialogContent
+ className="max-w-2xl max-h-[85vh] overflow-y-auto"
+ onDragEnter={handleDragEnter}
+ onDragLeave={handleDragLeave}
+ onDragOver={handleDragOver}
+ onDrop={handleDrop}
+ >
<DialogHeader>
<DialogTitle>{t("bulkUpload.title")} (V3)</DialogTitle>
<DialogDescription>
@@ -506,10 +604,6 @@ export function B4BulkUploadDialogV3({
? "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"
diff --git a/lib/dolce/table/drawing-list-table-v2.tsx b/lib/dolce/table/drawing-list-table-v2.tsx
index 1274a148..929a1252 100644
--- a/lib/dolce/table/drawing-list-table-v2.tsx
+++ b/lib/dolce/table/drawing-list-table-v2.tsx
@@ -44,7 +44,7 @@ interface DrawingListTableV2Props<TData extends DrawingData, TValue> {
getRowId?: (row: TData) => string;
maxHeight?: string; // e.g., "45vh"
minHeight?: string; // e.g., "400px"
- defaultPageSize?: number;
+ defaultPageSize?: number | "all"
}
export function DrawingListTableV2<TData extends DrawingData, TValue>({