summaryrefslogtreecommitdiff
path: root/components/po-rfq/po-rfq-container.tsx
blob: e5159242eaf32a969bc97c9a6be04ac210905e7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
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>
  )
}