diff options
Diffstat (limited to 'lib/rfq-last/table')
| -rw-r--r-- | lib/rfq-last/table/create-general-rfq-dialog.tsx | 44 | ||||
| -rw-r--r-- | lib/rfq-last/table/rfq-table-columns.tsx | 292 | ||||
| -rw-r--r-- | lib/rfq-last/table/rfq-table.tsx | 91 |
3 files changed, 127 insertions, 300 deletions
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<CreateGeneralRfqFormValues>({ 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 = <XCircle className="h-4 w-4" />; + statusText = `${daysOverdue}일 지남`; + statusClass = "text-red-600"; + } else if (daysLeft === 0) { + statusIcon = <AlertTriangle className="h-4 w-4" />; + statusText = "오늘 마감"; + statusClass = "text-orange-600"; + } else if (daysLeft <= 3) { + statusIcon = <AlertCircle className="h-4 w-4" />; + statusText = `${daysLeft}일 남음`; + statusClass = "text-amber-600"; + } else if (daysLeft <= 7) { + statusIcon = <Clock className="h-4 w-4" />; + statusText = `${daysLeft}일 남음`; + statusClass = "text-blue-600"; + } else { + statusIcon = <CheckCircle className="h-4 w-4" />; + statusText = `${daysLeft}일 남음`; + statusClass = "text-green-600"; + } + + return ( + <div className="flex flex-col gap-1"> + <span className="text-sm text-muted-foreground"> + {format(dueDate, "yyyy-MM-dd")} + </span> + <div className={`flex items-center gap-1 ${statusClass}`}> + {statusIcon} + <span className="text-xs font-medium">{statusText}</span> + </div> + </div> + ); +}; + export function getRfqColumns({ setRowAction, rfqCategory = "itb", @@ -125,7 +172,7 @@ export function getRfqColumns({ cell: ({ row, table }) => ( <RfqSealToggleCell rfqId={row.original.id} - isSealed={row.original.rfqSealedYn} + isSealed={!!row.original.rfqSealedYn} onUpdate={() => { // 테이블 데이터를 새로고침하는 로직 // 이 부분은 상위 컴포넌트에서 refreshData 함수를 prop으로 전달받아 사용 @@ -134,7 +181,7 @@ export function getRfqColumns({ }} /> ), - size: 80, + size: 120, }, // 구매담당자 @@ -258,59 +305,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적마감일" />, - 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 = <XCircle className="h-4 w-4" />; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - // 오늘 마감 - statusIcon = <AlertTriangle className="h-4 w-4" />; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - // 3일 이내 마감 임박 - statusIcon = <AlertCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - // 일주일 이내 - statusIcon = <Clock className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - // 여유 있음 - statusIcon = <CheckCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( - <div className="flex flex-col gap-1"> - <span className="text-sm text-muted-foreground"> - {format(dueDate, "yyyy-MM-dd")} - </span> - <div className={`flex items-center gap-1 ${statusClass}`}> - {statusIcon} - <span className="text-xs font-medium">{statusText}</span> - </div> - </div> - ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, // 크기를 약간 늘림 }, @@ -463,7 +458,7 @@ export function getRfqColumns({ cell: ({ row, table }) => ( <RfqSealToggleCell rfqId={row.original.id} - isSealed={row.original.rfqSealedYn} + isSealed={!!row.original.rfqSealedYn} onUpdate={() => { // 테이블 데이터를 새로고침하는 로직 // 이 부분은 상위 컴포넌트에서 refreshData 함수를 prop으로 전달받아 사용 @@ -472,7 +467,7 @@ export function getRfqColumns({ }} /> ), - size: 80, + size: 120, }, // 구매담당자 @@ -628,59 +623,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적마감일" />, - 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 = <XCircle className="h-4 w-4" />; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - // 오늘 마감 - statusIcon = <AlertTriangle className="h-4 w-4" />; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - // 3일 이내 마감 임박 - statusIcon = <AlertCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - // 일주일 이내 - statusIcon = <Clock className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - // 여유 있음 - statusIcon = <CheckCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( - <div className="flex flex-col gap-1"> - <span className="text-sm text-muted-foreground"> - {format(dueDate, "yyyy-MM-dd")} - </span> - <div className={`flex items-center gap-1 ${statusClass}`}> - {statusIcon} - <span className="text-xs font-medium">{statusText}</span> - </div> - </div> - ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, // 크기를 약간 늘림 }, @@ -978,54 +921,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적마감일" />, - 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 = <XCircle className="h-4 w-4" />; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - statusIcon = <AlertTriangle className="h-4 w-4" />; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - statusIcon = <AlertCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - statusIcon = <Clock className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - statusIcon = <CheckCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( - <div className="flex flex-col gap-1"> - <span className="text-sm text-muted-foreground"> - {format(dueDate, "yyyy-MM-dd")} - </span> - <div className={`flex items-center gap-1 ${statusClass}`}> - {statusIcon} - <span className="text-xs font-medium">{statusText}</span> - </div> - </div> - ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, }, @@ -1166,7 +1062,7 @@ export function getRfqColumns({ cell: ({ row, table }) => ( <RfqSealToggleCell rfqId={row.original.id} - isSealed={row.original.rfqSealedYn} + isSealed={!!row.original.rfqSealedYn} onUpdate={() => { // 테이블 데이터를 새로고침하는 로직 // 이 부분은 상위 컴포넌트에서 refreshData 함수를 prop으로 전달받아 사용 @@ -1204,17 +1100,17 @@ export function getRfqColumns({ }, // 시리즈 - { - accessorKey: "series", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="시리즈" />, - cell: ({ row }) => { - const series = row.original.series; - if (!series) return "-"; - const label = series === "SS" ? "시리즈 통합" : series === "II" ? "품목 통합" : series; - return <Badge variant="outline">{label}</Badge>; - }, - size: 100, - }, + // { + // accessorKey: "series", + // header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="시리즈" />, + // cell: ({ row }) => { + // const series = row.original.series; + // if (!series) return "-"; + // const label = series === "SS" ? "시리즈 통합" : series === "II" ? "품목 통합" : series; + // return <Badge variant="outline">{label}</Badge>; + // }, + // size: 100, + // }, // 선급 { @@ -1322,59 +1218,7 @@ export function getRfqColumns({ { accessorKey: "dueDate", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적마감일" />, - 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 = <XCircle className="h-4 w-4" />; - statusText = `${daysOverdue}일 지남`; - statusClass = "text-red-600"; - } else if (daysLeft === 0) { - // 오늘 마감 - statusIcon = <AlertTriangle className="h-4 w-4" />; - statusText = "오늘 마감"; - statusClass = "text-orange-600"; - } else if (daysLeft <= 3) { - // 3일 이내 마감 임박 - statusIcon = <AlertCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-amber-600"; - } else if (daysLeft <= 7) { - // 일주일 이내 - statusIcon = <Clock className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-blue-600"; - } else { - // 여유 있음 - statusIcon = <CheckCircle className="h-4 w-4" />; - statusText = `${daysLeft}일 남음`; - statusClass = "text-green-600"; - } - - return ( - <div className="flex flex-col gap-1"> - <span className="text-sm text-muted-foreground"> - {format(dueDate, "yyyy-MM-dd")} - </span> - <div className={`flex items-center gap-1 ${statusClass}`}> - {statusIcon} - <span className="text-xs font-medium">{statusText}</span> - </div> - </div> - ); - }, + cell: ({ row }) => renderDueDateCell(row.original.dueDate), size: 120, // 크기를 약간 늘림 }, diff --git a/lib/rfq-last/table/rfq-table.tsx b/lib/rfq-last/table/rfq-table.tsx index e8dd299d..aa52b2e8 100644 --- a/lib/rfq-last/table/rfq-table.tsx +++ b/lib/rfq-last/table/rfq-table.tsx @@ -70,60 +70,18 @@ export function RfqTable({ // 초기 데이터 설정 const [tableData, setTableData] = React.useState(data); const [isDataLoading, setIsDataLoading] = React.useState(false); + const loadingRef = React.useRef(false); // 중복 요청 방지 + + // 데이터 새로고침 함수 (메모이제이션) + const refetchData = React.useCallback(async () => { + // 이미 로딩 중이면 중복 요청 방지 + if (loadingRef.current) { + console.log("=== 이미 로딩 중, 요청 스킵 ==="); + return; + } - // URL 필터 변경 감지 및 데이터 새로고침 - React.useEffect(() => { - const refetchData = async () => { - try { - setIsDataLoading(true); - - const currentFilters = getSearchParam("filters"); - const currentJoinOperator = getSearchParam("joinOperator", "and"); - const currentPage = parseInt(getSearchParam("page", "1")); - const currentPerPage = parseInt(getSearchParam("perPage", "10")); - const currentSort = getSearchParam('sort') ? JSON.parse(getSearchParam('sort')!) : [{ id: "createdAt", desc: true }]; - const currentSearch = getSearchParam("search", ""); - - const searchParams = { - filters: currentFilters ? JSON.parse(currentFilters) : [], - joinOperator: currentJoinOperator as "and" | "or", - page: currentPage, - perPage: currentPerPage, - sort: currentSort, - search: currentSearch, - rfqCategory: rfqCategory, - }; - - console.log("=== 새 데이터 요청 ===", searchParams); - - const newData = await getRfqs(searchParams); - setTableData(newData); - - console.log("=== 데이터 업데이트 완료 ===", newData.data.length, "건"); - } catch (error) { - console.error("데이터 새로고침 오류:", error); - } finally { - setIsDataLoading(false); - } - }; - - const timeoutId = setTimeout(() => { - const hasChanges = getSearchParam("filters") || - getSearchParam("search") || - getSearchParam("page") !== "1" || - getSearchParam("perPage") !== "10" || - getSearchParam("sort"); - - if (hasChanges) { - refetchData(); - } - }, 300); - - return () => clearTimeout(timeoutId); - }, [searchString, rfqCategory, getSearchParam]); - - const refreshData = React.useCallback(async () => { try { + loadingRef.current = true; setIsDataLoading(true); const currentFilters = getSearchParam("filters"); @@ -143,16 +101,41 @@ export function RfqTable({ rfqCategory: rfqCategory, }; + console.log("=== 새 데이터 요청 ===", searchParams); + const newData = await getRfqs(searchParams); setTableData(newData); - console.log("=== 데이터 새로고침 완료 ===", newData.data.length, "건"); + console.log("=== 데이터 업데이트 완료 ===", newData.data.length, "건"); } catch (error) { console.error("데이터 새로고침 오류:", error); } finally { setIsDataLoading(false); + loadingRef.current = false; } - }, [rfqCategory, getSearchParam]); + }, [getSearchParam, rfqCategory]); + + // URL 필터 변경 감지 및 데이터 새로고침 + React.useEffect(() => { + const timeoutId = setTimeout(() => { + const hasChanges = getSearchParam("filters") || + getSearchParam("search") || + getSearchParam("page") !== "1" || + getSearchParam("perPage") !== "10" || + getSearchParam("sort"); + + if (hasChanges) { + refetchData(); + } + }, 300); + + return () => clearTimeout(timeoutId); + }, [searchString, rfqCategory, refetchData, getSearchParam]); + + // 수동 새로고침 함수 (refetchData 재사용) + const refreshData = React.useCallback(async () => { + await refetchData(); + }, [refetchData]); // 컨테이너 위치 추적 const containerRef = React.useRef<HTMLDivElement>(null); |
