diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-12 11:36:25 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-12 11:36:25 +0000 |
| commit | a6b9cdaf9ea5ed548292632f821e36453f377a83 (patch) | |
| tree | 1c07b92b4173bfe3a12eedba7188fba8dc6f94cb /lib/procurement-rfqs/table/pr-item-dialog.tsx | |
| parent | df91418cd28e98ce05845e476e51aa810202bf33 (diff) | |
(대표님) procurement-rfq 작업업
Diffstat (limited to 'lib/procurement-rfqs/table/pr-item-dialog.tsx')
| -rw-r--r-- | lib/procurement-rfqs/table/pr-item-dialog.tsx | 258 |
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 |
