summaryrefslogtreecommitdiff
path: root/lib/avl/table/vendor-pool-table.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-15 01:23:00 +0000
committerjoonhoekim <26rote@gmail.com>2025-09-15 01:23:00 +0000
commite7818a457371849e29519497ebf046f385f05ab6 (patch)
tree9bf08ba1b31a512c481dc521c9dd7c90091a75b8 /lib/avl/table/vendor-pool-table.tsx
parent3f293c90beb58ce206a66ff444d7acfc41b56429 (diff)
(김준회) AVL 기능 구현 1차 및 벤더풀 E/B 구분 개선
Diffstat (limited to 'lib/avl/table/vendor-pool-table.tsx')
-rw-r--r--lib/avl/table/vendor-pool-table.tsx290
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>
+ )
+}