From cbb4c7fe0b94459162ad5e998bc05cd293e0ff96 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 11 Aug 2025 09:02:00 +0000 Subject: (대표님) 입찰, EDP 변경사항 대응, spreadJS 오류 수정, 벤더실사 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enhanced-document-service.ts | 11 +- .../plant/document-stage-dialogs.tsx | 892 ++++++++++++++++----- .../plant/document-stages-expanded-content.tsx | 98 ++- .../plant/document-stages-service.ts | 314 +++++--- .../plant/document-stages-table.tsx | 10 - .../ship/import-from-dolce-button.tsx | 152 ++-- .../ship/send-to-shi-button.tsx | 127 +-- 7 files changed, 1129 insertions(+), 475 deletions(-) (limited to 'lib/vendor-document-list') diff --git a/lib/vendor-document-list/enhanced-document-service.ts b/lib/vendor-document-list/enhanced-document-service.ts index 28fad74b..d2cec15d 100644 --- a/lib/vendor-document-list/enhanced-document-service.ts +++ b/lib/vendor-document-list/enhanced-document-service.ts @@ -1027,11 +1027,11 @@ export async function getDocumentDetails(documentId: number) { // 2. 해당 벤더의 모든 계약 ID들 조회 const vendorContracts = await db - .select({ projectId: contracts.projectId }) + .select({ projectId: contracts.projectId, contractId:contracts.id }) .from(contracts) .where(eq(contracts.vendorId, companyId)) - const contractIds = vendorContracts.map(c => c.projectId) + const contractIds = vendorContracts.map(c => c.contractId) if (contractIds.length === 0) { return { data: [], pageCount: 0, total: 0, drawingKind: null, vendorInfo: null } @@ -1057,7 +1057,7 @@ export async function getDocumentDetails(documentId: number) { // 5. 최종 WHERE 조건 (계약 ID들로 필터링) const finalWhere = and( - inArray(simplifiedDocumentsView.projectId, contractIds), + inArray(simplifiedDocumentsView.contractId, contractIds), advancedWhere, globalWhere, ) @@ -1099,9 +1099,8 @@ export async function getDocumentDetails(documentId: number) { vendorName: vendors.vendorName, vendorCode: vendors.vendorCode, }) - .from(contracts) - .leftJoin(vendors, eq(contracts.vendorId, vendors.id)) - .where(eq(contracts.projectId, contractIds[0])) + .from(vendors) + .where(eq(vendors.id, companyId)) .limit(1) return { data, total, drawingKind, vendorInfo } diff --git a/lib/vendor-document-list/plant/document-stage-dialogs.tsx b/lib/vendor-document-list/plant/document-stage-dialogs.tsx index 726ea101..3811e668 100644 --- a/lib/vendor-document-list/plant/document-stage-dialogs.tsx +++ b/lib/vendor-document-list/plant/document-stage-dialogs.tsx @@ -32,7 +32,7 @@ import { } from "@/components/ui/select" import { Badge } from "@/components/ui/badge" import { DocumentStagesOnlyView } from "@/db/schema" -import { Upload, FileSpreadsheet, Calendar, User, Target, Loader2, AlertTriangle, Loader ,Trash} from "lucide-react" +import { Upload, FileSpreadsheet, Calendar, User, Target, Loader2, AlertTriangle, Loader ,Trash, CheckCircle, Download, AlertCircle} from "lucide-react" import { toast } from "sonner" import { getDocumentNumberTypes, @@ -43,7 +43,8 @@ import { createDocument, updateDocument, deleteDocuments, - updateStage + updateStage, + getDocumentClassOptionsByContract } from "./document-stages-service" import { type Row } from "@tanstack/react-table" @@ -59,6 +60,32 @@ import { DrawerTrigger, } from "@/components/ui/drawer" import { useRouter } from "next/navigation" +import { cn, formatDate } from "@/lib/utils" +import ExcelJS from 'exceljs' +import { Progress } from "@/components/ui/progress" +import { Alert, AlertDescription } from "@/components/ui/alert" + +const getStatusVariant = (status: string) => { + switch (status) { + case 'COMPLETED': return 'success' + case 'IN_PROGRESS': return 'default' + case 'SUBMITTED': return 'secondary' + case 'REJECTED': return 'destructive' + default: return 'outline' + } +} + +const getStatusText = (status: string) => { + switch (status) { + case 'PLANNED': return '계획' + case 'IN_PROGRESS': return '진행중' + case 'SUBMITTED': return '제출' + case 'COMPLETED': return '완료' + case 'REJECTED': return '반려' + default: return status + } +} + // ============================================================================= // 1. Add Document Dialog @@ -83,6 +110,10 @@ export function AddDocumentDialog({ const [comboBoxOptions, setComboBoxOptions] = React.useState>({}) const [documentClassOptions, setDocumentClassOptions] = React.useState([]) + + console.log(documentNumberTypes, "documentNumberTypes") + console.log(documentClassOptions, "documentClassOptions") + const [formData, setFormData] = React.useState({ documentNumberTypeId: "", documentClassId: "", @@ -103,8 +134,8 @@ export function AddDocumentDialog({ setIsLoading(true) try { const [typesResult, classesResult] = await Promise.all([ - getDocumentNumberTypes(), - getDocumentClasses() + getDocumentNumberTypes(contractId), + getDocumentClasses(contractId) ]) if (typesResult.success) { @@ -137,8 +168,8 @@ export function AddDocumentDialog({ const comboBoxPromises = configsResult.data .filter(config => config.codeGroup?.controlType === 'combobox') .map(async (config) => { - const optionsResult = await getComboBoxOptions(config.codeGroupId!) - return { + const optionsResult = await getComboBoxOptions(config.codeGroupId!, contractId) + return { codeGroupId: config.codeGroupId, options: optionsResult.success ? optionsResult.data : [] } @@ -328,7 +359,7 @@ export function AddDocumentDialog({ {documentNumberTypes.map((type) => ( - {type.name} - {type.description} + {type.name} ))} @@ -408,7 +439,7 @@ export function AddDocumentDialog({ {documentClasses.map((cls) => ( - {cls.code} - {cls.description} + {cls.value} ))} @@ -496,7 +527,7 @@ export function AddDocumentDialog({ } // ============================================================================= -// 2. Edit Document Dialog +// Edit Document Dialog (with improved stage plan date editing) // ============================================================================= interface EditDocumentDialogProps { open: boolean @@ -591,7 +622,7 @@ export function EditDocumentDialog({ return ( - + Edit Document @@ -644,28 +675,54 @@ export function EditDocumentDialog({
{document.allStages .sort((a, b) => (a.stageOrder || 0) - (b.stageOrder || 0)) - .map((stage) => ( -
-
- -

- Status: {stage.stageStatus} - {stage.actualDate && ` | Completed: ${stage.actualDate}`} -

-
-
- - handleStagePlanDateChange(stage.id, e.target.value)} - className="text-sm" - /> -
-
- ))} + .map((stage) => { + const canEditPlanDate = stage.stageStatus === 'PLANNED' + return ( +
+
+ +
+ + {getStatusText(stage.stageStatus)} + + {!canEditPlanDate && ( + + 편집 불가 + + )} +
+ {stage.actualDate && ( +

+ 완료일: {formatDate(stage.actualDate)} +

+ )} +
+
+ + handleStagePlanDateChange(stage.id, e.target.value)} + disabled={!canEditPlanDate} + className={cn( + "text-sm", + !canEditPlanDate && "bg-gray-100 cursor-not-allowed" + )} + /> + {!canEditPlanDate && ( +

+ 계획 상태일 때만 수정 가능 +

+ )} +
+
+ ) + })}
)} @@ -689,6 +746,10 @@ export function EditDocumentDialog({ // ============================================================================= // 3. Edit Stage Dialog // ============================================================================= + +// ============================================================================= +// Improved Edit Stage Dialog +// ============================================================================= interface EditStageDialogProps { open: boolean onOpenChange: (open: boolean) => void @@ -704,33 +765,31 @@ export function EditStageDialog({ }: EditStageDialogProps) { const [isLoading, setIsLoading] = React.useState(false) const [formData, setFormData] = React.useState({ - stageName: "", planDate: "", - actualDate: "", - stageStatus: "PLANNED", - assigneeName: "", - priority: "MEDIUM", notes: "" }) - // Load stage information by stageId - React.useEffect(() => { + // 현재 스테이지 정보 + const currentStage = React.useMemo(() => { if (document && stageId) { - const stage = document.allStages?.find(s => s.id === stageId) - if (stage) { - setFormData({ - stageName: stage.stageName || "", - planDate: stage.planDate || "", - actualDate: stage.actualDate || "", - stageStatus: stage.stageStatus || "PLANNED", - assigneeName: stage.assigneeName || "", - priority: stage.priority || "MEDIUM", - notes: stage.notes || "" - }) - } + return document.allStages?.find(s => s.id === stageId) || null } + return null }, [document, stageId]) + // Load stage information by stageId + React.useEffect(() => { + if (currentStage) { + setFormData({ + planDate: currentStage.planDate || "", + notes: currentStage.notes || "" + }) + } + }, [currentStage]) + + // 계획날짜 편집 가능 여부 확인 + const canEditPlanDate = currentStage?.stageStatus === 'PLANNED' + const handleSubmit = async () => { if (!stageId) return @@ -738,147 +797,137 @@ export function EditStageDialog({ try { const result = await updateStage({ stageId, - ...formData + planDate: formData.planDate, + notes: formData.notes }) if (result.success) { - toast.success("Stage updated successfully.") + toast.success("스테이지가 성공적으로 업데이트되었습니다.") onOpenChange(false) } else { - toast.error(result.error || "Error updating stage.") + toast.error(result.error || "스테이지 업데이트 중 오류가 발생했습니다.") } } catch (error) { - toast.error("Error updating stage.") + toast.error("스테이지 업데이트 중 오류가 발생했습니다.") } finally { setIsLoading(false) } } + if (!currentStage) { + return null + } + return ( - - - Edit Stage + + + 스테이지 편집 - You can modify stage information. + 스테이지의 계획 날짜와 노트를 수정할 수 있습니다. -
-
-
- -
- {formData.stageName} -
-
- -
-
- - setFormData({ ...formData, planDate: e.target.value })} - /> -
-
- - setFormData({ ...formData, actualDate: e.target.value })} - /> +
+ {/* 참조 정보 섹션 */} +
+ + +
+
+ +
{currentStage.stageName}
-
- -
-
- - + {getStatusText(currentStage.stageStatus)} +
-
- - + +
+ +
{currentStage.assigneeName || "미지정"}
+ +
+ +
{currentStage.priority || "MEDIUM"}
+
+ + {currentStage.actualDate && ( +
+ +
+ + {formatDate(currentStage.actualDate)} +
+
+ )}
+
+ {/* 편집 가능한 필드들 */} +
+ {/* 계획 날짜 */}
-
+ {/* 노트 */}
- +