summaryrefslogtreecommitdiff
path: root/lib/tech-vendors/table/tech-vendors-table.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-21 07:54:26 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-21 07:54:26 +0000
commit14f61e24947fb92dd71ec0a7196a6e815f8e66da (patch)
tree317c501d64662d05914330628f867467fba78132 /lib/tech-vendors/table/tech-vendors-table.tsx
parent194bd4bd7e6144d5c09c5e3f5476d254234dce72 (diff)
(최겸)기술영업 RFQ 담당자 초대, 요구사항 반영
Diffstat (limited to 'lib/tech-vendors/table/tech-vendors-table.tsx')
-rw-r--r--lib/tech-vendors/table/tech-vendors-table.tsx470
1 files changed, 276 insertions, 194 deletions
diff --git a/lib/tech-vendors/table/tech-vendors-table.tsx b/lib/tech-vendors/table/tech-vendors-table.tsx
index a8e18501..7f9625cf 100644
--- a/lib/tech-vendors/table/tech-vendors-table.tsx
+++ b/lib/tech-vendors/table/tech-vendors-table.tsx
@@ -1,195 +1,277 @@
-"use client"
-
-import * as React from "react"
-import { useRouter } from "next/navigation"
-import type {
- DataTableAdvancedFilterField,
- DataTableFilterField,
- DataTableRowAction,
-} from "@/types/table"
-
-import { useDataTable } from "@/hooks/use-data-table"
-import { DataTable } from "@/components/data-table/data-table"
-import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
-import { getColumns } from "./tech-vendors-table-columns"
-import { getTechVendors, getTechVendorStatusCounts } from "../service"
-import { TechVendor, techVendors, TechVendorWithAttachments } from "@/db/schema/techVendors"
-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 {
- promises: Promise<
- [
- Awaited<ReturnType<typeof getTechVendors>>,
- Awaited<ReturnType<typeof getTechVendorStatusCounts>>
- ]
- >
-}
-
-export function TechVendorsTable({ promises }: TechVendorsTableProps) {
- // Suspense로 받아온 데이터
- const [{ data, pageCount }, statusCounts] = React.use(promises)
- 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()
-
- // openItemsDialog 함수 정의
- const openItemsDialog = React.useCallback((vendor: TechVendor) => {
- setSelectedVendorForItems(vendor)
- setItemsDialogOpen(true)
- }, [])
-
- // getColumns() 호출 시, router와 openItemsDialog를 주입
- const columns = React.useMemo(
- () => getColumns({ setRowAction, router, openItemsDialog }),
- [setRowAction, router, openItemsDialog]
- )
-
- // 상태 한글 변환 유틸리티 함수
- const getStatusDisplay = (status: string): string => {
- const statusMap: Record<string, string> = {
- "ACTIVE": "활성 상태",
- "INACTIVE": "비활성 상태",
- "BLACKLISTED": "거래 금지",
- "PENDING_INVITE": "초대 대기",
- "INVITED": "초대 완료",
- "QUOTE_COMPARISON": "견적 비교",
- };
-
- return statusMap[status] || status;
- };
-
- const filterFields: DataTableFilterField<TechVendorWithAttachments>[] = [
- {
- id: "status",
- label: "상태",
- options: techVendors.status.enumValues.map((status) => ({
- label: getStatusDisplay(status),
- value: status,
- count: statusCounts[status],
- })),
- },
-
- { id: "vendorCode", label: "업체 코드" },
- ]
-
- const advancedFilterFields: DataTableAdvancedFilterField<TechVendorWithAttachments>[] = [
- { id: "vendorName", label: "업체명", type: "text" },
- { id: "vendorCode", label: "업체코드", type: "text" },
- { id: "email", label: "이메일", type: "text" },
- { id: "country", label: "국가", type: "text" },
- {
- id: "status",
- label: "업체승인상태",
- type: "multi-select",
- options: techVendors.status.enumValues.map((status) => ({
- label: getStatusDisplay(status),
- value: status,
- count: statusCounts[status],
- icon: getVendorStatusIcon(status),
- })),
- },
- {
- id: "techVendorType",
- label: "벤더 타입",
- type: "multi-select",
- options: [
- { label: "조선", value: "조선" },
- { label: "해양TOP", value: "해양TOP" },
- { label: "해양HULL", value: "해양HULL" },
- ],
- },
- {
- id: "workTypes",
- label: "Work Type",
- type: "multi-select",
- options: [
- // 조선 workTypes
- { label: "기장", value: "기장" },
- { label: "전장", value: "전장" },
- { label: "선실", value: "선실" },
- { label: "배관", value: "배관" },
- { label: "철의", value: "철의" },
- // 해양TOP workTypes
- { label: "TM", value: "TM" },
- { label: "TS", value: "TS" },
- { label: "TE", value: "TE" },
- { label: "TP", value: "TP" },
- // 해양HULL workTypes
- { label: "HA", value: "HA" },
- { label: "HE", value: "HE" },
- { label: "HH", value: "HH" },
- { label: "HM", value: "HM" },
- { label: "NC", value: "NC" },
- ],
- },
- { id: "createdAt", label: "등록일", type: "date" },
- { id: "updatedAt", label: "수정일", type: "date" },
- ]
-
- const { table } = useDataTable({
- data,
- columns,
- pageCount,
- filterFields,
- enablePinning: true,
- enableAdvancedFilter: true,
- initialState: {
- sorting: [{ id: "createdAt", desc: true }],
- columnPinning: { right: ["actions", "possibleItems"] },
- },
- getRowId: (originalRow) => String(originalRow.id),
- shallow: false,
- clearOnDefault: true,
- })
-
- const handleCompactChange = React.useCallback((compact: boolean) => {
- setIsCompact(compact)
- }, [])
-
- // 테이블 새로고침 핸들러
- const handleRefresh = React.useCallback(() => {
- router.refresh()
- }, [router])
-
-
- return (
- <>
- <DataTable
- table={table}
- compact={isCompact}
- // floatingBar={<TechVendorsTableFloatingBar table={table} />}
- >
- <DataTableAdvancedToolbar
- table={table}
- filterFields={advancedFilterFields}
- shallow={false}
- enableCompactToggle={true}
- compactStorageKey="techVendorsTableCompact"
- onCompactChange={handleCompactChange}
- >
- <TechVendorsTableToolbarActions table={table} onRefresh={handleRefresh} />
- </DataTableAdvancedToolbar>
- </DataTable>
- <UpdateVendorSheet
- open={rowAction?.type === "update"}
- onOpenChange={() => setRowAction(null)}
- vendor={rowAction?.row.original ?? null}
- />
- <TechVendorPossibleItemsViewDialog
- open={itemsDialogOpen}
- onOpenChange={setItemsDialogOpen}
- vendor={selectedVendorForItems}
- />
-
- </>
- )
+"use client"
+
+import * as React from "react"
+import { useRouter } from "next/navigation"
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+ DataTableRowAction,
+} from "@/types/table"
+import { cn } from "@/lib/utils"
+import { PanelLeftClose, PanelLeftOpen } from "lucide-react"
+
+import { useDataTable } from "@/hooks/use-data-table"
+import { DataTable } from "@/components/data-table/data-table"
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
+import { Button } from "@/components/ui/button"
+import { getColumns } from "./tech-vendors-table-columns"
+import { getTechVendors, getTechVendorStatusCounts } from "../service"
+import { TechVendor, techVendors, TechVendorWithAttachments } from "@/db/schema/techVendors"
+import { TechVendorsTableToolbarActions } from "./tech-vendors-table-toolbar-actions"
+import { UpdateVendorSheet } from "./update-vendor-sheet"
+import { getVendorStatusIcon } from "../utils"
+import { TechVendorsFilterSheet } from "./tech-vendors-filter-sheet"
+// import { ViewTechVendorLogsDialog } from "./view-tech-vendors-logs-dialog"
+
+// 필터 패널 관련 상수
+const FILTER_PANEL_WIDTH = 400;
+const LAYOUT_HEADER_HEIGHT = 60;
+const LOCAL_HEADER_HEIGHT = 60;
+const FIXED_FILTER_HEIGHT = "calc(100vh - 120px)";
+
+interface TechVendorsTableProps {
+ promises: Promise<
+ [
+ Awaited<ReturnType<typeof getTechVendors>>,
+ Awaited<ReturnType<typeof getTechVendorStatusCounts>>
+ ]
+ >
+ className?: string;
+ calculatedHeight?: string;
+}
+
+export function TechVendorsTable({
+ promises,
+ className,
+ calculatedHeight
+}: TechVendorsTableProps) {
+ // Suspense로 받아온 데이터
+ const [{ data, pageCount }, statusCounts] = React.use(promises)
+ const [isCompact, setIsCompact] = React.useState<boolean>(false)
+
+ const [rowAction, setRowAction] = React.useState<DataTableRowAction<TechVendor> | null>(null)
+
+ // 필터 패널 상태
+ const [isFilterPanelOpen, setIsFilterPanelOpen] = React.useState(false)
+
+ // **router** 획득
+ const router = useRouter()
+
+ // getColumns() 호출 시, router를 주입
+ const columns = React.useMemo(
+ () => getColumns({ setRowAction, router }),
+ [setRowAction, router]
+ )
+
+ // 상태 한글 변환 유틸리티 함수
+ const getStatusDisplay = (status: string): string => {
+ const statusMap: Record<string, string> = {
+ "ACTIVE": "활성 상태",
+ "INACTIVE": "비활성 상태",
+ "BLACKLISTED": "거래 금지",
+ "PENDING_INVITE": "초대 대기",
+ "INVITED": "초대 완료",
+ "QUOTE_COMPARISON": "견적 비교",
+ };
+
+ return statusMap[status] || status;
+ };
+
+ const filterFields: DataTableFilterField<TechVendorWithAttachments>[] = [
+ {
+ id: "status",
+ label: "상태",
+ options: techVendors.status.enumValues.map((status) => ({
+ label: getStatusDisplay(status),
+ value: status,
+ count: statusCounts[status],
+ })),
+ },
+
+ { id: "vendorCode", label: "업체 코드" },
+ ]
+
+ const advancedFilterFields: DataTableAdvancedFilterField<TechVendorWithAttachments>[] = [
+ { id: "vendorName", label: "업체명", type: "text" },
+ { id: "vendorCode", label: "업체코드", type: "text" },
+ { id: "email", label: "이메일", type: "text" },
+ { id: "country", label: "국가", type: "text" },
+ {
+ id: "status",
+ label: "업체승인상태",
+ type: "multi-select",
+ options: techVendors.status.enumValues.map((status) => ({
+ label: getStatusDisplay(status),
+ value: status,
+ count: statusCounts[status],
+ icon: getVendorStatusIcon(status),
+ })),
+ },
+ {
+ id: "techVendorType",
+ label: "벤더 타입",
+ type: "multi-select",
+ options: [
+ { label: "조선", value: "조선" },
+ { label: "해양TOP", value: "해양TOP" },
+ { label: "해양HULL", value: "해양HULL" },
+ ],
+ },
+ {
+ id: "workTypes",
+ label: "Work Type",
+ type: "multi-select",
+ options: [
+ // 조선 workTypes
+ { label: "기장", value: "기장" },
+ { label: "전장", value: "전장" },
+ { label: "선실", value: "선실" },
+ { label: "배관", value: "배관" },
+ { label: "철의", value: "철의" },
+ { label: "선체", value: "선체" },
+ // 해양TOP workTypes
+ { label: "TM", value: "TM" },
+ { label: "TS", value: "TS" },
+ { label: "TE", value: "TE" },
+ { label: "TP", value: "TP" },
+ // 해양HULL workTypes
+ { label: "HA", value: "HA" },
+ { label: "HE", value: "HE" },
+ { label: "HH", value: "HH" },
+ { label: "HM", value: "HM" },
+ { label: "NC", value: "NC" },
+ { label: "HP", value: "HP" },
+ { label: "HO", value: "HO" },
+ ],
+ },
+ { id: "createdAt", label: "등록일", type: "date" },
+ { id: "updatedAt", label: "수정일", type: "date" },
+ ]
+
+ const { table } = useDataTable({
+ data,
+ columns,
+ pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "createdAt", desc: true }],
+ columnPinning: { right: ["actions", "possibleItems"] },
+ },
+ getRowId: (originalRow) => String(originalRow.id),
+ shallow: false,
+ clearOnDefault: true,
+ })
+
+ const handleCompactChange = React.useCallback((compact: boolean) => {
+ setIsCompact(compact)
+ }, [])
+
+ // 테이블 새로고침 핸들러
+ const handleRefresh = React.useCallback(() => {
+ router.refresh()
+ }, [router])
+
+ // 필터 패널 검색 핸들러
+ const handleSearch = React.useCallback(() => {
+ router.refresh()
+ }, [router])
+
+ return (
+ <div
+ className={cn("flex flex-col relative", className)}
+ style={{ height: calculatedHeight }}
+ >
+ {/* Filter Panel */}
+ <div
+ className={cn(
+ "fixed left-0 bg-background border-r z-30 flex flex-col transition-all duration-300 ease-in-out overflow-hidden",
+ isFilterPanelOpen ? "border-r shadow-lg" : "border-r-0"
+ )}
+ style={{
+ width: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px',
+ top: `${LAYOUT_HEADER_HEIGHT*2}px`,
+ height: FIXED_FILTER_HEIGHT
+ }}
+ >
+ {/* Filter Content */}
+ <div className="h-full">
+ <TechVendorsFilterSheet
+ isOpen={isFilterPanelOpen}
+ onClose={() => setIsFilterPanelOpen(false)}
+ onSearch={handleSearch}
+ isLoading={false}
+ />
+ </div>
+ </div>
+
+ {/* Main Content */}
+ <div
+ className="flex flex-col transition-all duration-300 ease-in-out"
+ style={{
+ width: isFilterPanelOpen ? `calc(100% - ${FILTER_PANEL_WIDTH}px)` : '100%',
+ marginLeft: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px',
+ height: '100%'
+ }}
+ >
+ {/* Header Bar - 고정 높이 */}
+ <div
+ className="flex items-center justify-between p-4 bg-background border-b"
+ style={{
+ height: `${LOCAL_HEADER_HEIGHT}px`,
+ flexShrink: 0
+ }}
+ >
+ <div className="flex items-center gap-3">
+ <Button
+ variant="outline"
+ size="sm"
+ type='button'
+ onClick={() => setIsFilterPanelOpen(!isFilterPanelOpen)}
+ className="flex items-center shadow-sm"
+ >
+ {isFilterPanelOpen ? <PanelLeftClose className="size-4"/> : <PanelLeftOpen className="size-4"/>}
+ </Button>
+ </div>
+
+ {/* Right side info
+ <div className="text-sm text-muted-foreground">
+ {data && (
+ <span>총 {data.length || 0}건</span>
+ )}
+ </div> */}
+ </div>
+
+ {/* DataTable */}
+ <div className="flex-1 overflow-hidden">
+ <DataTable
+ table={table}
+ compact={isCompact}
+ // floatingBar={<TechVendorsTableFloatingBar table={table} />}
+ >
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ enableCompactToggle={true}
+ compactStorageKey="techVendorsTableCompact"
+ onCompactChange={handleCompactChange}
+ >
+ <TechVendorsTableToolbarActions
+ table={table}
+ onRefresh={handleRefresh}
+ />
+ </DataTableAdvancedToolbar>
+ </DataTable>
+ </div>
+ </div>
+
+ <UpdateVendorSheet
+ open={rowAction?.type === "update"}
+ onOpenChange={() => setRowAction(null)}
+ vendor={rowAction?.row.original ?? null}
+ />
+ </div>
+ )
} \ No newline at end of file