diff --git a/frontend/components/report/designer/ReportPreviewModal.tsx b/frontend/components/report/designer/ReportPreviewModal.tsx index 92a9c7a6..97b3ac48 100644 --- a/frontend/components/report/designer/ReportPreviewModal.tsx +++ b/frontend/components/report/designer/ReportPreviewModal.tsx @@ -3,11 +3,11 @@ import { Dialog, DialogContent, - - + DialogDescription, + DialogFooter, DialogHeader, - -} from "@/components/ui/resizable-dialog"; + DialogTitle, +} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Printer, FileDown, FileText } from "lucide-react"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; @@ -895,7 +895,7 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) - + 닫기 @@ -911,7 +911,7 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) {isExporting ? "생성 중..." : "WORD"} - + ); diff --git a/frontend/components/report/designer/SaveAsTemplateModal.tsx b/frontend/components/report/designer/SaveAsTemplateModal.tsx index d2521b98..7b471bb8 100644 --- a/frontend/components/report/designer/SaveAsTemplateModal.tsx +++ b/frontend/components/report/designer/SaveAsTemplateModal.tsx @@ -4,11 +4,11 @@ import { useState } from "react"; import { Dialog, DialogContent, - - + DialogDescription, + DialogFooter, DialogHeader, - -} from "@/components/ui/resizable-dialog"; + DialogTitle, +} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -131,7 +131,7 @@ export function SaveAsTemplateModal({ isOpen, onClose, onSave }: SaveAsTemplateM - + 취소 @@ -145,7 +145,7 @@ export function SaveAsTemplateModal({ isOpen, onClose, onSave }: SaveAsTemplateM "저장" )} - + ); diff --git a/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx b/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx index 34b3044c..01906c21 100644 --- a/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx +++ b/frontend/lib/registry/components/table-search-widget/TableSearchWidget.tsx @@ -10,13 +10,7 @@ import { ColumnVisibilityPanel } from "@/components/screen/table-options/ColumnV import { FilterPanel } from "@/components/screen/table-options/FilterPanel"; import { GroupingPanel } from "@/components/screen/table-options/GroupingPanel"; import { TableFilter } from "@/types/table-options"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; interface TableSearchWidgetProps { component: { @@ -39,9 +33,11 @@ interface TableSearchWidgetProps { export function TableSearchWidget({ component, screenId, onHeightChange }: TableSearchWidgetProps) { const { registeredTables, selectedTableId, setSelectedTableId, getTable } = useTableOptions(); - + // 높이 관리 context (실제 화면에서만 사용) - let setWidgetHeight: ((screenId: number, componentId: string, height: number, originalHeight: number) => void) | undefined; + let setWidgetHeight: + | ((screenId: number, componentId: string, height: number, originalHeight: number) => void) + | undefined; try { const heightContext = useTableSearchWidgetHeight(); setWidgetHeight = heightContext.setWidgetHeight; @@ -49,11 +45,11 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table // Context가 없으면 (디자이너 모드) 무시 setWidgetHeight = undefined; } - + const [columnVisibilityOpen, setColumnVisibilityOpen] = useState(false); const [filterOpen, setFilterOpen] = useState(false); const [groupingOpen, setGroupingOpen] = useState(false); - + // 활성화된 필터 목록 const [activeFilters, setActiveFilters] = useState([]); const [filterValues, setFilterValues] = useState>({}); @@ -61,7 +57,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table const [selectOptions, setSelectOptions] = useState>>({}); // 선택된 값의 라벨 저장 (데이터 없을 때도 라벨 유지) const [selectedLabels, setSelectedLabels] = useState>({}); - + // 높이 감지를 위한 ref const containerRef = useRef(null); @@ -75,16 +71,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table // 첫 번째 테이블 자동 선택 useEffect(() => { const tables = Array.from(registeredTables.values()); - - console.log("🔍 [TableSearchWidget] 테이블 감지:", { - tablesCount: tables.length, - tableIds: tables.map(t => t.tableId), - selectedTableId, - autoSelectFirstTable, - }); - + if (autoSelectFirstTable && tables.length > 0 && !selectedTableId) { - console.log("✅ [TableSearchWidget] 첫 번째 테이블 자동 선택:", tables[0].tableId); setSelectedTableId(tables[0].tableId); } }, [registeredTables, selectedTableId, autoSelectFirstTable, setSelectedTableId]); @@ -94,7 +82,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table if (currentTable?.tableName) { const storageKey = `table_filters_${currentTable.tableName}`; const savedFilters = localStorage.getItem(storageKey); - + if (savedFilters) { try { const parsed = JSON.parse(savedFilters) as Array<{ @@ -105,7 +93,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table filterType: "text" | "number" | "date" | "select"; width?: number; }>; - + // enabled된 필터들만 activeFilters로 설정 const activeFiltersList: TableFilter[] = parsed .filter((f) => f.enabled) @@ -116,7 +104,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table filterType: f.filterType, width: f.width || 200, // 저장된 너비 포함 })); - + setActiveFilters(activeFiltersList); } catch (error) { console.error("저장된 필터 불러오기 실패:", error); @@ -132,20 +120,20 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table } const loadSelectOptions = async () => { - const selectFilters = activeFilters.filter(f => f.filterType === "select"); - + const selectFilters = activeFilters.filter((f) => f.filterType === "select"); + if (selectFilters.length === 0) { return; } const newOptions: Record> = { ...selectOptions }; - + for (const filter of selectFilters) { // 이미 로드된 옵션이 있으면 스킵 (초기값 유지) if (newOptions[filter.columnName] && newOptions[filter.columnName].length > 0) { continue; } - + try { const options = await currentTable.getColumnUniqueValues(filter.columnName); newOptions[filter.columnName] = options; @@ -155,31 +143,30 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table } setSelectOptions(newOptions); }; - + loadSelectOptions(); }, [activeFilters, currentTable?.tableName, currentTable?.getColumnUniqueValues]); // dataCount 제거, tableName으로 변경 - // 높이 변화 감지 및 알림 (실제 화면에서만) useEffect(() => { if (!containerRef.current || !screenId || !setWidgetHeight) return; - + // 컴포넌트의 원래 높이 (디자이너에서 설정한 높이) const originalHeight = (component as any).size?.height || 50; const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { const newHeight = entry.contentRect.height; - + // Context에 높이 저장 (다른 컴포넌트 위치 조정에 사용) setWidgetHeight(screenId, component.id, newHeight, originalHeight); - + // localStorage에 높이 저장 (새로고침 시 복원용) localStorage.setItem( `table_search_widget_height_screen_${screenId}_${component.id}`, - JSON.stringify({ height: newHeight, originalHeight }) + JSON.stringify({ height: newHeight, originalHeight }), ); - + // 콜백이 있으면 호출 if (onHeightChange) { onHeightChange(newHeight); @@ -197,10 +184,10 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table // 화면 로딩 시 저장된 높이 복원 useEffect(() => { if (!screenId || !setWidgetHeight) return; - + const storageKey = `table_search_widget_height_screen_${screenId}_${component.id}`; const savedData = localStorage.getItem(storageKey); - + if (savedData) { try { const { height, originalHeight } = JSON.parse(savedData); @@ -219,9 +206,9 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table ...filterValues, [columnName]: value, }; - + setFilterValues(newValues); - + // 실시간 검색: 값 변경 시 즉시 필터 적용 applyFilters(newValues); }; @@ -229,10 +216,12 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table // 필터 적용 함수 const applyFilters = (values: Record = filterValues) => { // 빈 값이 아닌 필터만 적용 - const filtersWithValues = activeFilters.map((filter) => ({ - ...filter, - value: values[filter.columnName] || "", - })).filter((f) => f.value !== ""); + const filtersWithValues = activeFilters + .map((filter) => ({ + ...filter, + value: values[filter.columnName] || "", + })) + .filter((f) => f.value !== ""); currentTable?.onFilterChange(filtersWithValues); }; @@ -257,8 +246,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table type="date" value={value} onChange={(e) => handleFilterChange(filter.columnName, e.target.value)} - className="h-9 text-xs sm:text-sm focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0" - style={{ width: `${width}px`, height: '36px', minHeight: '36px', outline: 'none', boxShadow: 'none' }} + className="h-9 text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none sm:text-sm" + style={{ width: `${width}px`, height: "36px", minHeight: "36px", outline: "none", boxShadow: "none" }} placeholder={column?.columnLabel} /> ); @@ -269,37 +258,40 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table type="number" value={value} onChange={(e) => handleFilterChange(filter.columnName, e.target.value)} - className="h-9 text-xs sm:text-sm focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0" - style={{ width: `${width}px`, height: '36px', minHeight: '36px', outline: 'none', boxShadow: 'none' }} + className="h-9 text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none sm:text-sm" + style={{ width: `${width}px`, height: "36px", minHeight: "36px", outline: "none", boxShadow: "none" }} placeholder={column?.columnLabel} /> ); case "select": { let options = selectOptions[filter.columnName] || []; - + // 현재 선택된 값이 옵션 목록에 없으면 추가 (데이터 없을 때도 선택값 유지) - if (value && !options.find(opt => opt.value === value)) { + if (value && !options.find((opt) => opt.value === value)) { const savedLabel = selectedLabels[filter.columnName] || value; options = [{ value, label: savedLabel }, ...options]; } - + // 중복 제거 (value 기준) - const uniqueOptions = options.reduce((acc, option) => { - if (!acc.find(opt => opt.value === option.value)) { - acc.push(option); - } - return acc; - }, [] as Array<{ value: string; label: string }>); - + const uniqueOptions = options.reduce( + (acc, option) => { + if (!acc.find((opt) => opt.value === option.value)) { + acc.push(option); + } + return acc; + }, + [] as Array<{ value: string; label: string }>, + ); + return ( { // 선택한 값의 라벨 저장 - const selectedOption = uniqueOptions.find(opt => opt.value === val); + const selectedOption = uniqueOptions.find((opt) => opt.value === val); if (selectedOption) { - setSelectedLabels(prev => ({ + setSelectedLabels((prev) => ({ ...prev, [filter.columnName]: selectedOption.label, })); @@ -307,17 +299,15 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table handleFilterChange(filter.columnName, val); }} > - {uniqueOptions.length === 0 ? ( - - 옵션 없음 - + 옵션 없음 ) : ( uniqueOptions.map((option, index) => ( @@ -336,8 +326,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table type="text" value={value} onChange={(e) => handleFilterChange(filter.columnName, e.target.value)} - className="h-9 text-xs sm:text-sm focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0" - style={{ width: `${width}px`, height: '36px', minHeight: '36px', outline: 'none', boxShadow: 'none' }} + className="h-9 text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none sm:text-sm" + style={{ width: `${width}px`, height: "36px", minHeight: "36px", outline: "none", boxShadow: "none" }} placeholder={column?.columnLabel} /> ); @@ -347,7 +337,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table return ( 0 && ( {activeFilters.map((filter) => ( - - {renderFilterInput(filter)} - + {renderFilterInput(filter)} ))} - + {/* 초기화 버튼 */} - + 초기화 @@ -380,10 +363,10 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table {activeFilters.length === 0 && } {/* 오른쪽: 데이터 건수 + 설정 버튼들 */} - + {/* 데이터 건수 표시 */} {currentTable?.dataCount !== undefined && ( - + {currentTable.dataCount.toLocaleString()}건 )} @@ -423,12 +406,9 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table {/* 패널들 */} - setColumnVisibilityOpen(false)} - /> - setColumnVisibilityOpen(false)} /> + setFilterOpen(false)} onFiltersApplied={(filters) => setActiveFilters(filters)} /> @@ -436,4 +416,3 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table ); } -