diff options
Diffstat (limited to 'lib/tech-vendors/table')
3 files changed, 260 insertions, 13 deletions
diff --git a/lib/tech-vendors/table/tech-vendor-possible-items-view-dialog.tsx b/lib/tech-vendors/table/tech-vendor-possible-items-view-dialog.tsx new file mode 100644 index 00000000..b2b9c990 --- /dev/null +++ b/lib/tech-vendors/table/tech-vendor-possible-items-view-dialog.tsx @@ -0,0 +1,201 @@ +"use client"
+
+import * as React from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Package, FileText, X } from "lucide-react"
+import { getVendorItemsByType } from "../service"
+
+interface VendorPossibleItem {
+ id: number;
+ itemCode: string;
+ itemList: string;
+ workType: string | null;
+ shipTypes?: string | null; // 조선용
+ subItemList?: string | null; // 해양용
+ techVendorType: "조선" | "해양TOP" | "해양HULL";
+}
+
+interface TechVendorPossibleItemsViewDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ vendor: {
+ id: number;
+ vendorName?: string | null;
+ vendorCode?: string | null;
+ techVendorType?: string | null;
+ } | null;
+}
+
+export function TechVendorPossibleItemsViewDialog({
+ open,
+ onOpenChange,
+ vendor,
+}: TechVendorPossibleItemsViewDialogProps) {
+ const [items, setItems] = React.useState<VendorPossibleItem[]>([]);
+ const [loading, setLoading] = React.useState(false);
+
+ console.log("TechVendorPossibleItemsViewDialog render:", { open, vendor });
+
+ React.useEffect(() => {
+ console.log("TechVendorPossibleItemsViewDialog useEffect:", { open, vendorId: vendor?.id });
+ if (open && vendor?.id && vendor?.techVendorType) {
+ loadItems();
+ }
+ }, [open, vendor?.id, vendor?.techVendorType]);
+
+ const loadItems = async () => {
+ if (!vendor?.id || !vendor?.techVendorType) return;
+
+ console.log("Loading items for vendor:", vendor.id, vendor.techVendorType);
+ setLoading(true);
+ try {
+ const result = await getVendorItemsByType(vendor.id, vendor.techVendorType);
+ console.log("Items loaded:", result);
+ if (result.data) {
+ setItems(result.data);
+ }
+ } catch (error) {
+ console.error("Failed to load items:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const getTypeLabel = (type: string) => {
+ switch (type) {
+ case "조선":
+ return "조선";
+ case "해양TOP":
+ return "해양TOP";
+ case "해양HULL":
+ return "해양HULL";
+ default:
+ return type;
+ }
+ };
+
+ const getTypeColor = (type: string) => {
+ switch (type) {
+ case "조선":
+ return "bg-blue-100 text-blue-800";
+ case "해양TOP":
+ return "bg-green-100 text-green-800";
+ case "해양HULL":
+ return "bg-purple-100 text-purple-800";
+ default:
+ return "bg-gray-100 text-gray-800";
+ }
+ };
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-none w-[1200px]">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ 벤더 Possible Items 조회
+ <Badge variant="outline" className="ml-2">
+ {vendor?.vendorName || `Vendor #${vendor?.id}`}
+ </Badge>
+ {vendor?.techVendorType && (
+ <Badge variant="secondary" className={getTypeColor(vendor.techVendorType)}>
+ {getTypeLabel(vendor.techVendorType)}
+ </Badge>
+ )}
+ </DialogTitle>
+ <DialogDescription>
+ 해당 벤더가 공급 가능한 아이템 목록을 확인할 수 있습니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="overflow-x-auto w-full">
+ <div className="space-y-4">
+ {loading ? (
+ <div className="flex items-center justify-center py-8">
+ <div className="text-center space-y-2">
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div>
+ <p className="text-sm text-muted-foreground">아이템을 불러오는 중...</p>
+ </div>
+ </div>
+ ) : items.length === 0 ? (
+ <div className="flex flex-col items-center justify-center py-12 text-center">
+ <FileText className="h-12 w-12 text-muted-foreground mb-3" />
+ <h3 className="text-lg font-medium mb-1">등록된 아이템이 없습니다</h3>
+ <p className="text-sm text-muted-foreground">
+ 이 벤더에 등록된 아이템이 없습니다.
+ </p>
+ </div>
+ ) : (
+ <>
+ {/* 헤더 행 (라벨) */}
+ <div className="flex items-center gap-2 border-b pb-2 font-medium text-sm">
+ <div className="w-[50px] text-center">No.</div>
+ <div className="w-[120px] pl-2">타입</div>
+ <div className="w-[200px] ">자재 그룹</div>
+ <div className="w-[150px] ">공종</div>
+ <div className="w-[300px] ">자재명</div>
+ <div className="w-[150px] ">선종/자재명(상세)</div>
+ </div>
+
+ {/* 아이템 행들 */}
+ <div className="max-h-[50vh] overflow-y-auto pr-1 space-y-2">
+ {items.map((item, index) => (
+ <div
+ key={item.id}
+ className="flex items-center gap-2 group hover:bg-gray-50 p-2 rounded-md transition-colors border"
+ >
+ <div className="w-[50px] text-center text-sm font-medium text-muted-foreground">
+ {index + 1}
+ </div>
+ <div className="w-[120px] pl-2">
+ <Badge variant="secondary" className={`text-xs ${getTypeColor(item.techVendorType)}`}>
+ {getTypeLabel(item.techVendorType)}
+ </Badge>
+ </div>
+ <div className="w-[200px] pl-2 font-mono text-sm">
+ {item.itemCode}
+ </div>
+ <div className="w-[150px] pl-2 text-sm">
+ {item.workType || '-'}
+ </div>
+ <div className="w-[300px] pl-2 font-medium">
+ {item.itemList}
+ </div>
+ <div className="w-[150px] pl-2 text-sm">
+ {item.techVendorType === '조선' ? item.shipTypes : item.subItemList}
+ </div>
+ </div>
+ ))}
+ </div>
+
+ <div className="flex justify-between items-center pt-2 border-t">
+ <div className="flex items-center gap-2">
+ <Package className="h-4 w-4 text-muted-foreground" />
+ <span className="text-sm text-muted-foreground">
+ 총 {items.length}개 아이템
+ </span>
+ </div>
+ </div>
+ </>
+ )}
+ </div>
+ </div>
+
+ <DialogFooter className="mt-6">
+ <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
+ <X className="mr-2 h-4 w-4" />
+ 닫기
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}
\ No newline at end of file diff --git a/lib/tech-vendors/table/tech-vendors-table-columns.tsx b/lib/tech-vendors/table/tech-vendors-table-columns.tsx index 22e89dd0..438fceac 100644 --- a/lib/tech-vendors/table/tech-vendors-table-columns.tsx +++ b/lib/tech-vendors/table/tech-vendors-table-columns.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { type DataTableRowAction } from "@/types/table" import { type ColumnDef } from "@tanstack/react-table" -import { Ellipsis } from "lucide-react" +import { Ellipsis, Package } from "lucide-react" import { toast } from "sonner" import { getErrorMessage } from "@/lib/handle-error" @@ -47,6 +47,7 @@ type NextRouter = ReturnType<typeof useRouter>; interface GetColumnsProps { setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<TechVendor> | null>>; router: NextRouter; + openItemsDialog: (vendor: TechVendor) => void; } @@ -55,7 +56,7 @@ interface GetColumnsProps { /** * tanstack table 컬럼 정의 (중첩 헤더 버전) */ -export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef<TechVendor>[] { +export function getColumns({ setRowAction, router, openItemsDialog }: GetColumnsProps): ColumnDef<TechVendor>[] { // ---------------------------------------------------------------- // 1) select 컬럼 (체크박스) // ---------------------------------------------------------------- @@ -230,12 +231,6 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef className: "bg-slate-800 text-white border-slate-900", iconColor: "text-white" }; - case "PENDING_REVIEW": - return { - variant: "default", - className: "bg-gray-100 text-gray-800 border-gray-300", - iconColor: "text-gray-600" - }; default: return { variant: "default", @@ -251,7 +246,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef "ACTIVE": "활성 상태", "INACTIVE": "비활성 상태", "BLACKLISTED": "거래 금지", - "PENDING_REVIEW": "비교 견적", + "PENDING_REVIEW": "비교 견적" }; return statusMap[status] || status; @@ -306,6 +301,43 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef } }); + // Possible Items 컬럼 추가 (액션 컬럼 직전에) + const possibleItemsColumn: ColumnDef<TechVendor> = { + id: "possibleItems", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="자재 그룹" /> + ), + cell: ({ row }) => { + const vendor = row.original; + + const handleClick = () => { + openItemsDialog(vendor); + }; + + return ( + <Button + variant="ghost" + size="sm" + className="relative h-8 w-8 p-0 group" + onClick={handleClick} + aria-label="View possible items" + > + <Package className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> + <span className="sr-only"> + Possible Items 보기 + </span> + </Button> + ); + }, + enableSorting: false, + enableResizing: false, + size: 80, + meta: { + excelHeader: "Possible Items" + }, + }; + + columns.push(possibleItemsColumn); columns.push(actionsColumn); // 마지막에 액션 컬럼 추가 return columns; diff --git a/lib/tech-vendors/table/tech-vendors-table.tsx b/lib/tech-vendors/table/tech-vendors-table.tsx index 63ca8fcc..125e39dc 100644 --- a/lib/tech-vendors/table/tech-vendors-table.tsx +++ b/lib/tech-vendors/table/tech-vendors-table.tsx @@ -17,6 +17,7 @@ import { TechVendor, techVendors, TechVendorWithAttachments } from "@/db/schema/ import { TechVendorsTableToolbarActions } from "./tech-vendors-table-toolbar-actions" import { UpdateVendorSheet } from "./update-vendor-sheet" import { getVendorStatusIcon } from "../utils" +import { TechVendorPossibleItemsViewDialog } from "./tech-vendor-possible-items-view-dialog" // import { ViewTechVendorLogsDialog } from "./view-tech-vendors-logs-dialog" interface TechVendorsTableProps { @@ -34,14 +35,22 @@ export function TechVendorsTable({ promises }: TechVendorsTableProps) { const [isCompact, setIsCompact] = React.useState<boolean>(false) const [rowAction, setRowAction] = React.useState<DataTableRowAction<TechVendor> | null>(null) + const [itemsDialogOpen, setItemsDialogOpen] = React.useState(false) + const [selectedVendorForItems, setSelectedVendorForItems] = React.useState<TechVendor | null>(null) // **router** 획득 const router = useRouter() - // getColumns() 호출 시, router를 주입 + // openItemsDialog 함수 정의 + const openItemsDialog = React.useCallback((vendor: TechVendor) => { + setSelectedVendorForItems(vendor) + setItemsDialogOpen(true) + }, []) + + // getColumns() 호출 시, router와 openItemsDialog를 주입 const columns = React.useMemo( - () => getColumns({ setRowAction, router }), - [setRowAction, router] + () => getColumns({ setRowAction, router, openItemsDialog }), + [setRowAction, router, openItemsDialog] ) // 상태 한글 변환 유틸리티 함수 @@ -133,7 +142,7 @@ export function TechVendorsTable({ promises }: TechVendorsTableProps) { enableAdvancedFilter: true, initialState: { sorting: [{ id: "createdAt", desc: true }], - columnPinning: { right: ["actions"] }, + columnPinning: { right: ["actions", "possibleItems"] }, }, getRowId: (originalRow) => String(originalRow.id), shallow: false, @@ -173,6 +182,11 @@ export function TechVendorsTable({ promises }: TechVendorsTableProps) { onOpenChange={() => setRowAction(null)} vendor={rowAction?.row.original ?? null} /> + <TechVendorPossibleItemsViewDialog + open={itemsDialogOpen} + onOpenChange={setItemsDialogOpen} + vendor={selectedVendorForItems} + /> </> ) }
\ No newline at end of file |
