diff options
| author | joonhoekim <26rote@gmail.com> | 2025-09-15 01:23:00 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-09-15 01:23:00 +0000 |
| commit | e7818a457371849e29519497ebf046f385f05ab6 (patch) | |
| tree | 9bf08ba1b31a512c481dc521c9dd7c90091a75b8 /lib/avl/table/vendor-pool-table.tsx | |
| parent | 3f293c90beb58ce206a66ff444d7acfc41b56429 (diff) | |
(김준회) AVL 기능 구현 1차 및 벤더풀 E/B 구분 개선
Diffstat (limited to 'lib/avl/table/vendor-pool-table.tsx')
| -rw-r--r-- | lib/avl/table/vendor-pool-table.tsx | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/lib/avl/table/vendor-pool-table.tsx b/lib/avl/table/vendor-pool-table.tsx new file mode 100644 index 00000000..1a0a5fca --- /dev/null +++ b/lib/avl/table/vendor-pool-table.tsx @@ -0,0 +1,290 @@ +"use client" + +import * as React from "react" +import { useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel, getFilteredRowModel, type ColumnDef } from "@tanstack/react-table" +import { DataTable } from "@/components/data-table/data-table" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { Input } from "@/components/ui/input" +import { Search } from "lucide-react" +import { getVendorPools } from "../../vendor-pool/service" +import { GetVendorPoolSchema } from "../../vendor-pool/validations" +import { VendorPool } from "../../vendor-pool/types" + +// Vendor Pool 데이터 타입 (실제 VendorPool 타입 사용) +export type VendorPoolItem = VendorPool + +interface VendorPoolTableProps { + onSelectionChange?: (count: number) => void + resetCounter?: number +} + +// Vendor Pool 테이블 컬럼 +const vendorPoolColumns: ColumnDef<VendorPoolItem>[] = [ + { + id: "select", + header: ({ table }) => ( + <Checkbox + checked={ + table.getIsAllPageRowsSelected() || + (table.getIsSomePageRowsSelected() && "indeterminate") + } + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row, table }) => { + // Vendor Pool 테이블의 단일 선택 핸들러 + const handleRowSelection = (checked: boolean) => { + if (checked) { + // 다른 모든 행의 선택 해제 + table.getRowModel().rows.forEach(r => { + if (r !== row && r.getIsSelected()) { + r.toggleSelected(false) + } + }) + } + // 현재 행 선택/해제 + row.toggleSelected(checked) + } + + return ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={handleRowSelection} + aria-label="Select row" + /> + ) + }, + enableSorting: false, + enableHiding: false, + size: 50, + }, + { + accessorKey: "no", + header: "No.", + size: 60, + }, + { + accessorKey: "designCategory", + header: "설계공종", + size: 120, + }, + { + accessorKey: "avlVendorName", + header: "AVL 등재업체명", + size: 140, + }, + { + accessorKey: "materialGroupCode", + header: "자재그룹코드", + size: 130, + }, + { + accessorKey: "materialGroupName", + header: "자재그룹명", + size: 130, + }, + { + accessorKey: "vendorName", + header: "협력업체 정보", + size: 130, + }, + { + accessorKey: "tier", + header: "업체분류", + size: 100, + }, + { + accessorKey: "faStatus", + header: "FA현황", + size: 100, + }, + { + accessorKey: "recentQuoteNumber", + header: "최근견적번호", + size: 130, + }, + { + accessorKey: "recentOrderNumber", + header: "최근발주번호", + size: 130, + }, +] + +// 실제 데이터는 API에서 가져옴 + +export function VendorPoolTable({ + onSelectionChange, + resetCounter +}: VendorPoolTableProps) { + const [data, setData] = React.useState<VendorPoolItem[]>([]) + const [loading, setLoading] = React.useState(false) + const [pageCount, setPageCount] = React.useState(0) + + // 검색 상태 + const [searchText, setSearchText] = React.useState("") + const [showAll, setShowAll] = React.useState(false) + + // 데이터 로드 함수 + const loadData = React.useCallback(async (searchParams: Partial<GetVendorPoolSchema>) => { + try { + setLoading(true) + const params: GetVendorPoolSchema = { + page: searchParams.page ?? 1, + perPage: searchParams.perPage ?? 10, + sort: searchParams.sort ?? [{ id: "registrationDate", desc: true }], + search: searchText || "", + ...searchParams, + } + const result = await getVendorPools(params) + setData(result.data) + setPageCount(result.pageCount) + } catch (error) { + console.error("Vendor Pool 데이터 로드 실패:", error) + setData([]) + setPageCount(0) + } finally { + setLoading(false) + } + }, [searchText]) + + // 검색 핸들러 + const handleSearch = React.useCallback(() => { + if (showAll) { + // 전체보기 모드에서는 페이징 없이 전체 데이터 로드 + loadData({ perPage: 1000 }) // 충분히 큰 숫자로 전체 데이터 가져오기 + } else { + loadData({}) + } + }, [loadData, showAll]) + + // 전체보기 토글 핸들러 + const handleShowAllToggle = React.useCallback((checked: boolean) => { + setShowAll(checked) + if (checked) { + // 전체보기 활성화 시 전체 데이터 로드 + loadData({ perPage: 1000 }) + setSearchText("") + } else { + // 전체보기 비활성화 시 일반 페이징으로 전환 + loadData({}) + } + }, [loadData]) + + // 초기 데이터 로드 + React.useEffect(() => { + loadData({}) + }, [loadData]) + + const table = useReactTable({ + data, + columns: vendorPoolColumns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + manualPagination: !showAll, // 전체보기 시에는 수동 페이징 비활성화 + pageCount: showAll ? 1 : pageCount, // 전체보기 시 1페이지, 일반 모드에서는 API에서 받은 pageCount 사용 + initialState: { + pagination: { + pageSize: showAll ? data.length : 10, // 전체보기 시 모든 데이터 표시 + }, + }, + onPaginationChange: (updater) => { + if (!showAll) { + // 전체보기가 아닐 때만 페이징 변경 처리 + const newState = typeof updater === 'function' ? updater(table.getState().pagination) : updater + loadData({ + page: newState.pageIndex + 1, + perPage: newState.pageSize, + }) + } + }, + }) + + // 선택 상태 변경 시 콜백 호출 + React.useEffect(() => { + onSelectionChange?.(table.getFilteredSelectedRowModel().rows.length) + }, [table.getFilteredSelectedRowModel().rows.length, onSelectionChange]) + + // 선택 해제 요청이 오면 모든 선택 해제 + React.useEffect(() => { + if (resetCounter && resetCounter > 0) { + table.toggleAllPageRowsSelected(false) + } + }, [resetCounter, table]) + + return ( + <div className="h-full flex flex-col"> + <div className="mb-2"> + <div className="flex items-center justify-between mb-2"> + <h4 className="font-medium">Vendor Pool</h4> + <div className="flex gap-1"> + {/* <Button variant="outline" size="sm"> + 신규업체 추가 + </Button> */} + </div> + </div> + </div> + + {/* 검색 UI */} + <div className="mb-4 p-4 border rounded-lg bg-muted/50"> + <div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center"> + {/* 전체보기 체크박스 */} + <div className="flex items-center space-x-2"> + <Checkbox + id="showAll" + checked={showAll} + onCheckedChange={handleShowAllToggle} + /> + <label + htmlFor="showAll" + className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" + > + 전체보기 + </label> + </div> + + {/* 검색어 입력 */} + {!showAll && ( + <div className="flex gap-2 flex-1 max-w-md"> + <Input + placeholder="설계공종, 업체명, 자재그룹 등으로 검색..." + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + className="flex-1" + onKeyPress={(e) => { + if (e.key === 'Enter') { + handleSearch() + } + }} + /> + <Button + onClick={handleSearch} + disabled={loading} + size="sm" + className="px-3" + > + <Search className="w-4 h-4" /> + </Button> + </div> + )} + + {/* 검색 결과 정보 */} + <div className="text-sm text-muted-foreground"> + {showAll ? ( + `전체 ${data.length}개 항목 표시 중` + ) : ( + `${data.length}개 항목${searchText ? ` (검색어: "${searchText}")` : ""}` + )} + </div> + </div> + </div> + + <div className="flex-1"> + <DataTable table={table} /> + </div> + </div> + ) +} |
