summaryrefslogtreecommitdiff
path: root/components/po-rfq/po-rfq-container.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/po-rfq/po-rfq-container.tsx')
-rw-r--r--components/po-rfq/po-rfq-container.tsx261
1 files changed, 261 insertions, 0 deletions
diff --git a/components/po-rfq/po-rfq-container.tsx b/components/po-rfq/po-rfq-container.tsx
new file mode 100644
index 00000000..e5159242
--- /dev/null
+++ b/components/po-rfq/po-rfq-container.tsx
@@ -0,0 +1,261 @@
+"use client"
+
+import { useState, useEffect, useCallback, useRef } from "react"
+import { useSearchParams, useRouter } from "next/navigation"
+import { Button } from "@/components/ui/button"
+import { PanelLeft, PanelLeftClose, PanelLeftOpen } from "lucide-react"
+
+// shadcn/ui components
+import {
+ ResizablePanelGroup,
+ ResizablePanel,
+ ResizableHandle,
+} from "@/components/ui/resizable"
+
+import { cn } from "@/lib/utils"
+import { ProcurementRfqsView } from "@/db/schema"
+import { RFQListTable } from "@/lib/procurement-rfqs/table/rfq-table"
+import { getPORfqs } from "@/lib/procurement-rfqs/services"
+import { RFQFilterSheet } from "./rfq-filter-sheet"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { RfqDetailTables } from "./detail-table/rfq-detail-table"
+
+interface RfqContainerProps {
+ // 초기 데이터 (필수)
+ initialData: Awaited<ReturnType<typeof getPORfqs>>
+ // 서버 액션으로 데이터를 가져오는 함수
+ fetchData: (params: any) => Promise<Awaited<ReturnType<typeof getPORfqs>>>
+}
+
+export default function RFQContainer({
+ initialData,
+ fetchData
+}: RfqContainerProps) {
+ const router = useRouter()
+ const searchParams = useSearchParams()
+
+ // Whether the filter panel is open (now a side panel instead of sheet)
+ const [isFilterPanelOpen, setIsFilterPanelOpen] = useState(false)
+
+ // 데이터 상태 관리 - 초기 데이터로 시작
+ const [data, setData] = useState<Awaited<ReturnType<typeof getPORfqs>>>(initialData)
+ const [isLoading, setIsLoading] = useState(false)
+
+ // 선택된 문서를 이 state로 관리
+ const [selectedRfq, setSelectedRfq] = useState<ProcurementRfqsView | null>(null)
+
+ // 패널 collapse
+ const [isTopCollapsed, setIsTopCollapsed] = useState(false)
+
+ // 이전 URL 파라미터를 저장하기 위한 ref
+ const prevParamsRef = useRef<string>(searchParams.toString())
+
+ // 현재 URL 파라미터로부터 필터 데이터 구성
+ const getFilterParams = useCallback(() => {
+ return {
+ page: searchParams.get('page') || '1',
+ perPage: searchParams.get('perPage') || '10',
+ sort: searchParams.get('sort') || JSON.stringify([{ id: "updatedAt", desc: true }]),
+ basicFilters: searchParams.get('basicFilters') || null,
+ basicJoinOperator: searchParams.get('basicJoinOperator') || 'and',
+ filters: searchParams.get('filters') || null,
+ joinOperator: searchParams.get('joinOperator') || 'and',
+ search: searchParams.get('search') || '',
+ }
+ }, [searchParams])
+
+ // 데이터 로드 함수
+ const loadData = useCallback(async () => {
+ try {
+ setIsLoading(true)
+ const filterParams = getFilterParams()
+
+ console.log("데이터 로드 시작:", filterParams)
+
+ // 서버 액션으로 데이터 가져오는 함수
+ const newData = await fetchData(filterParams)
+
+ console.log("데이터 로드 완료:", newData.data.length, "건")
+
+ setData(newData)
+ } catch (error) {
+ console.error("데이터 로드 오류:", error)
+ } finally {
+ setIsLoading(false)
+ }
+ }, [fetchData, getFilterParams])
+
+ const refreshData = useCallback(() => {
+ // 현재 파라미터로 데이터 다시 로드
+ loadData();
+ }, [loadData]);
+
+ // URL 파라미터 변경 감지
+ useEffect(() => {
+ const currentParams = searchParams.toString()
+
+ // 파라미터가 변경되었을 때만 데이터 로드
+ if (currentParams !== prevParamsRef.current) {
+ console.log("URL 파라미터 변경 감지:", {
+ previous: prevParamsRef.current,
+ current: currentParams,
+ })
+
+ prevParamsRef.current = currentParams
+ loadData()
+ }
+ }, [searchParams, loadData])
+
+ // 문서 선택 핸들러
+ const handleSelectRfq = (rfq: ProcurementRfqsView | null) => {
+ setSelectedRfq(rfq)
+ }
+
+ // 조회 버튼 클릭 핸들러 - RFQFilterSheet에 전달
+ // 페이지 리라우팅을 통해 처리하므로 별도 로직 불필요
+ const handleSearch = () => {
+ // Close the panel after search
+ setIsFilterPanelOpen(false)
+ }
+
+ const [panelHeight, setPanelHeight] = useState<number>(400)
+
+ // Get active filter count for UI display
+ const getActiveFilterCount = () => {
+ try {
+ const basicFilters = searchParams.get('basicFilters')
+ return basicFilters ? JSON.parse(basicFilters).length : 0
+ } catch (e) {
+ console.error("Error parsing filters:", e)
+ return 0
+ }
+ }
+
+ // Filter panel width in pixels
+ const FILTER_PANEL_WIDTH = 400;
+
+ // Table refresh key - 패널 상태가 변경되면 테이블을 강제로 재렌더링
+ const [tableRefreshKey, setTableRefreshKey] = useState(0);
+
+ useEffect(() => {
+ // 패널 상태가 변경될 때 테이블 강제 재렌더링
+ setTableRefreshKey(prev => prev + 1);
+ }, [isFilterPanelOpen]);
+
+ return (
+ <div className="h-[calc(100vh-220px)] w-full overflow-hidden relative">
+ {/* Fixed Filter Panel - 가장 왼쪽부터 시작, 전체 높이 맞춤 */}
+ <div
+ className={cn(
+ "fixed left-0 bg-background border-r overflow-hidden flex flex-col transition-all duration-300 ease-in-out z-30",
+ isFilterPanelOpen ? "border-r" : "border-r-0"
+ )}
+ style={{
+ width: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px',
+ height: 'calc(100vh - 130px)' // 나머지 높이 전체 사용
+ }}
+ >
+ {/* Filter Content - 제목 포함하여 내부에서 처리 */}
+ <div className="h-full">
+ <RFQFilterSheet
+ isOpen={isFilterPanelOpen}
+ onClose={() => setIsFilterPanelOpen(false)}
+ onSearch={handleSearch}
+ isLoading={isLoading}
+ />
+ </div>
+ </div>
+
+ {/* Main Content Panel - 패널이 열릴 때 오른쪽으로 이동 */}
+ <div
+ className="h-full overflow-hidden transition-all duration-300 ease-in-out"
+ style={{
+ marginLeft: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px'
+ }}
+ >
+ {/* Filter Toggle Button - 메인 콘텐츠 상단에 위치 */}
+ <div className="flex items-center p-4 border-b bg-background pl-0">
+ <Button
+ variant="outline"
+ size="sm"
+ type='button'
+ onClick={() => setIsFilterPanelOpen(!isFilterPanelOpen)}
+ className="flex items-center shadow-md"
+ >
+ {
+ isFilterPanelOpen ? <PanelLeftClose className="size-4"/> : <PanelLeftOpen className="size-4"/>
+ }
+ {/* 검색 필터 */}
+ {getActiveFilterCount() > 0 && (
+ <span className="ml-2 bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
+ {getActiveFilterCount()}
+ </span>
+ )}
+ </Button>
+
+ {/* 추가적인 헤더 정보나 버튼들을 여기에 배치할 수 있음 */}
+ <div className="flex-1" />
+ <div className="text-sm text-muted-foreground">
+ {data && !isLoading && (
+ <span>총 {data.total || 0}건</span>
+ )}
+ </div>
+ </div>
+
+ {/* Main Content Area */}
+ <div className="h-[calc(100%-64px)] w-full overflow-hidden">
+ {isLoading ? (
+ // 로딩 중 상태
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ ) : (
+ // 데이터 로드 완료 상태
+ <ResizablePanelGroup direction="vertical" className="h-full">
+ <ResizablePanel
+ defaultSize={55}
+ minSize={0}
+ maxSize={95}
+ collapsible
+ collapsedSize={10}
+ onCollapse={() => setIsTopCollapsed(true)}
+ onExpand={() => setIsTopCollapsed(false)}
+ onResize={(size) => {
+ setPanelHeight(size)
+ }}
+ className={cn("overflow-y-auto overflow-x-hidden border-b", isTopCollapsed && "transition-all")}
+ >
+ <div className="flex h-full min-h-0 flex-col">
+ <RFQListTable
+ key={tableRefreshKey} // Force re-render when panel toggles
+ maxHeight={`${panelHeight*0.5}vh`}
+ data={data}
+ onSelectRFQ={handleSelectRfq}
+ onDataRefresh={refreshData}
+ />
+ </div>
+ </ResizablePanel>
+
+ <ResizableHandle
+ withHandle
+ className="pointer-events-none data-[resize-handle]:pointer-events-auto"
+ />
+
+ <ResizablePanel
+ minSize={0}
+ defaultSize={35}
+ className="overflow-y-auto overflow-x-hidden"
+ >
+ <RfqDetailTables selectedRfq={selectedRfq} />
+ </ResizablePanel>
+ </ResizablePanelGroup>
+ )}
+ </div>
+ </div>
+ </div>
+ )
+} \ No newline at end of file