summaryrefslogtreecommitdiff
path: root/lib/tech-vendors/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendors/table')
-rw-r--r--lib/tech-vendors/table/tech-vendor-possible-items-view-dialog.tsx201
-rw-r--r--lib/tech-vendors/table/tech-vendors-table-columns.tsx50
-rw-r--r--lib/tech-vendors/table/tech-vendors-table.tsx22
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