summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-27 13:48:44 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-27 13:48:44 +0900
commitb43b1d92ef3d7e57b5df5cd72f75dc3a1c3f1c7a (patch)
tree943e2a52c9e56bdce2fa3a35ef61b795370f47e0
parent79cfa7ea8f21ae227dbb2843ae536fe876ba7c55 (diff)
(김준회) swp 파일 개수 컬럼 삭제 (API에서 주지 않는 데이터), dolce rebuild 에서 상태값 수정, bulk upload MatchBatchFileDwg API 사용해 Edit 으로 보내도록 수정 (Category, status 하드코딩 값 넣어주도록 처리), 상세도면, 파일 추가시 확인 다이얼로그 추가
-rw-r--r--app/[lng]/partners/(partners)/dolce-upload-v2/dolce-upload-page-v2.tsx21
-rw-r--r--app/[lng]/partners/(partners)/dolce-upload-v2/page.tsx9
-rw-r--r--i18n/locales/en/dolce.json14
-rw-r--r--i18n/locales/ko/dolce.json14
-rw-r--r--lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx415
-rw-r--r--lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx25
-rw-r--r--lib/dolce/dialogs/upload-files-to-detail-dialog.tsx252
-rw-r--r--lib/dolce/table/detail-drawing-columns.tsx2
-rw-r--r--lib/dolce/table/drawing-list-table-v2.tsx4
-rw-r--r--lib/swp/document-service.ts46
-rw-r--r--lib/swp/table/swp-table-columns.tsx20
-rw-r--r--lib/swp/vendor-actions.ts6
12 files changed, 467 insertions, 361 deletions
diff --git a/app/[lng]/partners/(partners)/dolce-upload-v2/dolce-upload-page-v2.tsx b/app/[lng]/partners/(partners)/dolce-upload-v2/dolce-upload-page-v2.tsx
index f5337c1c..29b41136 100644
--- a/app/[lng]/partners/(partners)/dolce-upload-v2/dolce-upload-page-v2.tsx
+++ b/app/[lng]/partners/(partners)/dolce-upload-v2/dolce-upload-page-v2.tsx
@@ -33,8 +33,8 @@ import { drawingListColumns } from "@/lib/dolce/table/drawing-list-columns";
import { createGttDrawingListColumns, DocumentType } from "@/lib/dolce/table/gtt-drawing-list-columns";
import { createDetailDrawingColumns } from "@/lib/dolce/table/detail-drawing-columns";
import { createFileListColumns } from "@/lib/dolce/table/file-list-columns";
-// V2: MatchBatchFileDwg API를 사용하지 않는 새로운 일괄 업로드 (DetailDwgReceiptMgmtEdit 사용)
-import { B4BulkUploadDialogV2 } from "@/lib/dolce/dialogs/b4-bulk-upload-dialog-v2";
+// V3: Sync 기능 없이 일괄 업로드 (MatchBatchFileDwg / Edit 사용)
+import { B4BulkUploadDialogV3 } from "@/lib/dolce/dialogs/b4-bulk-upload-dialog-v3";
// V1로 되돌리려면: 위 줄을 주석 처리하고 아래 줄의 주석을 해제하세요
// import { B4BulkUploadDialog } from "@/lib/dolce/dialogs/b4-bulk-upload-dialog";
import { AddAndModifyDetailDrawingDialog } from "@/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog";
@@ -520,14 +520,14 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
</Card>
{/* 도면 리스트 테이블 - 항상 렌더링 */}
- <Card className="flex-shrink-0" style={{ minHeight: "500px" }}>
+ <Card className="flex-shrink-0 flex flex-col" style={{ minHeight: "500px" }}>
<CardHeader className="py-3">
<CardTitle className="text-base">
{t("drawingList.title")}
{filteredDrawings.length > 0 && ` ${t("drawingList.count", { count: filteredDrawings.length })}`}
</CardTitle>
</CardHeader>
- <CardContent className="p-0">
+ <CardContent className="p-0 flex-1 min-h-0 flex flex-col">
{!projNo || !vendorInfo ? (
<div className="flex items-center justify-center text-muted-foreground p-8" style={{ minHeight: "400px" }}>
<div className="text-center">
@@ -550,7 +550,8 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
onRowClick={handleDrawingClick}
selectedRow={selectedDrawing || undefined}
getRowId={getDrawingId}
- maxHeight="calc(100vh - 600px)"
+ selectedRow={selectedDrawing || undefined}
+ getRowId={getDrawingId}
minHeight="400px"
defaultPageSize={10}
/>
@@ -612,7 +613,6 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
onRowClick={setSelectedDetail}
selectedRow={selectedDetail || undefined}
getRowId={getDetailDrawingId}
- maxHeight="calc(100vh - 600px)"
minHeight="400px"
defaultPageSize={10}
/>
@@ -658,7 +658,6 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
<DrawingListTableV2
columns={fileColumns}
data={files}
- maxHeight="calc(100vh - 600px)"
minHeight="400px"
defaultPageSize={10}
/>
@@ -667,10 +666,10 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
</Card>
</div>
- {/* B4 일괄 업로드 다이얼로그 (V2) */}
- {/* V2: MatchBatchFileDwg API를 사용하지 않는 새로운 방식 */}
+ {/* B4 일괄 업로드 다이얼로그 (V3) */}
+ {/* V3: Sync 기능 없이 일괄 업로드 (MatchBatchFileDwg / Edit 사용) */}
{vendorInfo && vendorInfo.drawingKind === "B4" && projNo && (
- <B4BulkUploadDialogV2
+ <B4BulkUploadDialogV3
open={bulkUploadDialogOpen}
onOpenChange={setBulkUploadDialogOpen}
projectNo={projNo}
@@ -682,7 +681,7 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
lng={lng}
/>
)}
- {/* V1로 되돌리려면: 위의 B4BulkUploadDialogV2를 B4BulkUploadDialog로 변경하세요 */}
+ {/* V1로 되돌리려면: 위의 B4BulkUploadDialogV3를 B4BulkUploadDialog로 변경하세요 */}
{/* 상세도면 추가 다이얼로그 */}
{vendorInfo && selectedDrawing && (
diff --git a/app/[lng]/partners/(partners)/dolce-upload-v2/page.tsx b/app/[lng]/partners/(partners)/dolce-upload-v2/page.tsx
index 6655606f..9ce7c6c6 100644
--- a/app/[lng]/partners/(partners)/dolce-upload-v2/page.tsx
+++ b/app/[lng]/partners/(partners)/dolce-upload-v2/page.tsx
@@ -53,14 +53,9 @@ export default async function DolceUploadPageWrapper({
<div>
<h2 className="text-2xl font-bold tracking-tight">
{lng === "ko"
- ? "DOLCE 도면 업로드 V2"
- : "DOLCE Drawing Upload V2"}
+ ? "조선 도면 업로드"
+ : "Shipbuilding Drawing Upload"}
</h2>
- <p className="text-muted-foreground">
- {lng === "ko"
- ? "설계문서를 조회하고 업로드할 수 있습니다 (분할 레이아웃)"
- : "View and upload design documents (Split Layout)"}
- </p>
</div>
</div>
diff --git a/i18n/locales/en/dolce.json b/i18n/locales/en/dolce.json
index 66d98f8a..adb35efe 100644
--- a/i18n/locales/en/dolce.json
+++ b/i18n/locales/en/dolce.json
@@ -144,7 +144,12 @@
"selectFilesError": "Please select files",
"uploadSuccess": "{{count}} file(s) uploaded successfully",
"uploadError": "Upload failed",
- "uploadErrorMessage": "An error occurred during upload"
+ "uploadErrorMessage": "An error occurred during upload",
+ "confirmTitle": "Confirm File Upload",
+ "confirmMessage": "Do you want to upload the selected files?",
+ "backButton": "Back",
+ "confirmUpload": "Upload",
+ "nextButton": "Next"
},
"addDetailDialog": {
"title": "Add Detail Drawing",
@@ -180,7 +185,12 @@
"addSuccessPartialUpload": "Detail drawing added but file upload failed: {{error}}",
"addSuccess": "Detail drawing added successfully",
"addError": "Failed to add detail drawing",
- "addErrorMessage": "An error occurred while adding detail drawing"
+ "addErrorMessage": "An error occurred while adding detail drawing",
+ "confirmTitle": "Confirmation",
+ "confirmMessage": "Do you want to submit with the following details?",
+ "backButton": "Back",
+ "confirmSubmit": "Submit",
+ "nextButton": "Next"
},
"editDetailDialog": {
"title": "Edit Detail Drawing",
diff --git a/i18n/locales/ko/dolce.json b/i18n/locales/ko/dolce.json
index 94c61d26..e9f7f862 100644
--- a/i18n/locales/ko/dolce.json
+++ b/i18n/locales/ko/dolce.json
@@ -144,7 +144,12 @@
"selectFilesError": "파일을 선택해주세요",
"uploadSuccess": "{{count}}개 파일 업로드 완료",
"uploadError": "업로드 실패",
- "uploadErrorMessage": "업로드 중 오류가 발생했습니다"
+ "uploadErrorMessage": "업로드 중 오류가 발생했습니다",
+ "confirmTitle": "파일 업로드 확인",
+ "confirmMessage": "선택한 파일을 업로드하시겠습니까?",
+ "backButton": "뒤로",
+ "confirmUpload": "업로드",
+ "nextButton": "다음"
},
"addDetailDialog": {
"title": "상세도면 추가",
@@ -180,7 +185,12 @@
"addSuccessPartialUpload": "상세도면은 추가되었으나 파일 업로드 실패: {{error}}",
"addSuccess": "상세도면이 추가되었습니다",
"addError": "상세도면 추가에 실패했습니다",
- "addErrorMessage": "상세도면 추가 중 오류가 발생했습니다"
+ "addErrorMessage": "상세도면 추가 중 오류가 발생했습니다",
+ "confirmTitle": "확인",
+ "confirmMessage": "아래 내용으로 제출하시겠습니까?",
+ "backButton": "뒤로",
+ "confirmSubmit": "제출",
+ "nextButton": "다음"
},
"editDetailDialog": {
"title": "상세도면 수정",
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 673d48d6..0253228b 100644
--- a/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx
+++ b/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx
@@ -72,6 +72,8 @@ export function AddAndModifyDetailDrawingDialog({
const [comment, setComment] = useState<string>("");
const [isSubmitting, setIsSubmitting] = useState(false);
+ const [showConfirmation, setShowConfirmation] = useState(false);
+
// Edit 모드일 때 초기값 설정
useEffect(() => {
if (mode === "edit" && detailDrawing && open) {
@@ -155,9 +157,10 @@ export function AddAndModifyDetailDrawingDialog({
setRevisionError("");
setComment("");
clearFiles();
+ setShowConfirmation(false);
};
- // 제출
+ // 제출 (확인 단계 포함)
const handleSubmit = async () => {
// 유효성 검사
if (!registerKind) {
@@ -200,6 +203,12 @@ export function AddAndModifyDetailDrawingDialog({
return;
}
+ // 확인 단계가 아니면 확인 단계로 이동
+ if (!showConfirmation) {
+ setShowConfirmation(true);
+ return;
+ }
+
try {
setIsSubmitting(true);
@@ -212,7 +221,7 @@ export function AddAndModifyDetailDrawingDialog({
dwgList: [
{
Mode: "ADD",
- Status: "Submitted",
+ Status: "Standby",
RegisterId: 0,
ProjectNo: drawing.ProjectNo,
Discipline: drawing.Discipline,
@@ -329,8 +338,12 @@ export function AddAndModifyDetailDrawingDialog({
};
const handleCancel = () => {
- resetForm();
- onOpenChange(false);
+ if (showConfirmation) {
+ setShowConfirmation(false);
+ } else {
+ resetForm();
+ onOpenChange(false);
+ }
};
// DrawingUsage가 변경되면 RegisterKind 초기화
@@ -355,219 +368,279 @@ export function AddAndModifyDetailDrawingDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="max-w-2xl">
+ <DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
- {mode === "edit" ? t("editDetailDialog.title") : t("addDetailDialog.title")}
+ {showConfirmation
+ ? t("addDetailDialog.confirmTitle", "확인")
+ : (mode === "edit" ? t("editDetailDialog.title") : t("addDetailDialog.title"))
+ }
</DialogTitle>
</DialogHeader>
- <div className="space-y-6">
- {/* 도면 정보 표시 */}
- {mode === "add" && drawing && (
+ {showConfirmation ? (
+ <div className="space-y-6">
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
- <div className="font-medium">{drawing.DrawingNo}</div>
- <div className="text-sm text-muted-foreground">{drawing.DrawingName}</div>
+ {t("addDetailDialog.confirmMessage", "아래 내용으로 제출하시겠습니까?")}
</AlertDescription>
</Alert>
- )}
- {mode === "edit" && detailDrawing && (
- <Alert>
- <Info className="h-4 w-4" />
- <AlertDescription>
- <div className="font-medium">{detailDrawing.DrawingNo} - Rev. {detailDrawing.DrawingRevNo}</div>
- <div className="text-sm text-muted-foreground">{detailDrawing.DrawingName}</div>
- </AlertDescription>
- </Alert>
- )}
+ <div className="grid grid-cols-2 gap-4 border rounded-lg p-4 bg-muted/20">
+ <div className="space-y-1">
+ <Label className="text-xs text-muted-foreground">{t("addDetailDialog.drawingUsageLabel")}</Label>
+ <p className="text-sm font-medium">
+ {drawingUsageOptions.find(opt => opt.value === drawingUsage)?.label || drawingUsage}
+ </p>
+ </div>
+ <div className="space-y-1">
+ <Label className="text-xs text-muted-foreground">{t("addDetailDialog.registerKindLabel")}</Label>
+ <p className="text-sm font-medium">
+ {registerKindOptions.find(opt => opt.value === registerKind)?.label || registerKind}
+ </p>
+ </div>
+ {drawingUsage !== "CMT" && (
+ <div className="space-y-1">
+ <Label className="text-xs text-muted-foreground">{t("addDetailDialog.revisionLabel")}</Label>
+ <p className="text-sm font-medium">{revision}</p>
+ </div>
+ )}
+ <div className="space-y-1 col-span-2">
+ <Label className="text-xs text-muted-foreground">{t("addDetailDialog.commentLabel")}</Label>
+ <p className="text-sm">{comment || "-"}</p>
+ </div>
+ </div>
+
+ {files.length > 0 && (
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.selectedFiles", { count: files.length })}</Label>
+ <div className="max-h-60 overflow-y-auto space-y-2 border rounded-lg p-2">
+ {isSubmitting ? (
+ <FileUploadProgressList fileProgresses={fileProgresses} />
+ ) : (
+ files.map((file, index) => (
+ <div key={index} className="flex items-center gap-2 p-2 rounded bg-muted/50 text-sm">
+ <FileIcon className="h-4 w-4 text-muted-foreground shrink-0" />
+ <span className="truncate flex-1">{file.name}</span>
+ <span className="text-xs text-muted-foreground whitespace-nowrap">
+ {(file.size / 1024 / 1024).toFixed(2)} MB
+ </span>
+ </div>
+ ))
+ )}
+ </div>
+ </div>
+ )}
+ </div>
+ ) : (
+ <div className="space-y-6">
+ {/* 도면 정보 표시 */}
+ {mode === "add" && drawing && (
+ <Alert>
+ <Info className="h-4 w-4" />
+ <AlertDescription>
+ <div className="font-medium">{drawing.DrawingNo}</div>
+ <div className="text-sm text-muted-foreground">{drawing.DrawingName}</div>
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {mode === "edit" && detailDrawing && (
+ <Alert>
+ <Info className="h-4 w-4" />
+ <AlertDescription>
+ <div className="font-medium">{detailDrawing.DrawingNo} - Rev. {detailDrawing.DrawingRevNo}</div>
+ <div className="text-sm text-muted-foreground">{detailDrawing.DrawingName}</div>
+ </AlertDescription>
+ </Alert>
+ )}
- {/* 도면용도 선택 (Add 모드에서만 표시) */}
- {mode === "add" && (
+ {/* 도면용도 선택 (Add 모드에서만 표시) */}
+ {mode === "add" && (
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.drawingUsageLabel")}</Label>
+ <Select value={drawingUsage} onValueChange={handleDrawingUsageChange}>
+ <SelectTrigger>
+ <SelectValue placeholder={t("addDetailDialog.drawingUsagePlaceholder")} />
+ </SelectTrigger>
+ <SelectContent>
+ {drawingUsageOptions.map((option) => (
+ <SelectItem key={option.value} value={option.value}>
+ {option.label}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ )}
+
+ {/* 등록종류 선택 */}
<div className="space-y-2">
- <Label>{t("addDetailDialog.drawingUsageLabel")}</Label>
- <Select value={drawingUsage} onValueChange={handleDrawingUsageChange}>
+ <Label>{t("addDetailDialog.registerKindLabel")}</Label>
+ <Select
+ value={registerKind}
+ onValueChange={setRegisterKind}
+ disabled={mode === "add" && !drawingUsage}
+ >
<SelectTrigger>
- <SelectValue placeholder={t("addDetailDialog.drawingUsagePlaceholder")} />
+ <SelectValue placeholder={t("addDetailDialog.registerKindPlaceholder")} />
</SelectTrigger>
<SelectContent>
- {drawingUsageOptions.map((option) => (
+ {registerKindOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
+ {revisionRule && (
+ <p className="text-sm text-muted-foreground">
+ {t("addDetailDialog.revisionFormatPrefix")}{revisionRule}
+ </p>
+ )}
</div>
- )}
-
- {/* 등록종류 선택 */}
- <div className="space-y-2">
- <Label>{t("addDetailDialog.registerKindLabel")}</Label>
- <Select
- value={registerKind}
- onValueChange={setRegisterKind}
- disabled={mode === "add" && !drawingUsage}
- >
- <SelectTrigger>
- <SelectValue placeholder={t("addDetailDialog.registerKindPlaceholder")} />
- </SelectTrigger>
- <SelectContent>
- {registerKindOptions.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- {revisionRule && (
- <p className="text-sm text-muted-foreground">
- {t("addDetailDialog.revisionFormatPrefix")}{revisionRule}
- </p>
+
+ {/* Revision 입력 */}
+ {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>
)}
- </div>
- {/* Revision 입력 */}
- {drawingUsage !== "CMT" && (
+ {/* Comment 입력 */}
<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" : ""}
+ <Label>{t("addDetailDialog.commentLabel")}</Label>
+ <Textarea
+ value={comment}
+ onChange={(e) => setComment(e.target.value)}
+ placeholder={t("addDetailDialog.commentPlaceholder")}
+ rows={3}
+ className="resize-none"
/>
- {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>
- )}
+ <p className="text-xs text-muted-foreground">
+ {t("addDetailDialog.commentMaxLength")}
+ </p>
</div>
- )}
-
- {/* Comment 입력 */}
- <div className="space-y-2">
- <Label>{t("addDetailDialog.commentLabel")}</Label>
- <Textarea
- value={comment}
- onChange={(e) => setComment(e.target.value)}
- placeholder={t("addDetailDialog.commentPlaceholder")}
- rows={3}
- className="resize-none"
- />
- <p className="text-xs text-muted-foreground">
- {t("addDetailDialog.commentMaxLength")}
- </p>
- </div>
- {/* 파일 업로드 (Add 모드에서만 표시) */}
- {mode === "add" && (
- <div className="space-y-2">
- <Label>{t("addDetailDialog.attachmentLabel")}</Label>
- <div
- {...getRootProps()}
- className={`
- border-2 border-dashed rounded-lg p-8 text-center cursor-pointer
- transition-colors
- ${isDragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25"}
- ${files.length > 0 ? "py-4" : ""}
- `}
- >
- <input {...getInputProps()} />
- {files.length === 0 ? (
- <div className="space-y-2">
- <Upload className="h-8 w-8 mx-auto text-muted-foreground" />
- <div>
+ {/* 파일 업로드 (Add 모드에서만 표시) */}
+ {mode === "add" && (
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.attachmentLabel")}</Label>
+ <div
+ {...getRootProps()}
+ className={`
+ border-2 border-dashed rounded-lg p-8 text-center cursor-pointer
+ transition-colors
+ ${isDragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25"}
+ ${files.length > 0 ? "py-4" : ""}
+ `}
+ >
+ <input {...getInputProps()} />
+ {files.length === 0 ? (
+ <div className="space-y-2">
+ <Upload className="h-8 w-8 mx-auto text-muted-foreground" />
+ <div>
+ <p className="text-sm font-medium">
+ {t("addDetailDialog.dragDropText")}
+ </p>
+ <p className="text-xs text-muted-foreground">
+ {t("addDetailDialog.fileInfo")}
+ </p>
+ </div>
+ </div>
+ ) : (
+ <div className="space-y-2">
<p className="text-sm font-medium">
- {t("addDetailDialog.dragDropText")}
+ {t("addDetailDialog.filesSelected", { count: files.length })}
</p>
<p className="text-xs text-muted-foreground">
- {t("addDetailDialog.fileInfo")}
+ {t("addDetailDialog.addMoreFiles")}
</p>
</div>
- </div>
- ) : (
- <div className="space-y-2">
- <p className="text-sm font-medium">
- {t("addDetailDialog.filesSelected", { count: files.length })}
- </p>
- <p className="text-xs text-muted-foreground">
- {t("addDetailDialog.addMoreFiles")}
- </p>
- </div>
- )}
- </div>
+ )}
+ </div>
- {/* 선택된 파일 목록 */}
- {files.length > 0 && (
- <div className="space-y-2 mt-4">
- {isSubmitting ? (
- // 업로드 중: 진행도 표시
- <FileUploadProgressList fileProgresses={fileProgresses} />
- ) : (
- // 대기 중: 삭제 버튼 표시
- <>
- <div className="flex items-center justify-between mb-2">
- <h4 className="text-sm font-medium">
- {t("addDetailDialog.selectedFiles", { count: files.length })}
- </h4>
- <Button
- variant="ghost"
- size="sm"
- onClick={clearFiles}
- >
- {t("addDetailDialog.removeAll")}
- </Button>
- </div>
- <div className="max-h-60 overflow-y-auto space-y-2">
- {files.map((file, index) => (
- <div
- key={index}
- className="flex items-center gap-2 p-2 border rounded-lg bg-muted/50"
+ {/* 선택된 파일 목록 */}
+ {files.length > 0 && (
+ <div className="space-y-2 mt-4">
+ {isSubmitting ? (
+ // 업로드 중: 진행도 표시
+ <FileUploadProgressList fileProgresses={fileProgresses} />
+ ) : (
+ // 대기 중: 삭제 버튼 표시
+ <>
+ <div className="flex items-center justify-between mb-2">
+ <h4 className="text-sm font-medium">
+ {t("addDetailDialog.selectedFiles", { count: files.length })}
+ </h4>
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={clearFiles}
>
- <FileIcon className="h-4 w-4 text-muted-foreground shrink-0" />
- <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={() => removeFile(index)}
+ {t("addDetailDialog.removeAll")}
+ </Button>
+ </div>
+ <div className="max-h-60 overflow-y-auto space-y-2">
+ {files.map((file, index) => (
+ <div
+ key={index}
+ className="flex items-center gap-2 p-2 border rounded-lg bg-muted/50"
>
- <X className="h-4 w-4" />
- </Button>
- </div>
- ))}
- </div>
- </>
- )}
+ <FileIcon className="h-4 w-4 text-muted-foreground shrink-0" />
+ <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={() => removeFile(index)}
+ >
+ <X className="h-4 w-4" />
+ </Button>
+ </div>
+ ))}
+ </div>
+ </>
+ )}
+ </div>
+ )}
</div>
)}
- </div>
- )}
- </div>
+ </div>
+ )}
<DialogFooter>
<Button variant="outline" onClick={handleCancel} disabled={isSubmitting}>
- {t("addDetailDialog.cancelButton")}
+ {showConfirmation ? t("addDetailDialog.backButton", "뒤로") : t("addDetailDialog.cancelButton")}
</Button>
<Button onClick={handleSubmit} disabled={isSubmitting || !isFormValid}>
{isSubmitting
? t("addDetailDialog.processingButton")
- : mode === "edit"
- ? t("editDetailDialog.updateButton")
- : t("addDetailDialog.addButton")
+ : showConfirmation
+ ? t("addDetailDialog.confirmSubmit", "제출")
+ : t("addDetailDialog.nextButton", "다음")
}
</Button>
</DialogFooter>
diff --git a/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx b/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx
index ea955420..8bb5dd42 100644
--- a/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx
+++ b/lib/dolce/dialogs/b4-bulk-upload-dialog-v3.tsx
@@ -301,7 +301,7 @@ export function B4BulkUploadDialogV3({
// 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로 만들어줌"
+ // UploadId는 있으면 재활용하고, 없으면 UUID로 만들어서 사용
const uploadId = firstItemMapping.UploadId || uuidv4();
console.log(`[V3 Dialog] Processing group ${groupKey}, UploadId: ${uploadId}`);
@@ -371,30 +371,31 @@ export function B4BulkUploadDialogV3({
const m = item.mappingData;
return {
CGbn: m.CGbn,
- Category: "TS", // Hardcoded as per prompt
- CheckBox: "0",
+ Category: "TS", // Hardcoded fixed value is required!
+ CheckBox: m.CheckBox,
DGbn: m.DGbn,
DegreeGbn: m.DegreeGbn,
DeptGbn: m.DeptGbn,
Discipline: m.Discipline,
- DrawingKind: "B4",
- DrawingMoveGbn: "도면입수",
+ DrawingKind: m.DrawingKind,
+ DrawingMoveGbn: m.DrawingMoveGbn,
DrawingName: m.DrawingName,
DrawingNo: m.DrawingNo,
- DrawingUsage: "입수용",
+ DrawingUsage: m.DrawingUsage,
FileNm: item.file.name,
JGbn: m.JGbn,
- Manager: m.Manager || "970043", // Fallback/Default
- MappingYN: "Y",
- NewOrNot: "N",
+ Manager: m.Manager,
+ MappingYN: m.MappingYN,
+ NewOrNot: m.NewOrNot,
ProjectNo: projectNo,
- RegisterGroup: 0,
+ 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
+ UploadId: uploadId, // Used for all files in this group
+ status: "Standby", // Hardcoded fixed value is required!
};
});
@@ -473,7 +474,7 @@ export function B4BulkUploadDialogV3({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="max-w-2xl">
+ <DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{t("bulkUpload.title")} (V3)</DialogTitle>
<DialogDescription>
diff --git a/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx b/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx
index e8d82129..f21ccc70 100644
--- a/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx
+++ b/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx
@@ -55,20 +55,29 @@ export function UploadFilesToDetailDialog({
isDragActive,
} = useFileUploadWithProgress();
+ const [showConfirmation, setShowConfirmation] = useState(false);
+
// 다이얼로그 닫을 때 초기화
React.useEffect(() => {
if (!open) {
clearFiles();
+ setShowConfirmation(false);
}
}, [open, clearFiles]);
- // 업로드 처리
+ // 업로드 처리 (확인 단계 포함)
const handleUpload = async () => {
if (selectedFiles.length === 0) {
toast.error(t("uploadFilesDialog.selectFilesError"));
return;
}
+ // 확인 단계가 아니면 확인 단계로 이동
+ if (!showConfirmation) {
+ setShowConfirmation(true);
+ return;
+ }
+
setIsUploading(true);
try {
@@ -112,117 +121,169 @@ export function UploadFilesToDetailDialog({
}
};
+ const handleCancel = () => {
+ if (showConfirmation) {
+ setShowConfirmation(false);
+ } else {
+ onOpenChange(false);
+ }
+ };
+
return (
<Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="max-w-2xl">
+ <DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
- <DialogTitle>{t("uploadFilesDialog.title")}</DialogTitle>
+ <DialogTitle>
+ {showConfirmation
+ ? t("uploadFilesDialog.confirmTitle", "파일 업로드 확인")
+ : t("uploadFilesDialog.title")
+ }
+ </DialogTitle>
<DialogDescription>
{t("uploadFilesDialog.description", { drawingNo, revNo })}
</DialogDescription>
</DialogHeader>
- <div className="space-y-4">
- {/* 안내 메시지 */}
- <Alert>
- <AlertCircle className="h-4 w-4" />
- <AlertDescription>
- {t("uploadFilesDialog.alertMessage")}
- </AlertDescription>
- </Alert>
-
- {/* 파일 선택 영역 */}
- <div
- {...getRootProps()}
- className={`border-2 border-dashed rounded-lg p-8 transition-all duration-200 cursor-pointer ${
- isDragActive
- ? "border-primary bg-primary/5 scale-[1.02]"
- : "border-muted-foreground/30 hover:border-muted-foreground/50"
- }`}
- >
- <input {...getInputProps()} />
- <div className="flex flex-col items-center justify-center">
- <FolderOpen
- className={`h-12 w-12 mb-3 transition-colors ${
- isDragActive ? "text-primary" : "text-muted-foreground"
- }`}
- />
- <p
- className={`text-sm transition-colors ${
- isDragActive
- ? "text-primary font-medium"
- : "text-muted-foreground"
- }`}
- >
- {isDragActive
- ? t("uploadFilesDialog.dropHereText")
- : t("uploadFilesDialog.dragDropText")}
- </p>
- <p className="text-xs text-muted-foreground mt-1">
- {t("uploadFilesDialog.fileInfo")}
- </p>
- </div>
- </div>
+ {showConfirmation ? (
+ <div className="space-y-4">
+ <Alert>
+ <AlertCircle className="h-4 w-4" />
+ <AlertDescription>
+ {t("uploadFilesDialog.confirmMessage", "선택한 파일을 업로드하시겠습니까?")}
+ </AlertDescription>
+ </Alert>
- {/* 선택된 파일 목록 */}
- {selectedFiles.length > 0 && (
<div className="border rounded-lg p-4">
- {isUploading ? (
- // 업로드 중: 진행도 표시
- <FileUploadProgressList fileProgresses={fileProgresses} />
- ) : (
- // 대기 중: 삭제 버튼 표시
- <>
- <div className="flex items-center justify-between mb-3">
- <h4 className="text-sm font-medium">
- {t("uploadFilesDialog.selectedFiles", { count: selectedFiles.length })}
- </h4>
- <Button
- variant="ghost"
- size="sm"
- onClick={clearFiles}
+ <h4 className="text-sm font-medium mb-3">
+ {t("uploadFilesDialog.selectedFiles", { count: selectedFiles.length })}
+ </h4>
+ <div className="max-h-60 overflow-y-auto space-y-2">
+ {isUploading ? (
+ <FileUploadProgressList fileProgresses={fileProgresses} />
+ ) : (
+ selectedFiles.map((file, index) => (
+ <div
+ key={index}
+ className="flex items-center justify-between p-2 rounded bg-muted/50"
>
- {t("uploadFilesDialog.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 items-center gap-2 flex-1 min-w-0">
- <FileText className="h-4 w-4 text-muted-foreground shrink-0" />
- <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>
+ <div className="flex items-center gap-2 flex-1 min-w-0">
+ <FileText className="h-4 w-4 text-muted-foreground shrink-0" />
+ <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={() => removeFile(index)}
- >
- <X className="h-4 w-4" />
- </Button>
</div>
- ))}
- </div>
- </>
- )}
+ </div>
+ ))
+ )}
+ </div>
+ </div>
+ </div>
+ ) : (
+ <div className="space-y-4">
+ {/* 안내 메시지 */}
+ <Alert>
+ <AlertCircle className="h-4 w-4" />
+ <AlertDescription>
+ {t("uploadFilesDialog.alertMessage")}
+ </AlertDescription>
+ </Alert>
+
+ {/* 파일 선택 영역 */}
+ <div
+ {...getRootProps()}
+ className={`border-2 border-dashed rounded-lg p-8 transition-all duration-200 cursor-pointer ${
+ isDragActive
+ ? "border-primary bg-primary/5 scale-[1.02]"
+ : "border-muted-foreground/30 hover:border-muted-foreground/50"
+ }`}
+ >
+ <input {...getInputProps()} />
+ <div className="flex flex-col items-center justify-center">
+ <FolderOpen
+ className={`h-12 w-12 mb-3 transition-colors ${
+ isDragActive ? "text-primary" : "text-muted-foreground"
+ }`}
+ />
+ <p
+ className={`text-sm transition-colors ${
+ isDragActive
+ ? "text-primary font-medium"
+ : "text-muted-foreground"
+ }`}
+ >
+ {isDragActive
+ ? t("uploadFilesDialog.dropHereText")
+ : t("uploadFilesDialog.dragDropText")}
+ </p>
+ <p className="text-xs text-muted-foreground mt-1">
+ {t("uploadFilesDialog.fileInfo")}
+ </p>
+ </div>
</div>
- )}
- </div>
+
+ {/* 선택된 파일 목록 */}
+ {selectedFiles.length > 0 && (
+ <div className="border rounded-lg p-4">
+ {isUploading ? (
+ // 업로드 중: 진행도 표시
+ <FileUploadProgressList fileProgresses={fileProgresses} />
+ ) : (
+ // 대기 중: 삭제 버튼 표시
+ <>
+ <div className="flex items-center justify-between mb-3">
+ <h4 className="text-sm font-medium">
+ {t("uploadFilesDialog.selectedFiles", { count: selectedFiles.length })}
+ </h4>
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={clearFiles}
+ >
+ {t("uploadFilesDialog.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 items-center gap-2 flex-1 min-w-0">
+ <FileText className="h-4 w-4 text-muted-foreground shrink-0" />
+ <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>
+ </div>
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => removeFile(index)}
+ >
+ <X className="h-4 w-4" />
+ </Button>
+ </div>
+ ))}
+ </div>
+ </>
+ )}
+ </div>
+ )}
+ </div>
+ )}
<DialogFooter>
<Button
variant="outline"
- onClick={() => onOpenChange(false)}
+ onClick={handleCancel}
disabled={isUploading}
>
- {t("uploadFilesDialog.cancelButton")}
+ {showConfirmation ? t("uploadFilesDialog.backButton", "뒤로") : t("uploadFilesDialog.cancelButton")}
</Button>
<Button
onClick={handleUpload}
@@ -233,10 +294,15 @@ export function UploadFilesToDetailDialog({
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t("uploadFilesDialog.uploadingButton")}
</>
+ ) : showConfirmation ? (
+ <>
+ <Upload className="mr-2 h-4 w-4" />
+ {t("uploadFilesDialog.confirmUpload", "업로드")}
+ </>
) : (
<>
<Upload className="mr-2 h-4 w-4" />
- {t("uploadFilesDialog.uploadButton", { count: selectedFiles.length })}
+ {t("uploadFilesDialog.nextButton", "다음")}
</>
)}
</Button>
diff --git a/lib/dolce/table/detail-drawing-columns.tsx b/lib/dolce/table/detail-drawing-columns.tsx
index 215a0cff..6127a7b6 100644
--- a/lib/dolce/table/detail-drawing-columns.tsx
+++ b/lib/dolce/table/detail-drawing-columns.tsx
@@ -148,7 +148,7 @@ export function createDetailDrawingColumns(
maxSize: 100,
cell: ({ row }) => {
const status = row.getValue("Status") as string;
- const isEditable = status === "Submitted";
+ const isEditable = status === "Standby";
return (
<div
diff --git a/lib/dolce/table/drawing-list-table-v2.tsx b/lib/dolce/table/drawing-list-table-v2.tsx
index 420ed672..e546fa79 100644
--- a/lib/dolce/table/drawing-list-table-v2.tsx
+++ b/lib/dolce/table/drawing-list-table-v2.tsx
@@ -53,7 +53,7 @@ export function DrawingListTableV2<TData extends DrawingData, TValue>({
onRowClick,
selectedRow,
getRowId,
- maxHeight = "45vh",
+ maxHeight,
minHeight = "400px",
defaultPageSize = 10,
}: DrawingListTableV2Props<TData, TValue>) {
@@ -120,7 +120,7 @@ export function DrawingListTableV2<TData extends DrawingData, TValue>({
minHeight: data.length === 0 ? minHeight : undefined,
}}
>
- <Table className="min-w-max">
+ <Table className="w-full">
<TableHeader className="sticky top-0 z-10 bg-background">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
diff --git a/lib/swp/document-service.ts b/lib/swp/document-service.ts
index b89d3442..6d8d7831 100644
--- a/lib/swp/document-service.ts
+++ b/lib/swp/document-service.ts
@@ -149,9 +149,7 @@ export interface DocumentDetail {
* 문서 목록 아이템 (통계 포함)
*/
export interface DocumentListItem extends SwpDocumentApiResponse {
- fileCount: number;
- standbyFileCount: number; // STAT=SCW01
- latestFiles: SwpFileApiResponse[];
+ // fileCount, standbyFileCount, latestFiles 제거됨
}
// ============================================================================
@@ -170,49 +168,21 @@ export async function getDocumentList(
debugLog("[getDocumentList] 시작", { projNo, vndrCd });
try {
- // 병렬 API 호출
- const [documents, allFiles] = await Promise.all([
- fetchGetVDRDocumentList({
- proj_no: projNo,
- doc_gb: "V",
- vndrCd: vndrCd,
- }),
- fetchGetExternalInboxList({
- projNo: projNo,
- vndrCd: vndrCd,
- }),
- ]);
+ // API 호출
+ const documents = await fetchGetVDRDocumentList({
+ proj_no: projNo,
+ doc_gb: "V",
+ vndrCd: vndrCd,
+ });
debugLog("[getDocumentList] API 조회 완료", {
documents: documents.length,
- files: allFiles.length,
});
- // 파일을 문서별로 그룹핑
- const filesByDoc = new Map<string, SwpFileApiResponse[]>();
- for (const file of allFiles) {
- const docNo = file.OWN_DOC_NO;
- if (!filesByDoc.has(docNo)) {
- filesByDoc.set(docNo, []);
- }
- filesByDoc.get(docNo)!.push(file);
- }
-
- // 문서에 파일 통계 추가
+ // 문서 목록 반환 (파일 통계 제거)
const result = documents.map((doc) => {
- const allFiles = filesByDoc.get(doc.OWN_DOC_NO || "") || [];
-
- // 최신 REV의 파일만 필터링
- const latestRevFiles = allFiles.filter((f) => f.REV_NO === doc.LTST_REV_NO);
- const standbyFiles = latestRevFiles.filter((f) => f.STAT === "SCW01");
-
return {
...doc,
- fileCount: latestRevFiles.length,
- standbyFileCount: standbyFiles.length,
- latestFiles: latestRevFiles
- .sort((a, b) => b.CRTE_DTM.localeCompare(a.CRTE_DTM))
- .slice(0, 5), // 최신 5개만
};
});
diff --git a/lib/swp/table/swp-table-columns.tsx b/lib/swp/table/swp-table-columns.tsx
index 91c811c3..261cf960 100644
--- a/lib/swp/table/swp-table-columns.tsx
+++ b/lib/swp/table/swp-table-columns.tsx
@@ -138,25 +138,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [
minSize: 100,
maxSize: 100,
},
- {
- id: "stats",
- header: "파일",
- cell: ({ row }) => (
- <div className="text-center">
- <div className="text-sm font-medium">
- {row.original.fileCount}개
- </div>
- {row.original.standbyFileCount > 0 && (
- <div className="text-xs text-yellow-600">
- 대기중 {row.original.standbyFileCount}
- </div>
- )}
- </div>
- ),
- size: 100,
- minSize: 100,
- maxSize: 100,
- },
+
{
id: "actions",
header: "커버페이지 다운로드",
diff --git a/lib/swp/vendor-actions.ts b/lib/swp/vendor-actions.ts
index 78521fed..12b7c513 100644
--- a/lib/swp/vendor-actions.ts
+++ b/lib/swp/vendor-actions.ts
@@ -304,9 +304,9 @@ export async function fetchVendorSwpStats(projNo?: string) {
let uploadedFiles = 0;
for (const doc of documents) {
- totalFiles += doc.fileCount;
- // standbyFileCount가 0이 아니면 업로드된 것으로 간주
- uploadedFiles += doc.fileCount - doc.standbyFileCount;
+ // 파일 통계는 더 이상 계산하지 않음 (API 호출 제거됨)
+ // totalFiles += doc.fileCount;
+ // uploadedFiles += doc.fileCount - doc.standbyFileCount;
// 리비전 수 추정 (LTST_REV_NO 기반)
if (doc.LTST_REV_NO) {