summaryrefslogtreecommitdiff
path: root/lib/procurement-rfqs/table/pr-item-dialog.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-05-12 11:36:25 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-05-12 11:36:25 +0000
commita6b9cdaf9ea5ed548292632f821e36453f377a83 (patch)
tree1c07b92b4173bfe3a12eedba7188fba8dc6f94cb /lib/procurement-rfqs/table/pr-item-dialog.tsx
parentdf91418cd28e98ce05845e476e51aa810202bf33 (diff)
(대표님) procurement-rfq 작업업
Diffstat (limited to 'lib/procurement-rfqs/table/pr-item-dialog.tsx')
-rw-r--r--lib/procurement-rfqs/table/pr-item-dialog.tsx258
1 files changed, 258 insertions, 0 deletions
diff --git a/lib/procurement-rfqs/table/pr-item-dialog.tsx b/lib/procurement-rfqs/table/pr-item-dialog.tsx
new file mode 100644
index 00000000..4523295d
--- /dev/null
+++ b/lib/procurement-rfqs/table/pr-item-dialog.tsx
@@ -0,0 +1,258 @@
+"use client";
+
+import * as React from "react";
+import { useState, useEffect } from "react";
+import { formatDate } from "@/lib/utils";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Badge } from "@/components/ui/badge";
+import { ProcurementRfqsView } from "@/db/schema";
+import { fetchPrItemsByRfqId } from "../services";
+import {
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Input } from "@/components/ui/input";
+import { Search } from "lucide-react";
+
+// PR 항목 타입 정의
+interface PrItemView {
+ id: number;
+ procurementRfqsId: number;
+ rfqItem: string | null;
+ prItem: string | null;
+ prNo: string | null;
+ itemId: number | null;
+ materialCode: string | null;
+ materialCategory: string | null;
+ acc: string | null;
+ materialDescription: string | null;
+ size: string | null;
+ deliveryDate: Date | null;
+ quantity: number | null;
+ uom: string | null;
+ grossWeight: number | null;
+ gwUom: string | null;
+ specNo: string | null;
+ specUrl: string | null;
+ trackingNo: string | null;
+ majorYn: boolean | null;
+ projectDef: string | null;
+ projectSc: string | null;
+ projectKl: string | null;
+ projectLc: string | null;
+ projectDl: string | null;
+ remark: string | null;
+ rfqCode: string | null;
+ itemCode: string | null;
+ itemName: string | null;
+}
+
+interface PrDetailsDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ selectedRfq: ProcurementRfqsView | null;
+}
+
+export function PrDetailsDialog({
+ open,
+ onOpenChange,
+ selectedRfq,
+}: PrDetailsDialogProps) {
+ const [isLoading, setIsLoading] = useState(false);
+ const [prItems, setPrItems] = useState<PrItemView[]>([]);
+ const [searchTerm, setSearchTerm] = useState("");
+
+ // 검색어로 필터링된 항목들
+ const filteredItems = React.useMemo(() => {
+ if (!searchTerm.trim()) return prItems;
+
+ const term = searchTerm.toLowerCase();
+ return prItems.filter(item =>
+ (item.materialDescription || "").toLowerCase().includes(term) ||
+ (item.materialCode || "").toLowerCase().includes(term) ||
+ (item.prNo || "").toLowerCase().includes(term) ||
+ (item.prItem || "").toLowerCase().includes(term) ||
+ (item.rfqItem || "").toLowerCase().includes(term)
+ );
+ }, [prItems, searchTerm]);
+
+ // 선택된 RFQ가 변경되면 PR 항목 데이터를 가져옴
+ useEffect(() => {
+ async function loadPrItems() {
+ if (!selectedRfq || !open) {
+ setPrItems([]);
+ return;
+ }
+
+ try {
+ setIsLoading(true);
+ const result = await fetchPrItemsByRfqId(selectedRfq.id);
+ const mappedItems: PrItemView[] = result.data.map(item => ({
+ ...item,
+ // procurementRfqsId가 null이면 selectedRfq.id 사용
+ procurementRfqsId: item.procurementRfqsId ?? selectedRfq.id,
+ // 기타 필요한 필드에 대한 기본값 처리
+ rfqItem: item.rfqItem ?? null,
+ prItem: item.prItem ?? null,
+ prNo: item.prNo ?? null,
+ // 다른 필드도 필요에 따라 추가
+ }));
+
+ setPrItems(mappedItems);
+ } catch (error) {
+ console.error("PR 항목 로드 오류:", error);
+ setPrItems([]);
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ if (open) {
+ loadPrItems();
+ setSearchTerm("");
+ }
+ }, [selectedRfq, open]);
+
+ // 선택된 RFQ가 없는 경우
+ if (!selectedRfq) {
+ return null;
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-screen-sm max-h-[90vh] flex flex-col" style={{ maxWidth: "70vw" }}>
+ <DialogHeader>
+ <DialogTitle className="text-xl">
+ PR 상세 정보 - {selectedRfq.rfqCode}
+ </DialogTitle>
+ <DialogDescription>
+ 프로젝트: {selectedRfq.projectName} ({selectedRfq.projectCode}) | 건수:{" "}
+ {selectedRfq.prItemsCount || 0}건
+ </DialogDescription>
+ </DialogHeader>
+
+ {isLoading ? (
+ <div className="py-4 space-y-3">
+ <Skeleton className="h-8 w-full" />
+ <Skeleton className="h-24 w-full" />
+ <Skeleton className="h-24 w-full" />
+ </div>
+ ) : (
+ <div className="flex-1 flex flex-col">
+ {/* 검색 필드 */}
+ <div className="mb-4 relative">
+ <div className="absolute inset-y-0 left-0 flex items-center pl-2 pointer-events-none">
+ <Search className="h-4 w-4 text-muted-foreground" />
+ </div>
+ <Input
+ placeholder="PR 번호, 자재 코드, 설명 등 검색..."
+ value={searchTerm}
+ onChange={(e) => setSearchTerm(e.target.value)}
+ className="pl-8"
+ />
+</div>
+ {filteredItems.length === 0 ? (
+ <div className="flex items-center justify-center py-8 text-muted-foreground border rounded-md">
+ {prItems.length === 0 ? "PR 항목이 없습니다" : "검색 결과가 없습니다"}
+ </div>
+ ) : (
+ <div className="rounded-md border flex-1 overflow-hidden">
+ <div className="overflow-x-auto" style={{ width: "100%" }}>
+ <Table style={{ minWidth: "2500px" }}>
+ <TableCaption>
+ 총 {filteredItems.length}개 항목 (전체 {prItems.length}개 중)
+ </TableCaption>
+ <TableHeader className="bg-muted/50 sticky top-0">
+ <TableRow>
+ <TableHead className="w-[100px] whitespace-nowrap">RFQ Item</TableHead>
+ <TableHead className="w-[120px] whitespace-nowrap">PR 번호</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">PR Item</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">자재그룹</TableHead>
+ <TableHead className="w-[120px] whitespace-nowrap">자재 코드</TableHead>
+ <TableHead className="w-[120px] whitespace-nowrap">자재 카테고리</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">ACC</TableHead>
+ <TableHead className="min-w-[200px] whitespace-nowrap">자재 설명</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">규격</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">납품일</TableHead>
+ <TableHead className="w-[80px] whitespace-nowrap">수량</TableHead>
+ <TableHead className="w-[80px] whitespace-nowrap">UOM</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">총중량</TableHead>
+ <TableHead className="w-[80px] whitespace-nowrap">중량 단위</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">사양 번호</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">사양 URL</TableHead>
+ <TableHead className="w-[120px] whitespace-nowrap">추적 번호</TableHead>
+ <TableHead className="w-[80px] whitespace-nowrap">주요 항목</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">프로젝트 DEF</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">프로젝트 SC</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">프로젝트 KL</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">프로젝트 LC</TableHead>
+ <TableHead className="w-[100px] whitespace-nowrap">프로젝트 DL</TableHead>
+ <TableHead className="w-[150px] whitespace-nowrap">비고</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {filteredItems.map((item) => (
+ <TableRow key={item.id}>
+ <TableCell className="whitespace-nowrap">{item.rfqItem || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.prNo || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.prItem || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.itemCode || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.materialCode || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.materialCategory || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.acc || "-"}</TableCell>
+ <TableCell>{item.materialDescription || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.size || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">
+ {item.deliveryDate ? formatDate(item.deliveryDate) : "-"}
+ </TableCell>
+ <TableCell className="whitespace-nowrap">{item.quantity || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.uom || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.grossWeight || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.gwUom || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.specNo || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.specUrl || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.trackingNo || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">
+ {item.majorYn ? (
+ <Badge variant="secondary">주요</Badge>
+ ) : (
+ "아니오"
+ )}
+ </TableCell>
+ <TableCell className="whitespace-nowrap">{item.projectDef || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.projectSc || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.projectKl || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.projectLc || "-"}</TableCell>
+ <TableCell className="whitespace-nowrap">{item.projectDl || "-"}</TableCell>
+ <TableCell className="text-sm">{item.remark || "-"}</TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </div>
+ </div>
+ )}
+ </div>
+ )}
+
+ <DialogFooter className="mt-2">
+ <Button onClick={() => onOpenChange(false)}>닫기</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ );
+} \ No newline at end of file