summaryrefslogtreecommitdiff
path: root/lib/avl/table/vendor-pool-table.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-15 18:59:41 +0900
committerjoonhoekim <26rote@gmail.com>2025-09-15 18:59:41 +0900
commitd5f26d34c4ac6f3eaac16fbc6069de2c2341a6ff (patch)
treead4ecb476a6fd3b754e741e795bd7a3adbbe03ea /lib/avl/table/vendor-pool-table.tsx
parent25b916d040a512cd5248dff319d727ae144d0652 (diff)
parent2b490956c9752c1b756780a3461bc1c37b6fe0a7 (diff)
[Merge] AVL 및 Vendor-Pool 기능 1차 구현
Diffstat (limited to 'lib/avl/table/vendor-pool-table.tsx')
-rw-r--r--lib/avl/table/vendor-pool-table.tsx301
1 files changed, 301 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..7ad9eb56
--- /dev/null
+++ b/lib/avl/table/vendor-pool-table.tsx
@@ -0,0 +1,301 @@
+"use client"
+
+import * as React from "react"
+import { useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel, getFilteredRowModel } from "@tanstack/react-table"
+import { forwardRef, useImperativeHandle } from "react"
+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"
+import { vendorPoolColumns } from "./vendor-pool-table-columns"
+
+// Vendor Pool 데이터 타입 (실제 VendorPool 타입 사용)
+export type VendorPoolItem = VendorPool
+
+// ref를 통해 외부에서 접근할 수 있는 메소드들
+export interface VendorPoolTableRef {
+ getSelectedIds: () => number[]
+}
+
+interface VendorPoolTableProps {
+ onSelectionChange?: (count: number) => void
+ resetCounter?: number
+ reloadTrigger?: number
+}
+
+// 실제 데이터는 API에서 가져옴
+
+export const VendorPoolTable = forwardRef<VendorPoolTableRef, VendorPoolTableProps>(({
+ onSelectionChange,
+ resetCounter,
+ reloadTrigger
+}, ref) => {
+ 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 [pagination, setPagination] = React.useState({
+ pageIndex: 0,
+ pageSize: 10,
+ })
+
+ // 데이터 로드 함수
+ const loadData = React.useCallback(async (searchParams: Partial<GetVendorPoolSchema> = {}) => {
+ try {
+ setLoading(true)
+
+ const params = {
+ page: searchParams.page ?? 1,
+ perPage: searchParams.perPage ?? 10,
+ sort: searchParams.sort ?? [{ id: "registrationDate", desc: true }],
+ flags: [],
+ search: searchText || "",
+ constructionSector: undefined,
+ shipType: undefined,
+ htDivision: undefined,
+ designCategoryCode: undefined,
+ designCategory: undefined,
+ equipBulkDivision: undefined,
+ packageCode: undefined,
+ packageName: undefined,
+ materialGroupCode: undefined,
+ materialGroupName: undefined,
+ vendorCode: undefined,
+ vendorName: undefined,
+ faTarget: undefined,
+ faStatus: undefined,
+ tier: undefined,
+ isAgent: undefined,
+ isBlacklist: undefined,
+ isBcc: undefined,
+ purchaseOpinion: undefined,
+ shipTypeCommon: undefined,
+ shipTypeAmax: undefined,
+ shipTypeSmax: undefined,
+ shipTypeVlcc: undefined,
+ shipTypeLngc: undefined,
+ shipTypeCont: undefined,
+ offshoreTypeCommon: undefined,
+ offshoreTypeFpso: undefined,
+ offshoreTypeFlng: undefined,
+ offshoreTypeFpu: undefined,
+ offshoreTypePlatform: undefined,
+ offshoreTypeWtiv: undefined,
+ offshoreTypeGom: undefined,
+ picName: undefined,
+ picEmail: undefined,
+ picPhone: undefined,
+ agentName: undefined,
+ agentEmail: undefined,
+ agentPhone: undefined,
+ recentQuoteDate: undefined,
+ recentQuoteNumber: undefined,
+ recentOrderDate: undefined,
+ recentOrderNumber: undefined,
+ registrationDate: undefined,
+ registrant: undefined,
+ lastModifiedDate: undefined,
+ lastModifier: undefined,
+ ...searchParams,
+ }
+ console.log('VendorPoolTable - API call params:', params)
+ const result = await getVendorPools(params as GetVendorPoolSchema)
+ console.log('VendorPoolTable - API result:', {
+ dataCount: result.data.length,
+ pageCount: result.pageCount,
+ requestedPage: params.page
+ })
+ 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 {
+ // 검색 시 페이지를 1페이지로 리셋
+ setPagination(prev => ({ ...prev, pageIndex: 0 }))
+ loadData({ page: 1, perPage: pagination.pageSize })
+ }
+ }, [loadData, showAll, pagination.pageSize])
+
+ // 전체보기 토글 핸들러
+ const handleShowAllToggle = React.useCallback((checked: boolean) => {
+ setShowAll(checked)
+ if (checked) {
+ // 전체보기 활성화 시 전체 데이터 로드
+ loadData({ perPage: 1000 })
+ setSearchText("")
+ } else {
+ // 전체보기 비활성화 시 일반 페이징으로 전환
+ loadData({})
+ }
+ }, [loadData])
+
+ // 초기 데이터 로드
+ React.useEffect(() => {
+ // 초기 로드 시 페이지를 1페이지로 설정
+ setPagination(prev => ({ ...prev, pageIndex: 0 }))
+ loadData({ page: 1, perPage: pagination.pageSize })
+ }, [pagination.pageSize])
+
+ // reloadTrigger가 변경될 때마다 데이터 리로드
+ React.useEffect(() => {
+ if (reloadTrigger && reloadTrigger > 0) {
+ console.log('VendorPoolTable - reloadTrigger changed, reloading data')
+ loadData({})
+ }
+ }, [reloadTrigger, loadData])
+
+ const table = useReactTable({
+ data,
+ columns: vendorPoolColumns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ manualPagination: !showAll, // 전체보기 시에는 수동 페이징 비활성화
+ pageCount: showAll ? 1 : pageCount, // 전체보기 시 1페이지, 일반 모드에서는 API에서 받은 pageCount 사용
+ state: {
+ pagination: showAll ? { pageIndex: 0, pageSize: data.length } : pagination,
+ },
+ onPaginationChange: (updater) => {
+ if (!showAll) {
+ // 전체보기가 아닐 때만 페이징 변경 처리
+ const newPaginationState = typeof updater === 'function' ? updater(pagination) : updater
+
+ console.log('VendorPoolTable - Pagination changed:', {
+ currentState: pagination,
+ newPaginationState,
+ showAll,
+ willLoadData: !showAll
+ })
+
+ setPagination(newPaginationState)
+
+ const apiParams = {
+ page: newPaginationState.pageIndex + 1,
+ perPage: newPaginationState.pageSize,
+ }
+ console.log('VendorPoolTable - Loading data with params:', apiParams)
+ loadData(apiParams)
+ }
+ },
+ })
+
+ // 외부에서 선택된 ID들을 가져올 수 있도록 ref에 메소드 노출
+ useImperativeHandle(ref, () => ({
+ getSelectedIds: () => {
+ const selectedRows = table.getFilteredSelectedRowModel().rows
+ return selectedRows.map(row => row.original.id)
+ }
+ }))
+
+ // 선택된 행 개수
+ const selectedRowCount = table.getFilteredSelectedRowModel().rows.length
+
+ // 선택 상태 변경 시 콜백 호출
+ React.useEffect(() => {
+ onSelectionChange?.(selectedRowCount)
+ }, [selectedRowCount, 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>
+ )
+})
+
+VendorPoolTable.displayName = "VendorPoolTable"