summaryrefslogtreecommitdiff
path: root/lib/rfq-last/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/table')
-rw-r--r--lib/rfq-last/table/create-general-rfq-dialog.tsx44
-rw-r--r--lib/rfq-last/table/rfq-table-columns.tsx292
-rw-r--r--lib/rfq-last/table/rfq-table.tsx91
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);