From 3ef36fc20e10391619be0a57de1b2184dece606a Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Mon, 8 Dec 2025 18:51:10 +0900 Subject: (김준회) 견적: 일반견적: 시리즈 컬럼 주석 처리 (상세품목에 프로젝트 정보 없음) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (김준회) 견적: 상세품목 다이얼로그에서 합쳐서 표현된 컬럼 분리시킴 (김준회) 견적: 마감일 남은 일자 표기 오류 수정 (김준회) 견적: 일반견적 생성시 마감일은 생성일(현재일자) + 7 calendar day 로 기본값 설정 (김준회) 견적: 벤더응답 상세: 제출문서 탭 추가 --- lib/rfq-last/shared/rfq-items-dialog.tsx | 30 ++- lib/rfq-last/table/create-general-rfq-dialog.tsx | 44 ++-- lib/rfq-last/table/rfq-table-columns.tsx | 292 ++++++----------------- lib/rfq-last/vendor/vendor-detail-dialog.tsx | 120 ++++++---- 4 files changed, 181 insertions(+), 305 deletions(-) (limited to 'lib') diff --git a/lib/rfq-last/shared/rfq-items-dialog.tsx b/lib/rfq-last/shared/rfq-items-dialog.tsx index afed9576..6a40dcfa 100644 --- a/lib/rfq-last/shared/rfq-items-dialog.tsx +++ b/lib/rfq-last/shared/rfq-items-dialog.tsx @@ -272,6 +272,8 @@ export function RfqItemsDialog({ 아이템 자재코드 자재명 + 자재그룹 + 사이즈 Specification 수량 수량단위 @@ -280,7 +282,9 @@ export function RfqItemsDialog({ PR 발행일 PR납기 요청일 PR번호 + PR 아이템 번호 사양/설계문서 + 프로젝트 @@ -298,6 +302,10 @@ export function RfqItemsDialog({ + + + + ))} @@ -314,6 +322,8 @@ export function RfqItemsDialog({ 아이템 자재코드 자재명 + 자재그룹 + 사이즈 Specification 수량 수량단위 @@ -355,18 +365,18 @@ export function RfqItemsDialog({ {item.materialDescription || "-"} - {item.materialCategory && ( - - {item.materialCategory} - - )} - {item.size && ( - - 크기: {item.size} - - )} + + + {item.materialCategory || "-"} + + + + + {item.size || "-"} + + {item.specification?.trim() ? ( diff --git a/lib/rfq-last/table/create-general-rfq-dialog.tsx b/lib/rfq-last/table/create-general-rfq-dialog.tsx index b46249f0..6f752bd0 100644 --- a/lib/rfq-last/table/create-general-rfq-dialog.tsx +++ b/lib/rfq-last/table/create-general-rfq-dialog.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { useForm, useFieldArray } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" -import { format } from "date-fns" +import { format, addDays } from "date-fns" import { CalendarIcon, Plus, Loader2, Trash2, PlusCircle } from "lucide-react" import { useSession } from "next-auth/react" @@ -103,25 +103,25 @@ export function CreateGeneralRfqDialog({ onSuccess }: CreateGeneralRfqDialogProp const form = useForm({ resolver: zodResolver(createGeneralRfqSchema), - defaultValues: { - rfqType: "", - rfqTitle: "", - dueDate: undefined, - picUserId: userId || undefined, - projectId: undefined, - remark: "", - items: [ - { - itemCode: "", - itemName: "", - materialCode: "", - materialName: "", - quantity: 1, - uom: "", - remark: "", - }, - ], - }, + defaultValues: { + rfqType: "", + rfqTitle: "", + dueDate: addDays(new Date(), 7), + picUserId: userId || undefined, + projectId: undefined, + remark: "", + items: [ + { + itemCode: "", + itemName: "", + materialCode: "", + materialName: "", + quantity: 1, + uom: "", + remark: "", + }, + ], + }, }) const { fields, append, remove } = useFieldArray({ @@ -193,7 +193,7 @@ export function CreateGeneralRfqDialog({ onSuccess }: CreateGeneralRfqDialogProp form.reset({ rfqType: "", rfqTitle: "", - dueDate: undefined, + dueDate: addDays(new Date(), 7), picUserId: userId || undefined, projectId: undefined, remark: "", @@ -219,7 +219,7 @@ export function CreateGeneralRfqDialog({ onSuccess }: CreateGeneralRfqDialogProp form.reset({ rfqType: "", rfqTitle: "", - dueDate: undefined, + dueDate: addDays(new Date(), 7), picUserId: userId || undefined, projectId: undefined, remark: "", diff --git a/lib/rfq-last/table/rfq-table-columns.tsx b/lib/rfq-last/table/rfq-table-columns.tsx index 58c45aa0..0d39e0d0 100644 --- a/lib/rfq-last/table/rfq-table-columns.tsx +++ b/lib/rfq-last/table/rfq-table-columns.tsx @@ -15,7 +15,7 @@ import { import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"; import { RfqsLastView } from "@/db/schema"; import { DataTableRowAction } from "@/types/table"; -import { format, differenceInDays } from "date-fns"; +import { format, differenceInCalendarDays } from "date-fns"; import { ko } from "date-fns/locale"; import { useRouter } from "next/navigation"; import { RfqSealToggleCell } from "./rfq-seal-toggle-cell"; @@ -44,6 +44,53 @@ const getStatusBadgeVariant = (status: string) => { } }; +const renderDueDateCell = (date?: string | Date | null) => { + if (!date) return "-"; + + const now = new Date(); + const dueDate = new Date(date); + const daysLeft = differenceInCalendarDays(dueDate, now); + + let statusIcon; + let statusText; + let statusClass; + + if (daysLeft < 0) { + const daysOverdue = Math.abs(daysLeft); + statusIcon = ; + statusText = `${daysOverdue}일 지남`; + statusClass = "text-red-600"; + } else if (daysLeft === 0) { + statusIcon = ; + statusText = "오늘 마감"; + statusClass = "text-orange-600"; + } else if (daysLeft <= 3) { + statusIcon = ; + statusText = `${daysLeft}일 남음`; + statusClass = "text-amber-600"; + } else if (daysLeft <= 7) { + statusIcon = ; + statusText = `${daysLeft}일 남음`; + statusClass = "text-blue-600"; + } else { + statusIcon = ; + statusText = `${daysLeft}일 남음`; + statusClass = "text-green-600"; + } + + return ( +
+ + {format(dueDate, "yyyy-MM-dd")} + +
+ {statusIcon} + {statusText} +
+
+ ); +}; + export function getRfqColumns({ setRowAction, rfqCategory = "itb", @@ -125,7 +172,7 @@ export function getRfqColumns({ cell: ({ row, table }) => ( { // 테이블 데이터를 새로고침하는 로직 // 이 부분은 상위 컴포넌트에서 refreshData 함수를 prop으로 전달받아 사용 @@ -134,7 +181,7 @@ export function getRfqColumns({ }} /> ), - size: 80, + size: 120, }, // 구매담당자 @@ -258,59 +305,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => , - cell: ({ row }) => { - const date = row.original.dueDate; - if (!date) return "-"; - - const now = new Date(); - const dueDate = new Date(date); - const daysLeft = differenceInDays(dueDate, now); - - // 상태별 스타일과 아이콘 설정 - let statusIcon; - let statusText; - let statusClass; - - if (daysLeft < 0) { - // 마감일 지남 - const daysOverdue = Math.abs(daysLeft); - statusIcon = ; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - // 오늘 마감 - statusIcon = ; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - // 3일 이내 마감 임박 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - // 일주일 이내 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - // 여유 있음 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( -
- - {format(dueDate, "yyyy-MM-dd")} - -
- {statusIcon} - {statusText} -
-
- ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, // 크기를 약간 늘림 }, @@ -463,7 +458,7 @@ export function getRfqColumns({ cell: ({ row, table }) => ( { // 테이블 데이터를 새로고침하는 로직 // 이 부분은 상위 컴포넌트에서 refreshData 함수를 prop으로 전달받아 사용 @@ -472,7 +467,7 @@ export function getRfqColumns({ }} /> ), - size: 80, + size: 120, }, // 구매담당자 @@ -628,59 +623,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => , - cell: ({ row }) => { - const date = row.original.dueDate; - if (!date) return "-"; - - const now = new Date(); - const dueDate = new Date(date); - const daysLeft = differenceInDays(dueDate, now); - - // 상태별 스타일과 아이콘 설정 - let statusIcon; - let statusText; - let statusClass; - - if (daysLeft < 0) { - // 마감일 지남 - const daysOverdue = Math.abs(daysLeft); - statusIcon = ; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - // 오늘 마감 - statusIcon = ; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - // 3일 이내 마감 임박 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - // 일주일 이내 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - // 여유 있음 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( -
- - {format(dueDate, "yyyy-MM-dd")} - -
- {statusIcon} - {statusText} -
-
- ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, // 크기를 약간 늘림 }, @@ -978,54 +921,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => , - cell: ({ row }) => { - const date = row.original.dueDate; - if (!date) return "-"; - - const now = new Date(); - const dueDate = new Date(date); - const daysLeft = differenceInDays(dueDate, now); - - // 상태별 스타일과 아이콘 설정 - let statusIcon; - let statusText; - let statusClass; - - if (daysLeft < 0) { - const daysOverdue = Math.abs(daysLeft); - statusIcon = ; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - statusIcon = ; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( -
- - {format(dueDate, "yyyy-MM-dd")} - -
- {statusIcon} - {statusText} -
-
- ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, }, @@ -1166,7 +1062,7 @@ export function getRfqColumns({ cell: ({ row, table }) => ( { // 테이블 데이터를 새로고침하는 로직 // 이 부분은 상위 컴포넌트에서 refreshData 함수를 prop으로 전달받아 사용 @@ -1204,17 +1100,17 @@ export function getRfqColumns({ }, // 시리즈 - { - accessorKey: "series", - header: ({ column }) => , - cell: ({ row }) => { - const series = row.original.series; - if (!series) return "-"; - const label = series === "SS" ? "시리즈 통합" : series === "II" ? "품목 통합" : series; - return {label}; - }, - size: 100, - }, + // { + // accessorKey: "series", + // header: ({ column }) => , + // cell: ({ row }) => { + // const series = row.original.series; + // if (!series) return "-"; + // const label = series === "SS" ? "시리즈 통합" : series === "II" ? "품목 통합" : series; + // return {label}; + // }, + // size: 100, + // }, // 선급 { @@ -1322,59 +1218,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => , - cell: ({ row }) => { - const date = row.original.dueDate; - if (!date) return "-"; - - const now = new Date(); - const dueDate = new Date(date); - const daysLeft = differenceInDays(dueDate, now); - - // 상태별 스타일과 아이콘 설정 - let statusIcon; - let statusText; - let statusClass; - - if (daysLeft < 0) { - // 마감일 지남 - const daysOverdue = Math.abs(daysLeft); - statusIcon = ; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - // 오늘 마감 - statusIcon = ; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - // 3일 이내 마감 임박 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - // 일주일 이내 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - // 여유 있음 - statusIcon = ; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( -
- - {format(dueDate, "yyyy-MM-dd")} - -
- {statusIcon} - {statusText} -
-
- ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, // 크기를 약간 늘림 }, diff --git a/lib/rfq-last/vendor/vendor-detail-dialog.tsx b/lib/rfq-last/vendor/vendor-detail-dialog.tsx index 9c112efa..cbe4f919 100644 --- a/lib/rfq-last/vendor/vendor-detail-dialog.tsx +++ b/lib/rfq-last/vendor/vendor-detail-dialog.tsx @@ -55,6 +55,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; +import { formatFileSize, quickDownload, smartFileAction } from "@/lib/file-download"; // Props 타입 정의 interface VendorResponseDetailDialogProps { @@ -183,10 +184,13 @@ export function VendorResponseDetailDialog({
- + 개요 견적정보 품목상세 + + 제출 문서 + {/* 개요 탭 */} @@ -773,60 +777,78 @@ export function VendorResponseDetailDialog({ )} - {/* 첨부파일 탭 */} - {/* - {attachments.length > 0 ? ( - - - 첨부파일 - - 총 {attachments.length}개 파일 - - - -
- {attachments.map((file: any) => ( -
-
- -
-

{file.originalFileName}

-

- {file.attachmentType} • {file.fileSize ? `${(file.fileSize / 1024).toFixed(2)} KB` : "크기 미상"} - {file.description && ` • ${file.description}`} + {/* 제출 문서 탭 */} + + + + 제출 문서 + + 총 {attachments.length}개 파일 + + + + {attachments.length > 0 ? ( +

+ {attachments.map((file: any) => { + const fileLabel = file.originalFileName || file.fileName || "파일명 없음"; + const canOpen = !!file.filePath; + return ( +
+
+
+ + {file.attachmentType || "문서"} + {fileLabel} +
+

+ {file.documentNo && 문서번호: {file.documentNo}} + {file.description && {file.description}} + {file.fileSize ? formatFileSize(file.fileSize) : "크기 정보 없음"} + {file.uploadedAt && ( + + 업로드: {format(new Date(file.uploadedAt), "yyyy-MM-dd HH:mm", { locale: ko })} + + )} + {file.uploadedByName && 작성: {file.uploadedByName}}

+
+ + +
-
- -
-
- ))} + ); + })}
- - - ) : ( - - -
+ ) : ( +
아직 제출된 첨부파일이 없습니다.
- - - )} - */} + )} + + +
-- cgit v1.2.3