From cbf85768970ed0e10b5cd8d0b6f82e284fd450b8 Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 3 Nov 2025 13:51:08 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20DataTableTemplate=EC=97=90=20=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=9C=84=EC=A0=AF=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=80=EC=83=89=20=ED=95=84=ED=84=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ 새로운 기능 - 플로우 위젯과 동일한 검색 필터 설정 기능 구현 - 사용자가 원하는 컬럼만 선택하여 검색 가능 - localStorage 기반 필터 설정 저장/복원 🎨 UI 추가 - '검색 필터 설정' 버튼 (FlowWidget 스타일) - 선택된 컬럼의 동적 검색 입력 필드 - 필터 개수 뱃지 표시 - 체크박스 기반 필터 설정 다이얼로그 🔧 기술적 구현 - searchFilterColumns 상태로 선택된 컬럼 관리 - searchValues 상태로 각 컬럼별 검색값 관리 - useAuth 훅으로 사용자별 필터 설정 저장 - Grid 레이아웃으로 검색 필드 반응형 배치 📝 변경된 파일 - frontend/components/screen/templates/DataTableTemplate.tsx ✅ 테스트 완료 - 필터 설정 저장/복원 - 동적 검색 필드 생성 - 반응형 레이아웃 - 미리보기 모드에서 비활성화 --- .../screen/templates/DataTableTemplate.tsx | 154 +++++++++++++++++- 1 file changed, 147 insertions(+), 7 deletions(-) diff --git a/frontend/components/screen/templates/DataTableTemplate.tsx b/frontend/components/screen/templates/DataTableTemplate.tsx index 86422e7f..b24f27c3 100644 --- a/frontend/components/screen/templates/DataTableTemplate.tsx +++ b/frontend/components/screen/templates/DataTableTemplate.tsx @@ -1,6 +1,6 @@ "use client"; -import React from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { Table, Filter, Search, Download, RefreshCw, Plus, Edit, Trash2 } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -8,6 +8,9 @@ import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { toast } from "sonner"; +import { useAuth } from "@/hooks/useAuth"; /** * 데이터 테이블 템플릿 컴포넌트 @@ -121,6 +124,13 @@ export const DataTableTemplate: React.FC = ({ className = "", isPreview = true, }) => { + const { user } = useAuth(); + + // 🆕 검색 필터 관련 상태 + const [searchFilterColumns, setSearchFilterColumns] = useState>(new Set()); + const [isFilterSettingOpen, setIsFilterSettingOpen] = useState(false); + const [searchValues, setSearchValues] = useState>({}); + // 설정된 컬럼만 사용 (자동 생성 안함) const defaultColumns = React.useMemo(() => { return columns || []; @@ -138,6 +148,54 @@ export const DataTableTemplate: React.FC = ({ }, [isPreview]); const visibleColumns = defaultColumns.filter((col) => col.visible); + + // 🆕 컬럼명 -> 라벨 매핑 + const columnLabels = React.useMemo(() => { + const labels: Record = {}; + defaultColumns.forEach(col => { + labels[col.id] = col.label; + }); + return labels; + }, [defaultColumns]); + + // 🆕 localStorage에서 필터 설정 복원 + useEffect(() => { + if (user?.userId && title) { + const storageKey = `datatable-search-filter-${user.userId}-${title}`; + const savedFilter = localStorage.getItem(storageKey); + if (savedFilter) { + try { + const parsed = JSON.parse(savedFilter); + setSearchFilterColumns(new Set(parsed)); + } catch (e) { + console.error("필터 설정 복원 실패:", e); + } + } + } + }, [user?.userId, title]); + + // 🆕 필터 저장 함수 + const handleSaveSearchFilter = useCallback(() => { + if (user?.userId && title) { + const storageKey = `datatable-search-filter-${user.userId}-${title}`; + const filterArray = Array.from(searchFilterColumns); + localStorage.setItem(storageKey, JSON.stringify(filterArray)); + toast.success("검색 필터 설정이 저장되었습니다."); + } + }, [user?.userId, title, searchFilterColumns]); + + // 🆕 필터 토글 함수 + const handleToggleFilterColumn = useCallback((columnId: string) => { + setSearchFilterColumns((prev) => { + const newSet = new Set(prev); + if (newSet.has(columnId)) { + newSet.delete(columnId); + } else { + newSet.add(columnId); + } + return newSet; + }); + }, []); return ( @@ -178,23 +236,65 @@ export const DataTableTemplate: React.FC = ({ + {/* 🆕 검색 필터 설정 버튼 영역 */} + {defaultColumns.length > 0 && ( +
+ +
+ )} + + {/* 🆕 선택된 컬럼의 검색 입력 필드 */} + {searchFilterColumns.size > 0 && ( +
+ {Array.from(searchFilterColumns).map((columnId) => { + const column = defaultColumns.find(col => col.id === columnId); + if (!column) return null; + + return ( +
+ + setSearchValues(prev => ({...prev, [columnId]: e.target.value}))} + disabled={isPreview} + className="h-9 text-sm" + /> +
+ ); + })} +
+ )} + {/* 검색 및 필터 영역 */}
{/* 검색 입력 */}
-
- - -
- {actions.showSearchButton && ( )}
- {/* 필터 영역 */} + {/* 기존 필터 영역 (이제는 사용하지 않음) */} {filters.length > 0 && (
@@ -352,6 +452,46 @@ export const DataTableTemplate: React.FC = ({
)} + + {/* 🆕 검색 필터 설정 다이얼로그 */} + + + + 검색 필터 설정 + + 표시할 검색 필터를 선택하세요. 선택하지 않은 필터는 숨겨집니다. + + + +
+ {defaultColumns.map((column) => ( +
+ handleToggleFilterColumn(column.id)} + /> + +
+ ))} +
+ + + + + +
+
); };