519 lines
12 KiB
Markdown
519 lines
12 KiB
Markdown
# Phase 3 완료 요약
|
|
|
|
**날짜**: 2026-02-04
|
|
**상태**: 완료 ✅
|
|
**버전**: v4.0 Phase 3
|
|
|
|
---
|
|
|
|
## 🎯 달성 목표
|
|
|
|
Phase 2의 배치 고정 기능 이후, 다음 3가지 핵심 기능 추가:
|
|
|
|
1. ✅ **모드별 컴포넌트 표시/숨김** (visibility)
|
|
2. ✅ **강제 줄바꿈 컴포넌트** (pop-break)
|
|
3. ✅ **컴포넌트 오버라이드 병합** (모드별 설정 변경)
|
|
|
|
---
|
|
|
|
## 📦 구현 내용
|
|
|
|
### 1. 타입 정의
|
|
|
|
**파일**: `frontend/components/pop/designer/types/pop-layout.ts`
|
|
|
|
```typescript
|
|
// pop-break 추가
|
|
export type PopComponentType =
|
|
| "pop-field"
|
|
| "pop-button"
|
|
| "pop-list"
|
|
| "pop-indicator"
|
|
| "pop-scanner"
|
|
| "pop-numpad"
|
|
| "pop-spacer"
|
|
| "pop-break"; // 🆕
|
|
|
|
// visibility 속성 추가
|
|
export interface PopComponentDefinitionV4 {
|
|
id: string;
|
|
type: PopComponentType;
|
|
size: PopSizeConstraintV4;
|
|
|
|
visibility?: {
|
|
tablet_landscape?: boolean;
|
|
tablet_portrait?: boolean;
|
|
mobile_landscape?: boolean;
|
|
mobile_portrait?: boolean;
|
|
};
|
|
|
|
// ...
|
|
}
|
|
|
|
// 기본 크기
|
|
defaultSizes["pop-break"] = {
|
|
width: "fill", // 100% 너비
|
|
height: "fixed",
|
|
fixedHeight: 0, // 높이 0
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### 2. 렌더러 로직
|
|
|
|
**파일**: `frontend/components/pop/designer/renderers/PopFlexRenderer.tsx`
|
|
|
|
#### visibility 체크
|
|
```typescript
|
|
const isComponentVisible = (component: PopComponentDefinitionV4): boolean => {
|
|
if (!component.visibility) return true; // 기본값: 표시
|
|
const modeVisibility = component.visibility[currentMode];
|
|
return modeVisibility !== false; // undefined도 true로 취급
|
|
};
|
|
```
|
|
|
|
#### 컴포넌트 오버라이드 병합
|
|
```typescript
|
|
const getMergedComponent = (baseComponent: PopComponentDefinitionV4) => {
|
|
if (currentMode === "tablet_landscape") return baseComponent;
|
|
|
|
const override = overrides?.[currentMode]?.components?.[baseComponent.id];
|
|
if (!override) return baseComponent;
|
|
|
|
// 깊은 병합 (config, size)
|
|
return {
|
|
...baseComponent,
|
|
...override,
|
|
size: { ...baseComponent.size, ...override.size },
|
|
config: { ...baseComponent.config, ...override.config },
|
|
};
|
|
};
|
|
```
|
|
|
|
#### pop-break 렌더링
|
|
```typescript
|
|
if (mergedComponent.type === "pop-break") {
|
|
return (
|
|
<div
|
|
style={{ flexBasis: "100%" }} // 핵심: 100% 너비로 줄바꿈 강제
|
|
className={isDesignMode
|
|
? "h-4 border-2 border-dashed border-gray-300"
|
|
: "h-0"
|
|
}
|
|
>
|
|
{isDesignMode && <span>줄바꿈</span>}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. 삭제 함수 개선
|
|
|
|
**파일**: `frontend/components/pop/designer/types/pop-layout.ts`
|
|
|
|
```typescript
|
|
export const removeComponentFromV4Layout = (
|
|
layout: PopLayoutDataV4,
|
|
componentId: string
|
|
): PopLayoutDataV4 => {
|
|
// 1. components에서 삭제
|
|
const { [componentId]: _, ...remainingComponents } = layout.components;
|
|
|
|
// 2. root.children에서 제거
|
|
const newRoot = removeChildFromContainer(layout.root, componentId);
|
|
|
|
// 3. 🆕 모든 오버라이드에서 제거
|
|
const newOverrides = cleanupOverridesAfterDelete(layout.overrides, componentId);
|
|
|
|
return {
|
|
...layout,
|
|
root: newRoot,
|
|
components: remainingComponents,
|
|
overrides: newOverrides,
|
|
};
|
|
};
|
|
```
|
|
|
|
#### 오버라이드 정리 로직
|
|
```typescript
|
|
function cleanupOverridesAfterDelete(
|
|
overrides: PopLayoutDataV4["overrides"],
|
|
componentId: string
|
|
) {
|
|
// 각 모드별로:
|
|
// 1. containers.root.children에서 componentId 제거
|
|
// 2. components[componentId] 제거
|
|
// 3. 빈 오버라이드 자동 삭제
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. 속성 패널 UI
|
|
|
|
**파일**: `frontend/components/pop/designer/panels/ComponentEditorPanelV4.tsx`
|
|
|
|
#### "표시" 탭 추가
|
|
```typescript
|
|
<TabsList>
|
|
<TabsTrigger value="size">크기</TabsTrigger>
|
|
<TabsTrigger value="settings">설정</TabsTrigger>
|
|
<TabsTrigger value="visibility">
|
|
<Eye className="h-3 w-3" />
|
|
표시
|
|
</TabsTrigger>
|
|
<TabsTrigger value="data">데이터</TabsTrigger>
|
|
</TabsList>
|
|
```
|
|
|
|
#### VisibilityForm 컴포넌트
|
|
```typescript
|
|
function VisibilityForm({ component, onUpdate }) {
|
|
const modes = [
|
|
{ key: "tablet_landscape", label: "태블릿 가로" },
|
|
{ key: "tablet_portrait", label: "태블릿 세로" },
|
|
{ key: "mobile_landscape", label: "모바일 가로" },
|
|
{ key: "mobile_portrait", label: "모바일 세로" },
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
{modes.map(({ key, label }) => (
|
|
<input
|
|
type="checkbox"
|
|
checked={component.visibility?.[key] !== false}
|
|
onChange={(e) => {
|
|
onUpdate?.({
|
|
visibility: {
|
|
...component.visibility,
|
|
[key]: e.target.checked,
|
|
},
|
|
});
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 5. 팔레트 업데이트
|
|
|
|
**파일**: `frontend/components/pop/designer/panels/ComponentPaletteV4.tsx`
|
|
|
|
```typescript
|
|
const COMPONENT_PALETTE = [
|
|
// ... 기존 컴포넌트들
|
|
{
|
|
type: "pop-break",
|
|
label: "줄바꿈",
|
|
icon: WrapText,
|
|
description: "강제 줄바꿈 (flex-basis: 100%)",
|
|
},
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 UI 변경사항
|
|
|
|
### 컴포넌트 팔레트
|
|
```
|
|
컴포넌트
|
|
├─ 필드
|
|
├─ 버튼
|
|
├─ 리스트
|
|
├─ 인디케이터
|
|
├─ 스캐너
|
|
├─ 숫자패드
|
|
├─ 스페이서
|
|
└─ 줄바꿈 🆕
|
|
```
|
|
|
|
### 속성 패널
|
|
```
|
|
┌─────────────────────┐
|
|
│ 탭: [크기][설정] │
|
|
│ [표시📍][데이터] │
|
|
├─────────────────────┤
|
|
│ 모드별 표시 설정 │
|
|
│ ☑ 태블릿 가로 │
|
|
│ ☑ 태블릿 세로 │
|
|
│ ☐ 모바일 가로 (숨김)│
|
|
│ ☑ 모바일 세로 │
|
|
├─────────────────────┤
|
|
│ 반응형 숨김 │
|
|
│ [500] px 이하 숨김 │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 📖 사용 예시
|
|
|
|
### 예시 1: 모바일 전용 버튼
|
|
```typescript
|
|
{
|
|
id: "call-button",
|
|
type: "pop-button",
|
|
label: "전화 걸기",
|
|
visibility: {
|
|
tablet_landscape: false, // 태블릿: 숨김
|
|
tablet_portrait: false,
|
|
mobile_landscape: true, // 모바일: 표시
|
|
mobile_portrait: true,
|
|
},
|
|
}
|
|
```
|
|
|
|
**결과**:
|
|
- 태블릿 화면: "전화 걸기" 버튼 안 보임
|
|
- 모바일 화면: "전화 걸기" 버튼 보임
|
|
|
|
---
|
|
|
|
### 예시 2: 모드별 줄바꿈
|
|
```typescript
|
|
레이아웃: [A] [B] [줄바꿈] [C] [D]
|
|
|
|
줄바꿈 설정:
|
|
{
|
|
id: "break-1",
|
|
type: "pop-break",
|
|
visibility: {
|
|
tablet_landscape: false, // 태블릿: 줄바꿈 숨김
|
|
mobile_portrait: true, // 모바일: 줄바꿈 표시
|
|
}
|
|
}
|
|
```
|
|
|
|
**결과**:
|
|
```
|
|
태블릿 가로 (1024px):
|
|
┌───────────────────────────┐
|
|
│ [A] [B] [C] [D] │ ← 한 줄
|
|
└───────────────────────────┘
|
|
|
|
모바일 세로 (375px):
|
|
┌─────────────────┐
|
|
│ [A] [B] │ ← 첫 줄
|
|
│ [C] [D] │ ← 둘째 줄 (줄바꿈 적용)
|
|
└─────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### 예시 3: 리스트 컬럼 수 변경 (확장 가능)
|
|
```typescript
|
|
// 기본 (태블릿 가로)
|
|
{
|
|
id: "product-list",
|
|
type: "pop-list",
|
|
config: {
|
|
columns: 7, // 7개 컬럼
|
|
}
|
|
}
|
|
|
|
// 오버라이드 (모바일 세로)
|
|
overrides: {
|
|
mobile_portrait: {
|
|
components: {
|
|
"product-list": {
|
|
config: {
|
|
columns: 3, // 3개 컬럼
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**결과**:
|
|
- 태블릿: 7개 컬럼 표시
|
|
- 모바일: 3개 컬럼 표시 (자동 병합)
|
|
|
|
---
|
|
|
|
## 🧪 테스트 시나리오
|
|
|
|
### ✅ 테스트 1: 줄바꿈 기본 동작
|
|
1. 팔레트에서 "줄바꿈" 드래그
|
|
2. 컴포넌트 사이에 드롭
|
|
3. 디자인 모드에서 점선 "줄바꿈" 표시 확인
|
|
4. 미리보기에서 줄바꿈이 안 보이는지 확인
|
|
|
|
### ✅ 테스트 2: 모드별 줄바꿈
|
|
1. 줄바꿈 컴포넌트 추가
|
|
2. "표시" 탭 → 태블릿 모드 체크 해제
|
|
3. 태블릿 가로: 한 줄
|
|
4. 모바일 세로: 두 줄
|
|
|
|
### ✅ 테스트 3: 삭제 시 오버라이드 정리
|
|
1. 모바일 세로에서 배치 고정
|
|
2. 컴포넌트 삭제
|
|
3. 저장 후 로드
|
|
4. DB 확인: overrides에서도 제거되었는지
|
|
|
|
### ✅ 테스트 4: 컴포넌트 숨김
|
|
1. 컴포넌트 선택
|
|
2. "표시" 탭 → 태블릿 모드 체크 해제
|
|
3. 태블릿: 컴포넌트 안 보임
|
|
4. 모바일: 컴포넌트 보임
|
|
|
|
### ✅ 테스트 5: 속성 패널 UI
|
|
1. 컴포넌트 선택
|
|
2. "표시" 탭 클릭
|
|
3. 4개 체크박스 확인
|
|
4. 체크 해제 시 "(숨김)" 표시
|
|
5. 저장 후 로드 → 상태 유지
|
|
|
|
---
|
|
|
|
## 📝 수정된 파일
|
|
|
|
### 코드 파일 (5개)
|
|
```
|
|
✅ frontend/components/pop/designer/types/pop-layout.ts
|
|
- PopComponentType 확장 (pop-break)
|
|
- PopComponentDefinitionV4.visibility 추가
|
|
- cleanupOverridesAfterDelete() 추가
|
|
|
|
✅ frontend/components/pop/designer/renderers/PopFlexRenderer.tsx
|
|
- isComponentVisible() 추가
|
|
- getMergedComponent() 추가
|
|
- pop-break 렌더링 추가
|
|
- ContainerRenderer props 확장
|
|
|
|
✅ frontend/components/pop/designer/panels/ComponentEditorPanelV4.tsx
|
|
- "표시" 탭 추가
|
|
- VisibilityForm 컴포넌트 추가
|
|
- COMPONENT_TYPE_LABELS 업데이트
|
|
|
|
✅ frontend/components/pop/designer/panels/ComponentPaletteV4.tsx
|
|
- "줄바꿈" 컴포넌트 추가
|
|
|
|
✅ frontend/components/pop/designer/PopDesigner.tsx
|
|
- (기존 Phase 2 변경사항 유지)
|
|
```
|
|
|
|
### 문서 파일 (6개)
|
|
```
|
|
✅ popdocs/CHANGELOG.md
|
|
- Phase 3 완료 기록
|
|
|
|
✅ popdocs/PLAN.md
|
|
- Phase 3 체크 완료
|
|
- Phase 4 계획 추가
|
|
|
|
✅ popdocs/V4_UNIFIED_DESIGN_SPEC.md
|
|
- Phase 3 섹션 추가
|
|
|
|
✅ popdocs/components-spec.md
|
|
- pop-break 상세 스펙 추가
|
|
- Phase 3 업데이트 노트
|
|
|
|
✅ popdocs/README.md
|
|
- 현재 상태 업데이트
|
|
- Phase 3 요약 추가
|
|
|
|
✅ popdocs/decisions/002-phase3-visibility-break.md (신규)
|
|
- 상세 설계 문서
|
|
|
|
✅ popdocs/PHASE3_SUMMARY.md (신규)
|
|
- 이 문서
|
|
```
|
|
|
|
---
|
|
|
|
## 🎓 핵심 개념
|
|
|
|
### Flexbox 줄바꿈 원리
|
|
```css
|
|
/* 컨테이너 */
|
|
.container {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap; /* 필수 */
|
|
}
|
|
|
|
/* pop-break */
|
|
.pop-break {
|
|
flex-basis: 100%; /* 전체 너비 차지 → 다음 요소는 새 줄로 */
|
|
height: 0; /* 실제로는 안 보임 */
|
|
}
|
|
```
|
|
|
|
### visibility vs hideBelow
|
|
| 속성 | 제어 방식 | 용도 |
|
|
|------|----------|------|
|
|
| `visibility` | 모드별 명시적 | 특정 모드에서만 표시 (예: 모바일 전용) |
|
|
| `hideBelow` | 픽셀 기반 자동 | 화면 너비에 따라 자동 숨김 (예: 500px 이하) |
|
|
|
|
**예시**:
|
|
```typescript
|
|
{
|
|
visibility: {
|
|
tablet_landscape: false, // 태블릿 가로: 무조건 숨김
|
|
},
|
|
hideBelow: 500, // 500px 이하: 자동 숨김 (다른 모드에서도)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 다음 단계
|
|
|
|
### Phase 4: 실제 컴포넌트 구현
|
|
```
|
|
우선순위:
|
|
1. pop-field (입력/표시 필드)
|
|
2. pop-button (액션 버튼)
|
|
3. pop-list (데이터 리스트)
|
|
4. pop-indicator (KPI 표시)
|
|
5. pop-scanner (바코드/QR)
|
|
6. pop-numpad (숫자 입력)
|
|
```
|
|
|
|
### 추가 개선 사항
|
|
```
|
|
1. 컴포넌트 오버라이드 UI
|
|
- 리스트 컬럼 수 조정 UI
|
|
- 버튼 스타일 변경 UI
|
|
- 필드 표시 형식 변경 UI
|
|
|
|
2. "모든 모드에 적용" 기능
|
|
- 한 번에 모든 모드 체크/해제
|
|
|
|
3. 오버라이드 비교 뷰
|
|
- 기본값 vs 오버라이드 차이 시각화
|
|
```
|
|
|
|
---
|
|
|
|
## ✨ 주요 성과
|
|
|
|
1. ✅ **모드별 컴포넌트 제어**: visibility 속성으로 유연한 표시/숨김
|
|
2. ✅ **Flexbox 줄바꿈 해결**: pop-break 컴포넌트로 업계 표준 달성
|
|
3. ✅ **확장 가능한 구조**: 컴포넌트 오버라이드 병합으로 추후 기능 추가 용이
|
|
4. ✅ **데이터 일관성**: 삭제 시 오버라이드 자동 정리로 데이터 무결성 유지
|
|
5. ✅ **직관적인 UI**: 체크박스 기반 visibility 제어
|
|
|
|
---
|
|
|
|
## 📚 참고 문서
|
|
|
|
- [decisions/002-phase3-visibility-break.md](./decisions/002-phase3-visibility-break.md) - 상세 설계
|
|
- [V4_UNIFIED_DESIGN_SPEC.md](./V4_UNIFIED_DESIGN_SPEC.md) - v4 통합 설계
|
|
- [CHANGELOG.md](./CHANGELOG.md) - 변경 이력
|
|
- [PLAN.md](./PLAN.md) - 로드맵
|
|
|
|
---
|
|
|
|
*Phase 3 완료 - 2026-02-04*
|
|
*다음: Phase 4 (실제 컴포넌트 구현)*
|