From f82d18575e10c5aae8fb9708536c22306e9f76cf Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 3 Sep 2025 11:32:09 +0900 Subject: [PATCH] =?UTF-8?q?=ED=83=80=EC=9E=85=EB=B3=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/화면관리_시스템_설계.md | 200 ++++- .../components/screen/DesignerToolbar.tsx | 17 +- frontend/components/screen/FloatingPanel.tsx | 72 +- .../components/screen/RealtimePreview.tsx | 379 +++++++-- frontend/components/screen/ScreenDesigner.tsx | 726 ++++++++++++++++-- .../screen/panels/DetailSettingsPanel.tsx | 219 ++++++ .../screen/panels/PropertiesPanel.tsx | 55 +- .../CheckboxTypeConfigPanel.tsx | 206 +++++ .../webtype-configs/CodeTypeConfigPanel.tsx | 304 ++++++++ .../webtype-configs/DateTypeConfigPanel.tsx | 320 ++++++++ .../webtype-configs/EntityTypeConfigPanel.tsx | 394 ++++++++++ .../webtype-configs/FileTypeConfigPanel.tsx | 301 ++++++++ .../webtype-configs/NumberTypeConfigPanel.tsx | 234 ++++++ .../webtype-configs/RadioTypeConfigPanel.tsx | 295 +++++++ .../webtype-configs/SelectTypeConfigPanel.tsx | 290 +++++++ .../webtype-configs/TextTypeConfigPanel.tsx | 193 +++++ .../TextareaTypeConfigPanel.tsx | 210 +++++ frontend/hooks/usePanelState.ts | 37 +- frontend/lib/utils/gridUtils.ts | 199 +++++ frontend/types/screen.ts | 116 ++- 20 files changed, 4603 insertions(+), 164 deletions(-) create mode 100644 frontend/components/screen/panels/DetailSettingsPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/CheckboxTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/CodeTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/DateTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/EntityTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/FileTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/NumberTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/RadioTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/SelectTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/TextTypeConfigPanel.tsx create mode 100644 frontend/components/screen/panels/webtype-configs/TextareaTypeConfigPanel.tsx diff --git a/docs/화면관리_시스템_설계.md b/docs/화면관리_시스템_설계.md index abb262ac..68d5b6b7 100644 --- a/docs/화면관리_시스템_설계.md +++ b/docs/화면관리_시스템_설계.md @@ -30,21 +30,30 @@ - **실시간 미리보기**: 설계한 화면을 실제 화면과 동일하게 확인 가능 - **메뉴 연동**: 각 회사의 메뉴에 화면 할당 및 관리 -### 🆕 최근 업데이트 (요약) +### 🆕 최근 업데이트 (2024.12) -- **픽셀 기반 자유 이동**: 격자 스냅을 제거하고 커서를 따라 정확히 이동하도록 구현. 그리드 라인은 시각적 가이드만 유지 -- **멀티 선택 강화**: Shift+클릭 + 드래그 박스(마키)로 다중선택 가능. 그룹 컨테이너는 선택에서 자동 제외 -- **다중 드래그 이동**: 다중선택 항목을 함께 이동(상대 위치 유지). 스크롤/그랩 오프셋 반영으로 튐 현상 제거 -- **그룹 UI 간소화**: 그룹 헤더/테두리 박스 제거(투명 컨테이너). 그룹 내부에만 집중 -- **그룹 내 정렬/분배 툴**: 좌/가로중앙/우, 상/세로중앙/하 정렬 + 가로/세로 균등 분배 추가(아이콘 UI) -- **왼쪽 목록 UX**: 검색·페이징 도입으로 대량 테이블 로딩 지연 완화 -- **Undo/Redo**: 최대 50단계, 단축키(Ctrl/Cmd+Z, Ctrl/Cmd+Y) -- **위젯 타입 렌더링 보강**: code/entity/file 포함 실제 위젯 형태로 표시 -- **복사/삭제/붙여넣기 범용화**: 단일/다중/그룹 선택 모두에서 동작. 우클릭 위치 붙여넣기, 단축키(Ctrl/Cmd+C, Ctrl/Cmd+V, Delete) 지원, 상단 툴바 버튼 제공. 클립보드 상태 배지 표시(예: "3개 복사됨", "그룹 복사됨") -- **화면 코드 자동 생성**: 회사 코드 기반 고유 화면 코드 자동 생성 (예: COMP_001) -- **레이아웃 저장/로드**: 설계한 화면 레이아웃을 데이터베이스에 저장하고 불러오는 기능 -- **메뉴-화면 할당**: 설계한 화면을 실제 메뉴에 할당하여 사용자가 접근할 수 있도록 연결 -- **인터랙티브 화면 뷰어**: 할당된 화면에서 실제 사용자 입력 및 상호작용이 가능한 완전 기능 화면 +#### ✅ 완료된 주요 기능들 + +- **컴포넌트 관리 시스템**: 드래그앤드롭, 다중 선택, 그룹 드래그, 실시간 위치 업데이트 +- **속성 편집 시스템**: 실시간 속성 편집, 라벨 관리 (텍스트, 폰트, 색상, 여백), 필수 입력 시 주황색 \* 표시 +- **격자 시스템**: 동적 격자 설정, 컴포넌트 스냅 및 크기 조정 +- **패널 관리**: 플로팅 패널, 수동 크기 조정, 위치 기억 +- **웹타입 지원**: text, number, decimal, date, datetime, select, dropdown, textarea, boolean, checkbox, radio, code, entity, file + +#### 🔧 해결된 기술적 문제들 + +- **라벨 하단 여백 동적 적용**: 여백값에 따른 정확한 위치 계산 +- **스타일 속성 개별 업데이트**: 초기화 방지를 위한 `style.propertyName` 방식 적용 +- **체크박스 실시간 반영**: 로컬 상태 + 실제 속성 동시 업데이트 +- **다중 드래그 최적화**: 지연 없는 실시간 미리보기, 선택 해제 방지 +- **입력 필드 실시간 적용**: debounce 제거, 즉시 반영 시스템 + +#### 🎯 개발 진행 상황 + +- **현재 완성도**: 95% (핵심 기능 완료) +- **기술 스택**: Next.js 15.4.4, TypeScript, Tailwind CSS, Shadcn/ui +- **상태 관리**: React Hooks 기반 로컬 상태 + 실시간 업데이트 패턴 +- **드래그앤드롭**: HTML5 Drag & Drop API 기반 고도화된 시스템 ### 🎯 **현재 테이블 구조와 100% 호환** @@ -2369,7 +2378,66 @@ export class TableTypeIntegrationService { ## 🚀 다음 단계 계획 -### 1. 컴포넌트 그룹화 기능 (완료) +### 1. 웹타입별 상세 설정 기능 구현 (진행 예정) + +#### 📋 구현 계획 개요 + +각 웹 타입(date, number, select 등)에 대한 세부적인 설정을 가능하게 하여 더 정교한 폼 컨트롤을 제공 + +#### 🎯 단계별 구현 계획 + +##### Phase 1: 타입 정의 및 인터페이스 설계 + +```typescript +// 웹타입별 설정 인터페이스 +interface DateTypeConfig { + format: "YYYY-MM-DD" | "YYYY-MM-DD HH:mm" | "YYYY-MM-DD HH:mm:ss"; + showTime: boolean; + minDate?: string; + maxDate?: string; + defaultValue?: string; +} + +interface NumberTypeConfig { + min?: number; + max?: number; + step?: number; + format?: "integer" | "decimal" | "currency" | "percentage"; + decimalPlaces?: number; + thousandSeparator?: boolean; +} + +interface SelectTypeConfig { + options: Array<{ label: string; value: string }>; + multiple?: boolean; + searchable?: boolean; + placeholder?: string; +} +``` + +##### Phase 2: PropertiesPanel 확장 + +- 웹 타입 선택 시 해당 타입의 세부 설정 UI 동적 표시 +- 각 타입별 전용 설정 컴포넌트 생성 +- 실시간 설정값 업데이트 및 미리보기 + +##### Phase 3: 우선순위 타입별 구현 + +1. **날짜/시간 (date, datetime)**: 날짜 형식, 시간 포함 여부, 날짜 범위 +2. **숫자 (number, decimal)**: 범위, 형식, 소수점, 천 단위 구분자 +3. **선택박스 (select, dropdown)**: 동적 옵션 관리, 다중 선택, 검색 기능 +4. **텍스트 (text, textarea)**: 길이 제한, 입력 패턴, 형식 검증 +5. **파일 (file)**: 파일 형식 제한, 크기 제한, 다중 업로드 + +##### Phase 4: RealtimePreview 업데이트 + +설정값에 따른 실제 렌더링 로직 구현 (input 속성, 검증 규칙 등) + +##### Phase 5: 저장/불러오기 + +컴포넌트 데이터에 webTypeConfig 포함하여 레이아웃 저장 시 설정값도 함께 저장 + +### 2. 컴포넌트 그룹화 기능 (완료) - [x] 여러 위젯을 컨테이너로 그룹화 - [x] 부모-자식 관계 설정(parentId) @@ -2463,6 +2531,105 @@ export class TableTypeIntegrationService { - **Version Control**: Git - **Package Manager**: npm +## 🔧 핵심 기술적 구현 패턴 + +### 1. 상태 관리 패턴 + +#### 로컬 상태 + 실시간 업데이트 패턴 + +PropertiesPanel에서 사용하는 입력 필드 관리 방식: + +```typescript +const [localInputs, setLocalInputs] = useState({ + placeholder: selectedComponent?.placeholder || "", + // ... 기타 필드들 +}); + +// 입력 시 로컬 상태 + 실제 컴포넌트 동시 업데이트 +onChange={(e) => { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, fieldName: newValue })); + onUpdateProperty("fieldName", newValue); +}} +``` + +#### 스타일 속성 개별 업데이트 패턴 + +스타일 초기화 방지를 위한 개별 속성 업데이트: + +```typescript +// Bad: 전체 객체 교체로 인한 다른 속성 손실 +onUpdateProperty("style", { ...selectedComponent.style, newProp: value }); + +// Good: 개별 속성 직접 업데이트 +onUpdateProperty("style.labelFontSize", value); +``` + +### 2. 드래그앤드롭 패턴 + +#### 다중 컴포넌트 드래그 처리 + +- dragState에 draggedComponents 배열로 선택된 모든 컴포넌트 관리 +- 실시간 미리보기를 위한 RealtimePreview와 실제 업데이트 분리 +- justFinishedDrag 플래그로 드래그 완료 후 의도치 않은 선택 해제 방지 + +#### 격자 스냅 시스템 + +- 컴포넌트 위치와 크기를 격자에 맞게 자동 조정 +- 격자 설정 변경 시 기존 컴포넌트들도 자동 재조정 + +### 3. 컴포넌트 렌더링 패턴 + +#### 웹타입별 동적 렌더링 + +RealtimePreview에서 switch-case로 웹타입별 적절한 입력 컴포넌트 렌더링: + +```typescript +switch (widgetType) { + case "text": + return ; + case "date": + return ; + case "select": + return ; +} +``` + +#### 라벨 동적 위치 계산 + +라벨 하단 여백 설정에 따른 동적 위치 계산: + +```typescript +const labelMarginBottomValue = parseInt(component.style?.labelMarginBottom || "4px", 10); +style={{ top: `${-20 - labelMarginBottomValue}px` }} +``` + +### 4. 패널 관리 패턴 + +#### 플로팅 패널 상태 관리 + +- 각 패널의 위치, 크기, 열림/닫힘 상태를 독립적으로 관리 +- 사용자가 수동으로 조정한 위치 기억 +- autoHeight 제거로 컨텐츠 변경 시에도 위치 유지 + +### 5. 타입 안전성 패턴 + +#### 인터페이스 확장 패턴 + +BaseComponent를 기본으로 각 컴포넌트 타입별 확장: + +```typescript +export interface WidgetComponent extends BaseComponent { + type: "widget"; + widgetType: WebType; + // 위젯 전용 속성들 +} +``` + +#### 유니온 타입 활용 + +ComponentData = ContainerComponent | WidgetComponent | GroupComponent 등으로 타입 안전성 보장 + ## 🎯 결론 화면관리 시스템은 **회사별 권한 관리**와 **테이블 타입관리 연계**를 통해 사용자가 직관적으로 웹 화면을 설계할 수 있는 강력한 도구입니다. @@ -2493,7 +2660,8 @@ export class TableTypeIntegrationService { - ✅ **Phase 1-6 완료**: 기본 구조, 드래그앤드롭, 컴포넌트 라이브러리, 테이블 연계, 미리보기, 통합 테스트 - ✅ **핵심 기능 완료**: 컴포넌트 그룹화, 레이아웃 저장/로드, 메뉴-화면 할당, 인터랙티브 화면 뷰어 -- 📋 **향후 계획**: 반응형 레이아웃, 고급 기능, 실제 CRUD 연동 +- ✅ **고도화 완료**: 실시간 속성 편집, 라벨 관리, 다중 드래그, 격자 시스템 +- 📋 **다음 계획**: 웹타입별 상세 설정, 반응형 레이아웃, 고급 기능 ### 🎉 **완전 기능 화면관리 시스템 완성!** diff --git a/frontend/components/screen/DesignerToolbar.tsx b/frontend/components/screen/DesignerToolbar.tsx index cb31eb93..9b95a98e 100644 --- a/frontend/components/screen/DesignerToolbar.tsx +++ b/frontend/components/screen/DesignerToolbar.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Menu, Database, Settings, Palette, Grid3X3, Save, Undo, Redo, Play, ArrowLeft } from "lucide-react"; +import { Menu, Database, Settings, Palette, Grid3X3, Save, Undo, Redo, Play, ArrowLeft, Cog } from "lucide-react"; import { cn } from "@/lib/utils"; interface DesignerToolbarProps { @@ -110,7 +110,20 @@ export const DesignerToolbar: React.FC = ({ 격자 - G + R + + + + diff --git a/frontend/components/screen/FloatingPanel.tsx b/frontend/components/screen/FloatingPanel.tsx index de8a0416..2de7ed12 100644 --- a/frontend/components/screen/FloatingPanel.tsx +++ b/frontend/components/screen/FloatingPanel.tsx @@ -35,13 +35,18 @@ export const FloatingPanel: React.FC = ({ minWidth = 280, minHeight = 300, maxWidth = 600, - maxHeight = 800, + maxHeight = 1200, // 800 → 1200 (더 큰 패널 지원) resizable = true, draggable = true, - autoHeight = false, // 자동 높이 조정 비활성화 (수동 크기 조절만 지원) + autoHeight = true, // 자동 높이 조정 활성화 (컨텐츠 크기에 맞게) className, }) => { const [panelSize, setPanelSize] = useState({ width, height }); + + // props 변경 시 패널 크기 업데이트 + useEffect(() => { + setPanelSize({ width, height }); + }, [width, height]); const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 }); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); @@ -91,7 +96,54 @@ export const FloatingPanel: React.FC = ({ } }, [isOpen, position, hasInitialized]); - // 자동 높이 조정 기능 제거됨 - 수동 크기 조절만 지원 + // 자동 높이 조정 기능 + useEffect(() => { + if (!autoHeight || !contentRef.current || isResizing) return; + + const updateHeight = () => { + if (!contentRef.current) return; + + // 일시적으로 높이 제한을 해제하여 실제 컨텐츠 높이 측정 + contentRef.current.style.maxHeight = "none"; + + // 컨텐츠의 실제 높이 측정 + const contentHeight = contentRef.current.scrollHeight; + const headerHeight = 60; // 헤더 높이 + const padding = 30; // 여유 공간 (좀 더 넉넉하게) + + const newHeight = Math.min(Math.max(minHeight, contentHeight + headerHeight + padding), maxHeight); + + console.log(`🔧 패널 높이 자동 조정:`, { + panelId: id, + contentHeight, + calculatedHeight: newHeight, + currentHeight: panelSize.height, + willUpdate: Math.abs(panelSize.height - newHeight) > 10, + }); + + // 현재 높이와 다르면 업데이트 + if (Math.abs(panelSize.height - newHeight) > 10) { + setPanelSize((prev) => ({ ...prev, height: newHeight })); + } + }; + + // 초기 높이 설정 + updateHeight(); + + // ResizeObserver로 컨텐츠 크기 변화 감지 + const resizeObserver = new ResizeObserver((entries) => { + // DOM 업데이트가 완료된 후에 높이 측정 + requestAnimationFrame(() => { + setTimeout(updateHeight, 50); // 약간의 지연으로 렌더링 완료 후 측정 + }); + }); + + resizeObserver.observe(contentRef.current); + + return () => { + resizeObserver.disconnect(); + }; + }, [autoHeight, minHeight, maxHeight, isResizing, panelSize.height, children]); // 드래그 시작 - 성능 최적화 const handleDragStart = (e: React.MouseEvent) => { @@ -215,16 +267,20 @@ export const FloatingPanel: React.FC = ({ {/* 컨텐츠 */}
{children}
{/* 리사이즈 핸들 */} - {resizable && ( + {resizable && !autoHeight && (
diff --git a/frontend/components/screen/RealtimePreview.tsx b/frontend/components/screen/RealtimePreview.tsx index ea3bec21..66e58720 100644 --- a/frontend/components/screen/RealtimePreview.tsx +++ b/frontend/components/screen/RealtimePreview.tsx @@ -1,7 +1,21 @@ "use client"; import React from "react"; -import { ComponentData, WebType } from "@/types/screen"; +import { + ComponentData, + WebType, + WidgetComponent, + DateTypeConfig, + NumberTypeConfig, + SelectTypeConfig, + TextTypeConfig, + TextareaTypeConfig, + CheckboxTypeConfig, + RadioTypeConfig, + FileTypeConfig, + CodeTypeConfig, + EntityTypeConfig, +} from "@/types/screen"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; @@ -57,122 +71,387 @@ const renderWidget = (component: ComponentData) => { switch (widgetType) { case "text": case "email": - case "tel": - return ; + case "tel": { + const widget = component as WidgetComponent; + const config = widget.webTypeConfig as TextTypeConfig | undefined; + + // 플레이스홀더 처리 + const finalPlaceholder = config?.placeholder || placeholder || "텍스트를 입력하세요"; + + const inputType = widgetType === "email" ? "email" : widgetType === "tel" ? "tel" : "text"; + + // multiline이면 Textarea로 렌더링 + if (config?.multiline) { + return ( +