@axboot-mcp/mcp-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +119 -0
- package/MCP_TOOL_PLAN.md +710 -0
- package/MCP_USAGE.md +914 -0
- package/README.md +168 -0
- package/REPOSITORY_CONVENTIONS.md +250 -0
- package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
- package/SEARCH_PARAMS_PLAN.md +2570 -0
- package/STORE_PATTERNS.md +1178 -0
- package/debug-dto.js +72 -0
- package/generate-banner-store.js +62 -0
- package/generation-plan.json +2176 -0
- package/generation-results.json +1817 -0
- package/package.json +45 -0
- package/scripts/batch-generate-all.js +159 -0
- package/scripts/batch-generate-mcp.js +329 -0
- package/scripts/batch-generate-stores-v2.js +272 -0
- package/scripts/batch-generate-stores.js +179 -0
- package/scripts/batch-plan.json +3810 -0
- package/scripts/batch-process.py +90 -0
- package/scripts/batch-regenerate.js +356 -0
- package/scripts/direct-generate.js +227 -0
- package/scripts/execute-batches.js +1911 -0
- package/scripts/generate-all-stores.js +144 -0
- package/scripts/generate-stores-mcp.js +161 -0
- package/scripts/generate-stores-v2.js +450 -0
- package/scripts/generate-stores-v3.js +412 -0
- package/scripts/generate-stores-v4.js +521 -0
- package/scripts/generate-stores.js +382 -0
- package/scripts/repos-to-process.json +1899 -0
- package/src/config/nh-layout-patterns.ts +166 -0
- package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
- package/src/docs/NH_STORE_PATTERNS.md +297 -0
- package/src/docs/README.md +216 -0
- package/src/docs/index.ts +28 -0
- package/src/docs/loader.ts +568 -0
- package/src/docs/patterns.json +419 -0
- package/src/docs/practical-examples.md +732 -0
- package/src/docs/quick-start.md +257 -0
- package/src/docs/requirements-analysis-guide.md +364 -0
- package/src/docs/rules.json +321 -0
- package/src/docs/store-pattern-analysis.md +664 -0
- package/src/docs/store-patterns-rules.md +1168 -0
- package/src/docs/store-patterns-usage-guide.md +1835 -0
- package/src/docs/troubleshooting.md +544 -0
- package/src/docs/type-selection-guide.md +572 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/AntD-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +1515 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/DataGrid-/354/202/254/354/232/251/353/262/225.md +866 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/FormItem-/354/202/254/354/232/251/353/262/225.md +903 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/FormModal-/354/202/254/354/232/251/353/262/225.md +1155 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/MCP-/353/260/224/354/235/264/353/270/214/354/275/224/353/224/251-/352/260/200/354/235/264/353/223/234.md +1133 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/MSW-Mock-/353/215/260/354/235/264/355/204/260-/354/202/254/354/232/251/353/262/225.md +579 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/Search-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +738 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/Store-/355/214/250/355/204/264-/354/202/254/354/232/251/353/262/225.md +1135 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/352/265/254/354/204/261-/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234/354/210/234/354/204/234.md +1805 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234-/355/224/204/353/241/254/355/224/204/355/212/270-/352/260/200/354/235/264/353/223/234.md +946 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/225/354/236/245/355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/354/203/201/354/204/270-/355/224/204/353/241/254/355/224/204/355/212/270/352/260/200/354/235/264/353/223/234.md +2422 -0
- package/src/features/store-features.ts +232 -0
- package/src/handlers/analyze-requirements.ts +403 -0
- package/src/handlers/analyze.ts +1373 -0
- package/src/handlers/generate-from-requirements.ts +250 -0
- package/src/handlers/generate-hook.ts +950 -0
- package/src/handlers/generate-interactive.ts +840 -0
- package/src/handlers/generate-listdatagrid.ts +521 -0
- package/src/handlers/generate-multi-stores.ts +577 -0
- package/src/handlers/generate-requirements-from-layout.ts +160 -0
- package/src/handlers/generate-search-params.ts +717 -0
- package/src/handlers/generate.ts +911 -0
- package/src/handlers/list-templates.ts +104 -0
- package/src/handlers/scan-metadata.ts +485 -0
- package/src/handlers/suggest-layout.ts +326 -0
- package/src/index.ts +959 -0
- package/src/prompts/search-params.md +793 -0
- package/src/templates/index.ts +107 -0
- package/src/templates/unified.ts +462 -0
- package/store-generation-error-patterns.md +225 -0
- package/test/useAgentStore.ts +136 -0
- package/test-server.js +78 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,1805 @@
|
|
|
1
|
+
# 화면 구성 타입별 개발순서 가이드
|
|
2
|
+
|
|
3
|
+
AXBoot 프로젝트에서 화면을 개발할 때, 화면 구성 타입에 따라 체계적인 개발 순서를 제시하는 가이드입니다. 실제 NH 프로젝트의 화면들을 분석하여 타입별로 분류하고 각각의 개발 순서를 정리했습니다.
|
|
4
|
+
|
|
5
|
+
## 목차
|
|
6
|
+
1. [화면 구성 타입 분류](#화면-구성-타입-분류)
|
|
7
|
+
- [기본 화면 타입 (6가지)](#기본-화면-타입-6가지)
|
|
8
|
+
- [확장 화면 타입 (20가지)](#확장-화면-타입-20가지)
|
|
9
|
+
2. [타입별 개발 순서](#타입별-개발-순서)
|
|
10
|
+
3. [확장 타입별 개발 순서](#확장-타입별-개발-순서)
|
|
11
|
+
4. [공통 개발 패턴](#공통-개발-패턴)
|
|
12
|
+
5. [체크리스트](#체크리스트)
|
|
13
|
+
|
|
14
|
+
## 화면 구성 타입 분류
|
|
15
|
+
|
|
16
|
+
### 기본 화면 타입 (6가지)
|
|
17
|
+
|
|
18
|
+
### 1. 📋 목록 관리형 (List Management)
|
|
19
|
+
**특징**: 검색 + 목록 + 상세 정보 표시
|
|
20
|
+
**예시**: 회원 관리, 쿠폰 관리, SEO 관리
|
|
21
|
+
**대표 URL**: `/NH/member.list` (회원 관리)
|
|
22
|
+
**구성요소**:
|
|
23
|
+
- SearchParams (검색 폼)
|
|
24
|
+
- ListDataGrid (목록 그리드)
|
|
25
|
+
- DetailDrawer/Panel (상세 정보)
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
**주요 특징**:
|
|
30
|
+
- 복합 검색 조건 (날짜범위, 검색구분, 다중선택 필터)
|
|
31
|
+
- 페이징 처리된 목록 그리드
|
|
32
|
+
- 선택 시 상세정보를 Drawer로 표시
|
|
33
|
+
- 액션 버튼들 (포인트지급, 엑셀다운로드 등)
|
|
34
|
+
|
|
35
|
+
### 2. 📊 대시보드형 (Dashboard)
|
|
36
|
+
**특징**: 요약 정보 + 탭 + 차트/통계
|
|
37
|
+
**예시**: 회원 대시보드
|
|
38
|
+
**대표 URL**: `/NH/member.dashboard` (회원 대시보드)
|
|
39
|
+
**구성요소**:
|
|
40
|
+
- Summary (요약 정보)
|
|
41
|
+
- Tabs (탭 네비게이션)
|
|
42
|
+
- Charts/Statistics (차트 및 통계)
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
**주요 특징**:
|
|
47
|
+
- 상단에 요약 통계 정보 표시
|
|
48
|
+
- 탭으로 구분된 컨텐츠 영역
|
|
49
|
+
- 기간별 데이터 표시
|
|
50
|
+
- 차트 및 그래프 (선택 사항)
|
|
51
|
+
|
|
52
|
+
### 3. 🌳 트리 관리형 (Tree Management)
|
|
53
|
+
**특징**: 계층 구조 + 상세 폼
|
|
54
|
+
**예시**: 카테고리 관리, 메뉴 관리
|
|
55
|
+
**대표 URL**: `/NH/product.category-mng` (카테고리 관리)
|
|
56
|
+
**구성요소**:
|
|
57
|
+
- TreeView (계층 트리)
|
|
58
|
+
- DetailForm (상세 폼)
|
|
59
|
+
- ColResizer (컬럼 크기 조정)
|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
**주요 특징**:
|
|
64
|
+
- 좌우 분할 레이아웃 (Tree + Form)
|
|
65
|
+
- 드래그앤드롭으로 계층 구조 변경 가능
|
|
66
|
+
- 컬럼 크기 조정 기능
|
|
67
|
+
- 트리 노드별 액션 버튼 (추가/삭제)
|
|
68
|
+
|
|
69
|
+
### 4. 📝 단일 폼형 (Single Form)
|
|
70
|
+
**특징**: 하나의 폼으로 CRUD 처리
|
|
71
|
+
**예시**: 시스템 설정, 배치 작업
|
|
72
|
+
**대표 URL**: `/NH/system.clear-cache` (캐시 클리어)
|
|
73
|
+
**구성요소**:
|
|
74
|
+
- Form (단일 폼)
|
|
75
|
+
- Action Buttons (액션 버튼)
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
**주요 특징**:
|
|
80
|
+
- 단순한 설정이나 관리 기능
|
|
81
|
+
- 하나의 카드 형태 폼
|
|
82
|
+
- 저장/초기화 등 기본 액션 버튼
|
|
83
|
+
- 유효성 검증 및 에러 처리
|
|
84
|
+
|
|
85
|
+
### 5. 💬 게시판형 (Board/BBS)
|
|
86
|
+
**특징**: 게시글 목록 + 상세 + 댓글
|
|
87
|
+
**예시**: 공지사항, FAQ, 게시판
|
|
88
|
+
**대표 URL**: `/NH/system.bbs` (시스템 게시판)
|
|
89
|
+
**구성요소**:
|
|
90
|
+
- ListDataGrid (게시글 목록)
|
|
91
|
+
- PostDetail (게시글 상세)
|
|
92
|
+
- Comments (댓글)
|
|
93
|
+
|
|
94
|
+

|
|
95
|
+
|
|
96
|
+
**주요 특징**:
|
|
97
|
+
- 좌우 분할 레이아웃 (목록 + 상세)
|
|
98
|
+
- 게시글 검색 기능
|
|
99
|
+
- 댓글 시스템
|
|
100
|
+
- 게시글 작성/수정 폼
|
|
101
|
+
|
|
102
|
+
### 6. 🏠 홈/랜딩형 (Home/Landing)
|
|
103
|
+
**특징**: 다양한 위젯과 정보를 종합 표시
|
|
104
|
+
**예시**: 메인 홈
|
|
105
|
+
**대표 URL**: `/NH/home` (홈 대시보드)
|
|
106
|
+
**구성요소**:
|
|
107
|
+
- Multiple Widgets
|
|
108
|
+
- Quick Actions
|
|
109
|
+
- Summary Cards
|
|
110
|
+
|
|
111
|
+

|
|
112
|
+
|
|
113
|
+
**주요 특징**:
|
|
114
|
+
- 다양한 정보 위젯들의 조합
|
|
115
|
+
- 빠른 액션 버튼들
|
|
116
|
+
- 카드 형태의 요약 정보
|
|
117
|
+
- 커스터마이징 가능한 레이아웃
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 타입별 개발 순서
|
|
122
|
+
|
|
123
|
+
## 1. 📋 목록 관리형 (List Management) 개발 순서
|
|
124
|
+
|
|
125
|
+
**대표 예시**: `member.list`, `benefit.coupon`, `system.seo`
|
|
126
|
+
|
|
127
|
+
### Step 1: 기본 구조 설정
|
|
128
|
+
```bash
|
|
129
|
+
# 폴더 구조 생성
|
|
130
|
+
your-feature/
|
|
131
|
+
├── App.tsx # 메인 컨테이너
|
|
132
|
+
├── ListDataGrid.tsx # 목록 그리드
|
|
133
|
+
├── useYourFeatureStore.ts # Zustand Store
|
|
134
|
+
└── index.ts # 내보내기
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Step 2: Store 개발 (핵심)
|
|
138
|
+
```typescript
|
|
139
|
+
// useYourFeatureStore.ts
|
|
140
|
+
interface ListRequest extends ApiRequestType {
|
|
141
|
+
dateRange?: [string, string];
|
|
142
|
+
searchType?: string;
|
|
143
|
+
searchText?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface States {
|
|
147
|
+
listData: AXDGDataItem<DtoItem>[];
|
|
148
|
+
listSpinning: boolean;
|
|
149
|
+
listRequestValue: ListRequest;
|
|
150
|
+
selectedItem?: DtoItem;
|
|
151
|
+
// ... 기타 상태
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface Actions {
|
|
155
|
+
callListApi: (request?: ListRequest) => Promise<void>;
|
|
156
|
+
setListRequestValue: (value: ListRequest) => void;
|
|
157
|
+
setSelectedItem: (item?: DtoItem) => Promise<void>;
|
|
158
|
+
// ... 기타 액션
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Step 3: 메인 App 컴포넌트
|
|
163
|
+
```typescript
|
|
164
|
+
// App.tsx
|
|
165
|
+
export default function App() {
|
|
166
|
+
// 1. Store 구독
|
|
167
|
+
const store = useYourFeatureStore();
|
|
168
|
+
|
|
169
|
+
// 2. 검색 폼 설정
|
|
170
|
+
const [searchForm] = Form.useForm();
|
|
171
|
+
|
|
172
|
+
// 3. 생명주기 훅
|
|
173
|
+
useDidMountEffect(() => {
|
|
174
|
+
store.init();
|
|
175
|
+
store.callListApi();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// 4. 이벤트 핸들러
|
|
179
|
+
const handleSearch = useCallback(async () => {
|
|
180
|
+
await store.callListApi({ pageNumber: 1 });
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<Container>
|
|
185
|
+
<Header>
|
|
186
|
+
<ProgramTitle />
|
|
187
|
+
<SearchParams
|
|
188
|
+
params={searchParams}
|
|
189
|
+
onSearch={handleSearch}
|
|
190
|
+
/>
|
|
191
|
+
</Header>
|
|
192
|
+
<Body>
|
|
193
|
+
<ListDataGrid />
|
|
194
|
+
</Body>
|
|
195
|
+
<DetailDrawer />
|
|
196
|
+
</Container>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Step 4: SearchParams 정의
|
|
202
|
+
```typescript
|
|
203
|
+
const searchParams = [
|
|
204
|
+
// 날짜 범위
|
|
205
|
+
{
|
|
206
|
+
label: "기간",
|
|
207
|
+
name: "dateRange",
|
|
208
|
+
type: SearchParamType.DTPICKER_DATE_RANGE,
|
|
209
|
+
format: DT_FORMAT.DATE,
|
|
210
|
+
presetRender: DtPickerPresetRender({})
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// 검색 구분 + 검색어
|
|
214
|
+
{
|
|
215
|
+
type: SearchParamType.GROUP,
|
|
216
|
+
children: [
|
|
217
|
+
{
|
|
218
|
+
label: "검색구분",
|
|
219
|
+
name: "searchType",
|
|
220
|
+
type: SearchParamType.SELECT,
|
|
221
|
+
options: searchTypeOptions
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
label: "검색어",
|
|
225
|
+
name: "searchText",
|
|
226
|
+
type: SearchParamType.INPUT,
|
|
227
|
+
placeholder: "검색어 입력"
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// 다중 선택 필터
|
|
233
|
+
{
|
|
234
|
+
label: "상태",
|
|
235
|
+
name: "statusFilter",
|
|
236
|
+
type: SearchParamType.SELECT_MULTI,
|
|
237
|
+
options: statusOptions,
|
|
238
|
+
checkAllItem: true
|
|
239
|
+
}
|
|
240
|
+
];
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Step 5: ListDataGrid 구현
|
|
244
|
+
```typescript
|
|
245
|
+
// ListDataGrid.tsx
|
|
246
|
+
export function ListDataGrid() {
|
|
247
|
+
const listData = useYourFeatureStore(s => s.listData);
|
|
248
|
+
const listPage = useYourFeatureStore(s => s.listPage);
|
|
249
|
+
const setSelectedItem = useYourFeatureStore(s => s.setSelectedItem);
|
|
250
|
+
|
|
251
|
+
const columns = [
|
|
252
|
+
{ key: 'id', label: 'ID', width: 80 },
|
|
253
|
+
{ key: 'name', label: '이름', width: 200 },
|
|
254
|
+
{ key: 'status', label: '상태', width: 100 },
|
|
255
|
+
{ key: 'createdAt', label: '등록일', width: 150 }
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<AXDataGrid
|
|
260
|
+
data={listData}
|
|
261
|
+
columns={columns}
|
|
262
|
+
page={listPage}
|
|
263
|
+
onRowClick={(record) => setSelectedItem(record.values)}
|
|
264
|
+
/>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Step 6: 상세 정보 (Drawer/Panel) 구현
|
|
270
|
+
```typescript
|
|
271
|
+
// DetailDrawer.tsx 또는 DetailPanel.tsx
|
|
272
|
+
export function DetailDrawer({ open, onClose, selectedItem }) {
|
|
273
|
+
return (
|
|
274
|
+
<AXDrawer open={open} onClose={onClose}>
|
|
275
|
+
<AXDrawer.Header>
|
|
276
|
+
<h3>상세 정보</h3>
|
|
277
|
+
</AXDrawer.Header>
|
|
278
|
+
<AXDrawer.Body>
|
|
279
|
+
{selectedItem && (
|
|
280
|
+
<Form>
|
|
281
|
+
<Form.Item label="이름">
|
|
282
|
+
<Input value={selectedItem.name} readOnly />
|
|
283
|
+
</Form.Item>
|
|
284
|
+
{/* 기타 필드들 */}
|
|
285
|
+
</Form>
|
|
286
|
+
)}
|
|
287
|
+
</AXDrawer.Body>
|
|
288
|
+
</AXDrawer>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 2. 📊 대시보드형 (Dashboard) 개발 순서
|
|
296
|
+
|
|
297
|
+
**대표 예시**: `member.dashboard`
|
|
298
|
+
|
|
299
|
+
### Step 1: 기본 구조 설정
|
|
300
|
+
```bash
|
|
301
|
+
your-dashboard/
|
|
302
|
+
├── App.tsx # 메인 대시보드
|
|
303
|
+
├── Summary.tsx # 요약 정보
|
|
304
|
+
├── PanelIndex.tsx # 탭 컨텐츠 라우터
|
|
305
|
+
├── PanelPg1.tsx # 첫 번째 탭
|
|
306
|
+
├── PanelPg2.tsx # 두 번째 탭
|
|
307
|
+
└── useStore.ts # 대시보드 Store
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Step 2: 대시보드 Store 개발
|
|
311
|
+
```typescript
|
|
312
|
+
// useDashboardStore.ts
|
|
313
|
+
interface States {
|
|
314
|
+
activeTabKey: string;
|
|
315
|
+
summaryData: SummaryData;
|
|
316
|
+
summarySpinning: boolean;
|
|
317
|
+
// 탭별 데이터
|
|
318
|
+
tab1Data: Tab1Data[];
|
|
319
|
+
tab2Data: Tab2Data[];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface Actions {
|
|
323
|
+
setActiveTabKey: (key: string) => void;
|
|
324
|
+
callSummaryApi: () => Promise<void>;
|
|
325
|
+
callTab1Api: () => Promise<void>;
|
|
326
|
+
callTab2Api: () => Promise<void>;
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Step 3: 메인 App 컴포넌트
|
|
331
|
+
```typescript
|
|
332
|
+
// App.tsx
|
|
333
|
+
export default function App() {
|
|
334
|
+
const store = useDashboardStore();
|
|
335
|
+
|
|
336
|
+
const handleSearch = useCallback(async () => {
|
|
337
|
+
await Promise.all([
|
|
338
|
+
store.callSummaryApi(),
|
|
339
|
+
store.callTab1Api(),
|
|
340
|
+
store.callTab2Api()
|
|
341
|
+
]);
|
|
342
|
+
}, []);
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<Container>
|
|
346
|
+
<Header>
|
|
347
|
+
<ProgramTitle />
|
|
348
|
+
<Button onClick={handleSearch}>검색</Button>
|
|
349
|
+
<Summary />
|
|
350
|
+
</Header>
|
|
351
|
+
|
|
352
|
+
<PageTabBar>
|
|
353
|
+
<Tabs
|
|
354
|
+
items={tabItems}
|
|
355
|
+
activeKey={store.activeTabKey}
|
|
356
|
+
onChange={store.setActiveTabKey}
|
|
357
|
+
/>
|
|
358
|
+
</PageTabBar>
|
|
359
|
+
|
|
360
|
+
<PanelIndex contentType={store.activeTabKey} />
|
|
361
|
+
</Container>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Step 4: Summary 컴포넌트
|
|
367
|
+
```typescript
|
|
368
|
+
// Summary.tsx
|
|
369
|
+
export function Summary() {
|
|
370
|
+
const summaryData = useDashboardStore(s => s.summaryData);
|
|
371
|
+
const summarySpinning = useDashboardStore(s => s.summarySpinning);
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<Descriptions loading={summarySpinning}>
|
|
375
|
+
<Descriptions.Item label="전체">{summaryData.total}</Descriptions.Item>
|
|
376
|
+
<Descriptions.Item label="활성">{summaryData.active}</Descriptions.Item>
|
|
377
|
+
<Descriptions.Item label="비활성">{summaryData.inactive}</Descriptions.Item>
|
|
378
|
+
</Descriptions>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Step 5: 탭별 Panel 구현
|
|
384
|
+
```typescript
|
|
385
|
+
// PanelIndex.tsx
|
|
386
|
+
export function PanelIndex({ contentType }: { contentType: string }) {
|
|
387
|
+
switch (contentType) {
|
|
388
|
+
case 'tab1':
|
|
389
|
+
return <Tab1Panel />;
|
|
390
|
+
case 'tab2':
|
|
391
|
+
return <Tab2Panel />;
|
|
392
|
+
default:
|
|
393
|
+
return <div>선택된 탭이 없습니다.</div>;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 3. 🌳 트리 관리형 (Tree Management) 개발 순서
|
|
401
|
+
|
|
402
|
+
**대표 예시**: `product.category-mng`, `display.gnb-menu`
|
|
403
|
+
|
|
404
|
+
### Step 1: 기본 구조 설정
|
|
405
|
+
```bash
|
|
406
|
+
your-tree/
|
|
407
|
+
├── App.tsx # 메인 컨테이너
|
|
408
|
+
├── TreeList.tsx # 트리 컴포넌트
|
|
409
|
+
├── DetailForm.tsx # 상세 폼
|
|
410
|
+
├── useTreeStore.ts # Tree Store
|
|
411
|
+
└── utils/
|
|
412
|
+
└── treeUtils.ts # 트리 유틸리티
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Step 2: Tree Store 개발
|
|
416
|
+
```typescript
|
|
417
|
+
// useTreeStore.ts
|
|
418
|
+
interface States {
|
|
419
|
+
treeData: TreeNode[];
|
|
420
|
+
selectedItem?: TreeNode;
|
|
421
|
+
expandedKeys: React.Key[];
|
|
422
|
+
flexGrow: number;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
interface Actions {
|
|
426
|
+
callTreeApi: () => Promise<void>;
|
|
427
|
+
setSelectedItem: (item?: TreeNode) => Promise<void>;
|
|
428
|
+
setExpandedKeys: (keys: React.Key[]) => void;
|
|
429
|
+
setFlexGrow: (width: number) => void;
|
|
430
|
+
handleAddNode: (parentKey?: number) => Promise<void>;
|
|
431
|
+
handleDelNode: (key: number) => Promise<void>;
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Step 3: 메인 App 컴포넌트 (분할 레이아웃)
|
|
436
|
+
```typescript
|
|
437
|
+
// App.tsx
|
|
438
|
+
export default function App() {
|
|
439
|
+
const store = useTreeStore();
|
|
440
|
+
const resizerContainerRef = useRef<HTMLDivElement>(null);
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<Container>
|
|
444
|
+
<Header>
|
|
445
|
+
<ProgramTitle />
|
|
446
|
+
</Header>
|
|
447
|
+
|
|
448
|
+
<Body ref={resizerContainerRef}>
|
|
449
|
+
{/* 왼쪽: 트리 */}
|
|
450
|
+
<Frame style={{ flex: `0 1 ${store.flexGrow}px`, minWidth: "400px" }}>
|
|
451
|
+
<TreeList />
|
|
452
|
+
</Frame>
|
|
453
|
+
|
|
454
|
+
{/* 구분자 */}
|
|
455
|
+
<ColResizer
|
|
456
|
+
containerRef={resizerContainerRef}
|
|
457
|
+
onResize={store.setFlexGrow}
|
|
458
|
+
/>
|
|
459
|
+
|
|
460
|
+
{/* 오른쪽: 상세 폼 */}
|
|
461
|
+
<Frame style={{ minWidth: "500px" }}>
|
|
462
|
+
{store.selectedItem ? <DetailForm /> : <EmptyMsg />}
|
|
463
|
+
</Frame>
|
|
464
|
+
</Body>
|
|
465
|
+
</Container>
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Step 4: TreeList 구현
|
|
471
|
+
```typescript
|
|
472
|
+
// TreeList.tsx
|
|
473
|
+
export function TreeList() {
|
|
474
|
+
const treeData = useTreeStore(s => s.treeData);
|
|
475
|
+
const selectedItem = useTreeStore(s => s.selectedItem);
|
|
476
|
+
const expandedKeys = useTreeStore(s => s.expandedKeys);
|
|
477
|
+
const setExpandedKeys = useTreeStore(s => s.setExpandedKeys);
|
|
478
|
+
const handleClickTreeItem = useTreeStore(s => s.handleClickTreeItem);
|
|
479
|
+
const handleAddNode = useTreeStore(s => s.handleAddNode);
|
|
480
|
+
const handleDelNode = useTreeStore(s => s.handleDelNode);
|
|
481
|
+
|
|
482
|
+
return (
|
|
483
|
+
<TreeContainer>
|
|
484
|
+
<Tree
|
|
485
|
+
treeData={treeData}
|
|
486
|
+
selectedKeys={selectedItem ? [selectedItem.id] : []}
|
|
487
|
+
expandedKeys={expandedKeys}
|
|
488
|
+
onExpand={setExpandedKeys}
|
|
489
|
+
titleRender={(nodeData) => (
|
|
490
|
+
<TreeNode onClick={(e) => handleClickTreeItem(e, nodeData)}>
|
|
491
|
+
<div role="tools">
|
|
492
|
+
<Tooltip title="추가">
|
|
493
|
+
<IconAdd onClick={(e) => {
|
|
494
|
+
e.stopPropagation();
|
|
495
|
+
handleAddNode(nodeData.key);
|
|
496
|
+
}} />
|
|
497
|
+
</Tooltip>
|
|
498
|
+
<Tooltip title="삭제">
|
|
499
|
+
<IconBin onClick={(e) => {
|
|
500
|
+
e.stopPropagation();
|
|
501
|
+
handleDelNode(nodeData.key);
|
|
502
|
+
}} />
|
|
503
|
+
</Tooltip>
|
|
504
|
+
</div>
|
|
505
|
+
{nodeData.title}
|
|
506
|
+
</TreeNode>
|
|
507
|
+
)}
|
|
508
|
+
/>
|
|
509
|
+
</TreeContainer>
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## 4. 📝 단일 폼형 (Single Form) 개발 순서
|
|
517
|
+
|
|
518
|
+
**대표 예시**: `system.clear-cache`, `system.isms-env-config`
|
|
519
|
+
|
|
520
|
+
### Step 1: 기본 구조 설정
|
|
521
|
+
```bash
|
|
522
|
+
your-form/
|
|
523
|
+
├── App.tsx # 메인 폼 컨테이너
|
|
524
|
+
├── FormFields.tsx # 폼 필드 구성
|
|
525
|
+
├── useFormStore.ts # 폼 Store
|
|
526
|
+
└── validations.ts # 유효성 검증
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Step 2: Form Store 개발
|
|
530
|
+
```typescript
|
|
531
|
+
// useFormStore.ts
|
|
532
|
+
interface SaveRequest {
|
|
533
|
+
// 폼 필드들
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
interface States {
|
|
537
|
+
saveRequestValue: SaveRequest;
|
|
538
|
+
saveSpinning: boolean;
|
|
539
|
+
formMode: 'view' | 'edit' | 'create';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
interface Actions {
|
|
543
|
+
setSaveRequestValue: (value: SaveRequest) => void;
|
|
544
|
+
callLoadApi: () => Promise<void>;
|
|
545
|
+
callSaveApi: (data: SaveRequest) => Promise<void>;
|
|
546
|
+
setFormMode: (mode: 'view' | 'edit' | 'create') => void;
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Step 3: 메인 App 컴포넌트
|
|
551
|
+
```typescript
|
|
552
|
+
// App.tsx
|
|
553
|
+
export default function App() {
|
|
554
|
+
const store = useFormStore();
|
|
555
|
+
const [form] = Form.useForm();
|
|
556
|
+
|
|
557
|
+
const handleSave = useCallback(async () => {
|
|
558
|
+
try {
|
|
559
|
+
await form.validateFields();
|
|
560
|
+
const values = form.getFieldsValue();
|
|
561
|
+
await store.callSaveApi(values);
|
|
562
|
+
message.success('저장되었습니다.');
|
|
563
|
+
} catch (error) {
|
|
564
|
+
await errorHandling(error);
|
|
565
|
+
}
|
|
566
|
+
}, [form, store]);
|
|
567
|
+
|
|
568
|
+
return (
|
|
569
|
+
<Container>
|
|
570
|
+
<Header>
|
|
571
|
+
<ProgramTitle />
|
|
572
|
+
<Space>
|
|
573
|
+
<Button onClick={handleSave} loading={store.saveSpinning}>
|
|
574
|
+
저장
|
|
575
|
+
</Button>
|
|
576
|
+
<Button onClick={() => form.resetFields()}>
|
|
577
|
+
초기화
|
|
578
|
+
</Button>
|
|
579
|
+
</Space>
|
|
580
|
+
</Header>
|
|
581
|
+
|
|
582
|
+
<Body>
|
|
583
|
+
<FormCard>
|
|
584
|
+
<Form
|
|
585
|
+
form={form}
|
|
586
|
+
layout="vertical"
|
|
587
|
+
initialValues={store.saveRequestValue}
|
|
588
|
+
onValuesChange={(_, values) => store.setSaveRequestValue(values)}
|
|
589
|
+
>
|
|
590
|
+
<FormFields />
|
|
591
|
+
</Form>
|
|
592
|
+
</FormCard>
|
|
593
|
+
</Body>
|
|
594
|
+
</Container>
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## 5. 💬 게시판형 (Board/BBS) 개발 순서
|
|
602
|
+
|
|
603
|
+
**대표 예시**: `system.bbs`, `cs.faq`
|
|
604
|
+
|
|
605
|
+
### Step 1: 기본 구조 설정
|
|
606
|
+
```bash
|
|
607
|
+
your-bbs/
|
|
608
|
+
├── App.tsx # 메인 컨테이너
|
|
609
|
+
├── ListDataGrid.tsx # 게시글 목록
|
|
610
|
+
├── PostDetail.tsx # 게시글 상세
|
|
611
|
+
├── FormSet.tsx # 게시글 폼
|
|
612
|
+
├── comments/
|
|
613
|
+
│ ├── BbsComments.tsx # 댓글 목록
|
|
614
|
+
│ └── BbsCommentList.tsx
|
|
615
|
+
└── useBbsStore.ts # 게시판 Store
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Step 2: BBS Store 개발
|
|
619
|
+
```typescript
|
|
620
|
+
// useBbsStore.ts
|
|
621
|
+
interface States {
|
|
622
|
+
listData: PostItem[];
|
|
623
|
+
selectedPost?: PostItem;
|
|
624
|
+
comments: CommentItem[];
|
|
625
|
+
formActive: boolean;
|
|
626
|
+
saveRequestValue: SaveRequest;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
interface Actions {
|
|
630
|
+
callListApi: () => Promise<void>;
|
|
631
|
+
setSelectedPost: (post?: PostItem) => void;
|
|
632
|
+
callSaveApi: (data: SaveRequest) => Promise<void>;
|
|
633
|
+
callDeleteApi: (postId: number) => Promise<void>;
|
|
634
|
+
setFormActive: (active: boolean) => void;
|
|
635
|
+
// 댓글 관련
|
|
636
|
+
callCommentsApi: (postId: number) => Promise<void>;
|
|
637
|
+
addComment: (comment: CommentData) => Promise<void>;
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Step 3: 메인 App 컴포넌트 (좌우 분할)
|
|
642
|
+
```typescript
|
|
643
|
+
// App.tsx
|
|
644
|
+
export default function App() {
|
|
645
|
+
const store = useBbsStore();
|
|
646
|
+
const [form] = Form.useForm();
|
|
647
|
+
|
|
648
|
+
return (
|
|
649
|
+
<Container>
|
|
650
|
+
<Header>
|
|
651
|
+
<ProgramTitle />
|
|
652
|
+
<SearchParams />
|
|
653
|
+
</Header>
|
|
654
|
+
|
|
655
|
+
<Body>
|
|
656
|
+
{/* 왼쪽: 목록 */}
|
|
657
|
+
<Frame>
|
|
658
|
+
<FrameHeader>
|
|
659
|
+
게시글 목록
|
|
660
|
+
<Button onClick={() => store.setFormActive(true)}>
|
|
661
|
+
새 게시글
|
|
662
|
+
</Button>
|
|
663
|
+
</FrameHeader>
|
|
664
|
+
<ListDataGrid />
|
|
665
|
+
</Frame>
|
|
666
|
+
|
|
667
|
+
{/* 오른쪽: 상세/폼 */}
|
|
668
|
+
<Frame>
|
|
669
|
+
{store.formActive ? (
|
|
670
|
+
<PostForm form={form} />
|
|
671
|
+
) : (
|
|
672
|
+
<PostDetail />
|
|
673
|
+
)}
|
|
674
|
+
</Frame>
|
|
675
|
+
</Body>
|
|
676
|
+
</Container>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## 공통 개발 패턴
|
|
684
|
+
|
|
685
|
+
### 1. 필수 파일 구조
|
|
686
|
+
```bash
|
|
687
|
+
your-feature/
|
|
688
|
+
├── App.tsx # 메인 컨테이너 (필수)
|
|
689
|
+
├── ListDataGrid.tsx # 목록 그리드 (목록형)
|
|
690
|
+
├── useFeatureStore.ts # Zustand Store (필수)
|
|
691
|
+
├── types.ts # 타입 정의 (선택)
|
|
692
|
+
└── components/ # 하위 컴포넌트들
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### 2. Store 개발 패턴
|
|
696
|
+
```typescript
|
|
697
|
+
// 모든 Store의 공통 구조
|
|
698
|
+
interface States extends MetaData {
|
|
699
|
+
// 로딩 상태
|
|
700
|
+
listSpinning: boolean;
|
|
701
|
+
saveSpinning: boolean;
|
|
702
|
+
deleteSpinning: boolean;
|
|
703
|
+
|
|
704
|
+
// 데이터 상태
|
|
705
|
+
listData: DataItem[];
|
|
706
|
+
selectedItem?: DataItem;
|
|
707
|
+
saveRequestValue: SaveRequest;
|
|
708
|
+
|
|
709
|
+
// UI 상태
|
|
710
|
+
listRequestValue: ListRequest;
|
|
711
|
+
listPage: AXDGPage;
|
|
712
|
+
checkedRowKeys: React.Key[];
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
interface Actions extends PageStoreActions<States> {
|
|
716
|
+
// API 호출
|
|
717
|
+
callListApi: (request?: ListRequest) => Promise<void>;
|
|
718
|
+
callSaveApi: (data: SaveRequest) => Promise<void>;
|
|
719
|
+
callDeleteApi: (id: number) => Promise<void>;
|
|
720
|
+
|
|
721
|
+
// 상태 변경
|
|
722
|
+
setListRequestValue: (value: ListRequest) => void;
|
|
723
|
+
setSelectedItem: (item?: DataItem) => void;
|
|
724
|
+
setSaveRequestValue: (value: SaveRequest) => void;
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### 3. 생명주기 패턴
|
|
729
|
+
```typescript
|
|
730
|
+
// 모든 App 컴포넌트의 공통 생명주기
|
|
731
|
+
export default function App() {
|
|
732
|
+
const store = useYourStore();
|
|
733
|
+
|
|
734
|
+
// 마운트 시 초기화
|
|
735
|
+
useDidMountEffect(() => {
|
|
736
|
+
(async () => {
|
|
737
|
+
try {
|
|
738
|
+
await store.init();
|
|
739
|
+
await store.callListApi(); // 또는 초기 데이터 로딩
|
|
740
|
+
} catch (error) {
|
|
741
|
+
await errorHandling(error);
|
|
742
|
+
}
|
|
743
|
+
})();
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// 언마운트 시 정리
|
|
747
|
+
useUnmountEffect(() => {
|
|
748
|
+
store.destroy();
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// URL 파라미터 처리 (선택)
|
|
752
|
+
const [searchParams] = useSearchParams();
|
|
753
|
+
const paramId = searchParams.get("id");
|
|
754
|
+
|
|
755
|
+
useEffect(() => {
|
|
756
|
+
if (paramId) {
|
|
757
|
+
// 파라미터 기반 초기화
|
|
758
|
+
}
|
|
759
|
+
}, [paramId]);
|
|
760
|
+
|
|
761
|
+
// 컴포넌트 렌더링
|
|
762
|
+
return <div>...</div>;
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### 4. 에러 처리 패턴
|
|
767
|
+
```typescript
|
|
768
|
+
// 모든 비동기 작업의 공통 에러 처리
|
|
769
|
+
const handleApiCall = useCallback(async () => {
|
|
770
|
+
try {
|
|
771
|
+
setSpinning(true);
|
|
772
|
+
await apiCall();
|
|
773
|
+
message.success('성공했습니다.');
|
|
774
|
+
} catch (error) {
|
|
775
|
+
await errorHandling(error);
|
|
776
|
+
} finally {
|
|
777
|
+
setSpinning(false);
|
|
778
|
+
}
|
|
779
|
+
}, [dependencies]);
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
## 체크리스트
|
|
785
|
+
|
|
786
|
+
### 📋 목록 관리형 체크리스트
|
|
787
|
+
|
|
788
|
+
#### Store 개발
|
|
789
|
+
- [ ] ListRequest, DtoItem, SaveRequest 인터페이스 정의
|
|
790
|
+
- [ ] MetaData 인터페이스 정의 (영속화 데이터)
|
|
791
|
+
- [ ] States, Actions 인터페이스 정의
|
|
792
|
+
- [ ] callListApi 구현 (페이징, 정렬, 필터링)
|
|
793
|
+
- [ ] setSelectedItem 구현 (상세 정보 로딩)
|
|
794
|
+
- [ ] 메타데이터 구독 설정 (탭 상태 유지)
|
|
795
|
+
|
|
796
|
+
#### 검색 기능
|
|
797
|
+
- [ ] SearchParams 배열 정의
|
|
798
|
+
- [ ] 날짜 범위 검색 (DTPICKER_DATE_RANGE)
|
|
799
|
+
- [ ] 검색어 구분 + 검색어 (GROUP)
|
|
800
|
+
- [ ] 다중 선택 필터 (SELECT_MULTI, CHECKBOX)
|
|
801
|
+
- [ ] 라디오 버튼 필터 (RADIO)
|
|
802
|
+
- [ ] 검색 조건 초기값 설정
|
|
803
|
+
|
|
804
|
+
#### 목록 그리드
|
|
805
|
+
- [ ] 컬럼 정의 (key, label, width, formatter)
|
|
806
|
+
- [ ] 페이징 설정
|
|
807
|
+
- [ ] 정렬 기능
|
|
808
|
+
- [ ] 행 선택 기능
|
|
809
|
+
- [ ] 체크박스 선택 (필요시)
|
|
810
|
+
- [ ] 더블클릭 이벤트
|
|
811
|
+
|
|
812
|
+
#### 상세 정보
|
|
813
|
+
- [ ] Drawer 또는 Panel 구현
|
|
814
|
+
- [ ] 상세 정보 폼 구성
|
|
815
|
+
- [ ] 편집 모드 지원 (필요시)
|
|
816
|
+
- [ ] 저장/삭제 기능 (필요시)
|
|
817
|
+
|
|
818
|
+
#### 추가 기능
|
|
819
|
+
- [ ] 엑셀 다운로드
|
|
820
|
+
- [ ] 일괄 처리 (선택 항목 대상)
|
|
821
|
+
- [ ] 권한 체크 (programFn)
|
|
822
|
+
- [ ] URL 파라미터 처리
|
|
823
|
+
|
|
824
|
+
### 📊 대시보드형 체크리스트
|
|
825
|
+
|
|
826
|
+
#### Store 개발
|
|
827
|
+
- [ ] 탭별 데이터 상태 정의
|
|
828
|
+
- [ ] activeTabKey 상태 관리
|
|
829
|
+
- [ ] 각 탭별 API 호출 액션
|
|
830
|
+
- [ ] Summary API 호출 액션
|
|
831
|
+
|
|
832
|
+
#### Summary 컴포넌트
|
|
833
|
+
- [ ] 요약 데이터 표시 (Descriptions)
|
|
834
|
+
- [ ] 로딩 상태 처리
|
|
835
|
+
- [ ] 숫자 포맷팅
|
|
836
|
+
- [ ] 기간 정보 표시
|
|
837
|
+
|
|
838
|
+
#### 탭 구성
|
|
839
|
+
- [ ] Tabs 컴포넌트 설정
|
|
840
|
+
- [ ] 탭별 Panel 컴포넌트 구현
|
|
841
|
+
- [ ] PanelIndex 라우터 구현
|
|
842
|
+
- [ ] 탭 변경 시 데이터 로딩
|
|
843
|
+
|
|
844
|
+
#### 차트/통계
|
|
845
|
+
- [ ] Chart 라이브러리 연동
|
|
846
|
+
- [ ] 데이터 변환 로직
|
|
847
|
+
- [ ] 반응형 크기 조정
|
|
848
|
+
|
|
849
|
+
### 🌳 트리 관리형 체크리스트
|
|
850
|
+
|
|
851
|
+
#### Store 개발
|
|
852
|
+
- [ ] TreeNode 인터페이스 정의
|
|
853
|
+
- [ ] treeData 상태 관리
|
|
854
|
+
- [ ] expandedKeys 상태 관리
|
|
855
|
+
- [ ] flexGrow 상태 관리 (컬럼 크기)
|
|
856
|
+
- [ ] 드래그앤드롭 처리 (필요시)
|
|
857
|
+
|
|
858
|
+
#### 트리 컴포넌트
|
|
859
|
+
- [ ] Tree 컴포넌트 설정
|
|
860
|
+
- [ ] titleRender 커스터마이징
|
|
861
|
+
- [ ] 노드별 액션 버튼 (추가/삭제)
|
|
862
|
+
- [ ] 트리 데이터 변환 로직
|
|
863
|
+
|
|
864
|
+
#### 상세 폼
|
|
865
|
+
- [ ] 선택된 노드 정보 표시
|
|
866
|
+
- [ ] 폼 필드 구성
|
|
867
|
+
- [ ] 저장/삭제 기능
|
|
868
|
+
- [ ] 유효성 검증
|
|
869
|
+
|
|
870
|
+
#### 레이아웃
|
|
871
|
+
- [ ] ColResizer 구현
|
|
872
|
+
- [ ] 좌우 분할 레이아웃
|
|
873
|
+
- [ ] 최소 너비 설정
|
|
874
|
+
- [ ] EmptyMsg 처리
|
|
875
|
+
|
|
876
|
+
### 📝 단일 폼형 체크리스트
|
|
877
|
+
|
|
878
|
+
#### Store 개발
|
|
879
|
+
- [ ] SaveRequest 인터페이스 정의
|
|
880
|
+
- [ ] formMode 상태 관리
|
|
881
|
+
- [ ] callSaveApi, callLoadApi 구현
|
|
882
|
+
|
|
883
|
+
#### 폼 구성
|
|
884
|
+
- [ ] Form 컴포넌트 설정
|
|
885
|
+
- [ ] FormFields 구성 요소
|
|
886
|
+
- [ ] 초기값 설정
|
|
887
|
+
- [ ] onValuesChange 핸들러
|
|
888
|
+
|
|
889
|
+
#### 유효성 검증
|
|
890
|
+
- [ ] 필드별 validation rules
|
|
891
|
+
- [ ] 커스텀 validator 함수
|
|
892
|
+
- [ ] 에러 메시지 처리
|
|
893
|
+
|
|
894
|
+
#### 액션 버튼
|
|
895
|
+
- [ ] 저장 버튼
|
|
896
|
+
- [ ] 초기화 버튼
|
|
897
|
+
- [ ] 권한별 버튼 표시 제어
|
|
898
|
+
|
|
899
|
+
### 💬 게시판형 체크리스트
|
|
900
|
+
|
|
901
|
+
#### Store 개발
|
|
902
|
+
- [ ] PostItem, CommentItem 인터페이스 정의
|
|
903
|
+
- [ ] formActive 상태 관리
|
|
904
|
+
- [ ] 게시글 CRUD 액션
|
|
905
|
+
- [ ] 댓글 관리 액션
|
|
906
|
+
|
|
907
|
+
#### 목록 기능
|
|
908
|
+
- [ ] 게시글 목록 표시
|
|
909
|
+
- [ ] 검색 기능
|
|
910
|
+
- [ ] 페이징 처리
|
|
911
|
+
- [ ] 정렬 기능
|
|
912
|
+
|
|
913
|
+
#### 상세/폼
|
|
914
|
+
- [ ] 게시글 상세 표시
|
|
915
|
+
- [ ] 게시글 작성/수정 폼
|
|
916
|
+
- [ ] 에디터 연동 (필요시)
|
|
917
|
+
- [ ] 첨부파일 처리 (필요시)
|
|
918
|
+
|
|
919
|
+
#### 댓글 시스템
|
|
920
|
+
- [ ] 댓글 목록 표시
|
|
921
|
+
- [ ] 댓글 작성 폼
|
|
922
|
+
- [ ] 대댓글 처리 (필요시)
|
|
923
|
+
- [ ] 댓글 삭제 기능
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## 마무리
|
|
928
|
+
|
|
929
|
+
이 가이드를 통해 화면 구성 타입을 먼저 파악하고, 해당 타입에 맞는 개발 순서를 따라가면 일관성 있고 효율적인 개발이 가능합니다.
|
|
930
|
+
|
|
931
|
+
### 🎯 핵심 포인트
|
|
932
|
+
1. **타입 분류가 우선**: 화면의 성격을 먼저 파악하고 적절한 타입을 선택
|
|
933
|
+
2. **Store부터 시작**: 비즈니스 로직과 상태 관리를 먼저 구현
|
|
934
|
+
3. **컴포넌트는 단순하게**: UI 컴포넌트는 Store의 상태와 액션만 연결
|
|
935
|
+
4. **체크리스트 활용**: 놓치기 쉬운 기능들을 체크리스트로 확인
|
|
936
|
+
5. **공통 패턴 준수**: 프로젝트 전체의 일관성 유지
|
|
937
|
+
|
|
938
|
+
---
|
|
939
|
+
|
|
940
|
+
## 확장 화면 타입 (20가지)
|
|
941
|
+
|
|
942
|
+
### 7. 🖼️ 이미지 썸네일 그리드 + 모달형 (Image Grid Modal)
|
|
943
|
+
**특징**: 이미지 썸네일이 포함된 그리드 + FormModal을 통한 상세 관리
|
|
944
|
+
**예시**: 배너 관리, 이미지 갤러리
|
|
945
|
+
**대표 URL**: `/NH/system/app/banner` (앱 배너 관리)
|
|
946
|
+
**구성요소**:
|
|
947
|
+
- SearchParams (검색 폼)
|
|
948
|
+
- ListDataGrid with ThumbPreview (썸네일 그리드)
|
|
949
|
+
- FormModal (배너 등록/수정 모달)
|
|
950
|
+
- SortModal (순서 관리 모달)
|
|
951
|
+
|
|
952
|
+

|
|
953
|
+
|
|
954
|
+
**주요 특징**:
|
|
955
|
+
- 탭으로 구분된 위치별 배너 관리
|
|
956
|
+
- 이미지 썸네일 미리보기
|
|
957
|
+
- 모달을 통한 배너 등록/수정
|
|
958
|
+
- 드래그앤드롭 순서 관리
|
|
959
|
+
- 노출 기간 설정 기능
|
|
960
|
+
|
|
961
|
+
### 8. 📝 일반 그리드 목록 + 모달형 (Grid Modal)
|
|
962
|
+
**특징**: 일반 그리드 + FormModal을 통한 CRUD 처리
|
|
963
|
+
**예시**: SEO 관리, 설정 관리
|
|
964
|
+
**대표 URL**: `/NH/system/website/seo` (SEO 관리)
|
|
965
|
+
**구성요소**:
|
|
966
|
+
- SearchParams (날짜범위, 검색조건)
|
|
967
|
+
- ListDataGrid (일반 목록 그리드)
|
|
968
|
+
- FormModal (등록/수정 모달)
|
|
969
|
+
|
|
970
|
+

|
|
971
|
+
|
|
972
|
+
**주요 특징**:
|
|
973
|
+
- 기간별 검색 필터
|
|
974
|
+
- 제목/설명 검색 구분
|
|
975
|
+
- 모달창을 통한 상세 등록/수정
|
|
976
|
+
- 삭제 기능 포함
|
|
977
|
+
|
|
978
|
+
### 9. ☑️ 체크박스 그리드 + 인라인 에디터형 (Checkbox Inline Editor)
|
|
979
|
+
**특징**: 체크박스 선택 + 그리드 내 직접 편집
|
|
980
|
+
**예시**: 금지어 관리
|
|
981
|
+
**대표 URL**: `/NH/system/general/forbidden-word` (금지어 관리)
|
|
982
|
+
**구성요소**:
|
|
983
|
+
- SearchParams (검색 조건)
|
|
984
|
+
- ListDataGrid with Checkbox (체크박스 그리드)
|
|
985
|
+
- Inline Editor (인라인 편집)
|
|
986
|
+
- Bulk Actions (일괄 처리)
|
|
987
|
+
|
|
988
|
+

|
|
989
|
+
|
|
990
|
+
**주요 특징**:
|
|
991
|
+
- 체크박스로 다중 선택
|
|
992
|
+
- 그리드 셀 내에서 직접 편집
|
|
993
|
+
- 일괄 추가/삭제 기능
|
|
994
|
+
- 실시간 저장
|
|
995
|
+
|
|
996
|
+
### 10. 📄 타이틀 셀병합 그리드 + 모달 + 에디터형 (Title Merged Grid Modal Editor)
|
|
997
|
+
**특징**: 셀 병합된 그리드 + 에디터가 포함된 모달
|
|
998
|
+
**예시**: 이용약관 관리
|
|
999
|
+
**대표 URL**: `/NH/system/general/terms-conditions` (이용약관 관리)
|
|
1000
|
+
**구성요소**:
|
|
1001
|
+
- SearchParams (복합 검색)
|
|
1002
|
+
- ListDataGrid with Merged Cells (셀 병합 그리드)
|
|
1003
|
+
- FormModal with Editor (에디터 모달)
|
|
1004
|
+
|
|
1005
|
+

|
|
1006
|
+
|
|
1007
|
+
**주요 특징**:
|
|
1008
|
+
- 정책별 셀 병합 표시
|
|
1009
|
+
- 버전별 약관 관리
|
|
1010
|
+
- 에디터를 통한 약관 내용 편집
|
|
1011
|
+
- 기간별 검색 필터
|
|
1012
|
+
- 사용여부 관리
|
|
1013
|
+
|
|
1014
|
+
### 11. ↔️ 좌우 그리드 목록형 (Dual Grid Layout)
|
|
1015
|
+
**특징**: 좌우 두 개의 그리드로 구성
|
|
1016
|
+
**예시**: 환경설정 관리
|
|
1017
|
+
**대표 URL**: `/NH/system/general/isms-env-config` (ISMS 환경설정)
|
|
1018
|
+
**구성요소**:
|
|
1019
|
+
- Left Grid (카테고리/그룹)
|
|
1020
|
+
- Right Grid (상세 항목)
|
|
1021
|
+
- SearchParams (필터링)
|
|
1022
|
+
|
|
1023
|
+

|
|
1024
|
+
|
|
1025
|
+
**주요 특징**:
|
|
1026
|
+
- 좌측 그리드에서 카테고리 선택
|
|
1027
|
+
- 우측 그리드에서 해당 카테고리의 상세 항목 표시
|
|
1028
|
+
- 연동된 필터링 시스템
|
|
1029
|
+
|
|
1030
|
+
### 12. 📊 StatDataGrid 사용형 (Statistical Grid)
|
|
1031
|
+
**특징**: 통계 데이터를 위한 특수 그리드
|
|
1032
|
+
**예시**: 캐시 클리어
|
|
1033
|
+
**대표 URL**: `/NH/system/general/clear-cache` (캐시 클리어)
|
|
1034
|
+
**구성요소**:
|
|
1035
|
+
- StatDataGrid (통계용 그리드)
|
|
1036
|
+
- Action Buttons (실행 버튼)
|
|
1037
|
+
- Status Display (상태 표시)
|
|
1038
|
+
|
|
1039
|
+

|
|
1040
|
+
|
|
1041
|
+
**주요 특징**:
|
|
1042
|
+
- 캐시 통계 정보 표시
|
|
1043
|
+
- 실행 버튼을 통한 캐시 클리어
|
|
1044
|
+
- 실시간 상태 업데이트
|
|
1045
|
+
|
|
1046
|
+
### 13. 🔐 좌그리드 + 우상세폼 + 하단상세형 (Grid Form Detail)
|
|
1047
|
+
**특징**: 3단 구성 - 그리드 + 상세폼 + 하단 상세 정보
|
|
1048
|
+
**예시**: 사용자 역할 관리
|
|
1049
|
+
**대표 URL**: `/NH/system/user-roles/roles` (역할 관리)
|
|
1050
|
+
**구성요소**:
|
|
1051
|
+
- Left Grid (역할 목록)
|
|
1052
|
+
- Right Form (역할 상세)
|
|
1053
|
+
- Bottom Detail (권한 상세)
|
|
1054
|
+
|
|
1055
|
+

|
|
1056
|
+
|
|
1057
|
+
**주요 특징**:
|
|
1058
|
+
- 3단 레이아웃 구성
|
|
1059
|
+
- 역할별 권한 관리
|
|
1060
|
+
- 계층적 정보 표시
|
|
1061
|
+
|
|
1062
|
+
### 14. 👤 좌그리드 + 우상세폼형 (Grid Form)
|
|
1063
|
+
**특징**: 좌우 분할 - 그리드 + 상세 폼
|
|
1064
|
+
**예시**: 사용자 관리
|
|
1065
|
+
**대표 URL**: `/NH/system/user-roles/users` (사용자 관리)
|
|
1066
|
+
**구성요소**:
|
|
1067
|
+
- Left Grid (사용자 목록)
|
|
1068
|
+
- Right Form (사용자 상세 폼)
|
|
1069
|
+
|
|
1070
|
+

|
|
1071
|
+
|
|
1072
|
+
**주요 특징**:
|
|
1073
|
+
- 사용자 목록과 상세 정보 분할 표시
|
|
1074
|
+
- 실시간 연동
|
|
1075
|
+
- 폼 기반 상세 편집
|
|
1076
|
+
|
|
1077
|
+
### 15. 📝 좌드래그박스 + 우상세폼 + 하단트리형 (Drag Box Form Tree)
|
|
1078
|
+
**특징**: 드래그 박스 + 상세 폼 + 트리 목록
|
|
1079
|
+
**예시**: 메뉴 관리
|
|
1080
|
+
**대표 URL**: `/NH/system/menu` (메뉴 관리)
|
|
1081
|
+
**구성요소**:
|
|
1082
|
+
- Left Drag Box (드래그 박스)
|
|
1083
|
+
- Right Form (메뉴 상세 폼)
|
|
1084
|
+
- Bottom Tree (하위 메뉴 트리)
|
|
1085
|
+
|
|
1086
|
+

|
|
1087
|
+
|
|
1088
|
+
**주요 특징**:
|
|
1089
|
+
- 드래그앤드롭으로 메뉴 이동
|
|
1090
|
+
- 메뉴 상세 정보 편집
|
|
1091
|
+
- 계층적 메뉴 구조 관리
|
|
1092
|
+
|
|
1093
|
+
### 16. ➕ 모달 행추가삭제형 (Modal Row Management)
|
|
1094
|
+
**특징**: 모달창에서 행 추가/삭제 기능
|
|
1095
|
+
**예시**: 부서 관리
|
|
1096
|
+
**대표 URL**: `/NH/b2b/mng-dept` (부서 관리)
|
|
1097
|
+
**구성요소**:
|
|
1098
|
+
- ListDataGrid (기본 목록)
|
|
1099
|
+
- FormModal with Row Actions (행 관리 모달)
|
|
1100
|
+
|
|
1101
|
+

|
|
1102
|
+
|
|
1103
|
+
**주요 특징**:
|
|
1104
|
+
- 모달창 내에서 동적 행 추가/삭제
|
|
1105
|
+
- 실시간 데이터 업데이트
|
|
1106
|
+
- 일괄 저장 기능
|
|
1107
|
+
|
|
1108
|
+
### 17. 🖼️ 모달 이미지업로드형 (Modal Image Upload)
|
|
1109
|
+
**특징**: 모달창에서 이미지 업로드 기능
|
|
1110
|
+
**예시**: 소개 사례
|
|
1111
|
+
**대표 URL**: `/NH/b2b/intro-case` (소개 사례)
|
|
1112
|
+
**구성요소**:
|
|
1113
|
+
- ListDataGrid (사례 목록)
|
|
1114
|
+
- FormModal with Image Upload (이미지 업로드 모달)
|
|
1115
|
+
|
|
1116
|
+

|
|
1117
|
+
|
|
1118
|
+
**주요 특징**:
|
|
1119
|
+
- 모달창에서 이미지 업로드/관리
|
|
1120
|
+
- 썸네일 미리보기
|
|
1121
|
+
- 다중 이미지 처리
|
|
1122
|
+
|
|
1123
|
+
### 18. 📋 모달 내용테이블형 (Modal Content Table)
|
|
1124
|
+
**특징**: 모달창에서 테이블 형태 내용 표시
|
|
1125
|
+
**예시**: 견적 상담
|
|
1126
|
+
**대표 URL**: `/NH/b2b/estimate-consulting` (견적 상담)
|
|
1127
|
+
**구성요소**:
|
|
1128
|
+
- ListDataGrid (상담 목록)
|
|
1129
|
+
- FormModal with Content Table (내용 테이블 모달)
|
|
1130
|
+
|
|
1131
|
+

|
|
1132
|
+
|
|
1133
|
+
**주요 특징**:
|
|
1134
|
+
- 모달창에서 테이블 형태 내용 관리
|
|
1135
|
+
- 구조화된 데이터 입력
|
|
1136
|
+
- 동적 테이블 생성
|
|
1137
|
+
|
|
1138
|
+
### 19. ✉️ 모달 추가삭제 기능형 (Modal Add Delete Function)
|
|
1139
|
+
**특징**: 함수 호출로 열리는 모달에서 추가/삭제 기능
|
|
1140
|
+
**예시**: 관리자 이메일
|
|
1141
|
+
**대표 URL**: `func://openAdminEmailsModal?type=report-center` (관리자 이메일 모달)
|
|
1142
|
+
**구성요소**:
|
|
1143
|
+
- Function-based Modal (함수 기반 모달)
|
|
1144
|
+
- Dynamic Content (동적 컨텐츠)
|
|
1145
|
+
- CRUD Operations (추가/삭제 기능)
|
|
1146
|
+
|
|
1147
|
+

|
|
1148
|
+
|
|
1149
|
+
**주요 특징**:
|
|
1150
|
+
- 프로그래매틱 모달 호출
|
|
1151
|
+
- 타입별 모달 컨텐츠 변경
|
|
1152
|
+
- 실시간 데이터 관리
|
|
1153
|
+
|
|
1154
|
+
### 20. 🏢 체크박스 모달 드래그 서브항목형 (Checkbox Modal Drag Sub)
|
|
1155
|
+
**특징**: 체크박스 + 모달창에서 드래그 + 서브 항목 관리
|
|
1156
|
+
**예시**: 회사 연혁
|
|
1157
|
+
**대표 URL**: `NH/kdh/company-history` (회사 연혁)
|
|
1158
|
+
**구성요소**:
|
|
1159
|
+
- Checkbox Grid (체크박스 그리드)
|
|
1160
|
+
- FormModal with Drag (드래그 모달)
|
|
1161
|
+
- Sub Item Management (서브 항목 관리)
|
|
1162
|
+
|
|
1163
|
+

|
|
1164
|
+
|
|
1165
|
+
**주요 특징**:
|
|
1166
|
+
- 체크박스로 다중 선택
|
|
1167
|
+
- 모달창에서 드래그앤드롭 순서 변경
|
|
1168
|
+
- 서브 항목 추가/삭제 기능
|
|
1169
|
+
|
|
1170
|
+
### 21. 📋 탭 서브탭 일괄등록형 (Tab SubTab Bulk Register)
|
|
1171
|
+
**특징**: 탭과 서브탭을 통한 일괄 등록 시스템
|
|
1172
|
+
**예시**: 사업 영역
|
|
1173
|
+
**대표 URL**: `/NH/kdh/biz-area` (사업 영역)
|
|
1174
|
+
**구성요소**:
|
|
1175
|
+
- Main Tabs (메인 탭)
|
|
1176
|
+
- Sub Tabs (서브 탭)
|
|
1177
|
+
- Bulk Register Form (일괄 등록 폼)
|
|
1178
|
+
|
|
1179
|
+

|
|
1180
|
+
|
|
1181
|
+
**주요 특징**:
|
|
1182
|
+
- 2단계 탭 네비게이션
|
|
1183
|
+
- 탭별 일괄 등록 기능
|
|
1184
|
+
- 카테고리별 데이터 관리
|
|
1185
|
+
|
|
1186
|
+
### 22. 📁 모달 파일업로드형 (Modal File Upload)
|
|
1187
|
+
**특징**: 모달창에서 다양한 파일 업로드 방식
|
|
1188
|
+
**예시**: 디지털 상품, 아카이브 보고서, 공고사항, 사업보고서
|
|
1189
|
+
**대표 URL**:
|
|
1190
|
+
- `/NH/kdh/pr-center/digital-goods` (파일 업로드)
|
|
1191
|
+
- `/NH/kdh/sustainability/archive-reports` (단건 업로드)
|
|
1192
|
+
- `/NH/kdh/invest/public-notices` (드래그 업로드)
|
|
1193
|
+
- `/NH/kdh/invest/IR/business-reports` (고정 파일 업로드)
|
|
1194
|
+
|
|
1195
|
+
**구성요소**:
|
|
1196
|
+
- ListDataGrid (파일 목록)
|
|
1197
|
+
- FormModal with File Upload (파일 업로드 모달)
|
|
1198
|
+
- Upload Variations (업로드 방식 선택)
|
|
1199
|
+
|
|
1200
|
+

|
|
1201
|
+
|
|
1202
|
+
**주요 특징**:
|
|
1203
|
+
- 다중 파일 업로드
|
|
1204
|
+
- 단건 파일 업로드
|
|
1205
|
+
- 드래그앤드롭 업로드
|
|
1206
|
+
- 고정 파일 형식 업로드
|
|
1207
|
+
- 파일 미리보기
|
|
1208
|
+
|
|
1209
|
+
### 23. 🎯 드로우모달형 (Draw Modal)
|
|
1210
|
+
**특징**: Drawer와 Modal이 결합된 형태
|
|
1211
|
+
**예시**: 회원 목록
|
|
1212
|
+
**대표 URL**: `/NH/member/member-list` (회원 목록)
|
|
1213
|
+
**구성요소**:
|
|
1214
|
+
- ListDataGrid (회원 목록)
|
|
1215
|
+
- DetailDrawer (상세 정보 drawer)
|
|
1216
|
+
- ActionModal (액션 모달)
|
|
1217
|
+
|
|
1218
|
+

|
|
1219
|
+
|
|
1220
|
+
**주요 특징**:
|
|
1221
|
+
- Drawer로 상세 정보 표시
|
|
1222
|
+
- 추가 액션을 위한 모달 팝업
|
|
1223
|
+
- 단계별 사용자 경험
|
|
1224
|
+
|
|
1225
|
+
### 24. 🎪 모달 내 모달형 (Modal in Modal)
|
|
1226
|
+
**특징**: 모달창에서 또 다른 모달 호출
|
|
1227
|
+
**예시**: 타임딜, 상품 마스터, 상품 배송 설정
|
|
1228
|
+
**대표 URL**:
|
|
1229
|
+
- `/NH/benefit/time-deal` (타임딜)
|
|
1230
|
+
- `/NH/product/product-common/product-master` (상품 마스터)
|
|
1231
|
+
- `/NH/product/setting/product-delivery-config` (배송 설정)
|
|
1232
|
+
|
|
1233
|
+
**구성요소**:
|
|
1234
|
+
- Primary Modal (1차 모달)
|
|
1235
|
+
- Secondary Modal (2차 모달)
|
|
1236
|
+
- Modal Stack Management (모달 스택 관리)
|
|
1237
|
+
|
|
1238
|
+

|
|
1239
|
+
|
|
1240
|
+
**주요 특징**:
|
|
1241
|
+
- 다단계 모달 시스템
|
|
1242
|
+
- 모달 스택 관리
|
|
1243
|
+
- 복잡한 워크플로우 처리
|
|
1244
|
+
|
|
1245
|
+
### 25. 📑 모달 탭구현형 (Modal Tab Implementation)
|
|
1246
|
+
**특징**: 모달창 내에서 탭을 통한 기능 분리
|
|
1247
|
+
**예시**: 상품 마스터
|
|
1248
|
+
**대표 URL**: `/NH/product/product-common/product-master` (상품 마스터)
|
|
1249
|
+
**구성요소**:
|
|
1250
|
+
- FormModal (메인 모달)
|
|
1251
|
+
- Inner Tabs (모달 내 탭)
|
|
1252
|
+
- Tab Content Panels (탭별 컨텐츠)
|
|
1253
|
+
|
|
1254
|
+

|
|
1255
|
+
|
|
1256
|
+
**주요 특징**:
|
|
1257
|
+
- 모달 내 탭 네비게이션
|
|
1258
|
+
- 탭별 독립적 기능
|
|
1259
|
+
- 통합 저장 시스템
|
|
1260
|
+
|
|
1261
|
+
### 26. ⚖️ 모달 데이터 좌우이동형 (Modal Data Transfer)
|
|
1262
|
+
**특징**: 모달창에서 데이터를 좌우로 이동하는 선택 시스템
|
|
1263
|
+
**예시**: 필터 설정
|
|
1264
|
+
**대표 URL**: `/NH/product/product-common/filter` (필터 설정)
|
|
1265
|
+
**구성요소**:
|
|
1266
|
+
- Source List (원본 목록)
|
|
1267
|
+
- Target List (대상 목록)
|
|
1268
|
+
- Transfer Controls (이동 컨트롤)
|
|
1269
|
+
- FormModal Container (모달 컨테이너)
|
|
1270
|
+
|
|
1271
|
+

|
|
1272
|
+
|
|
1273
|
+
**주요 특징**:
|
|
1274
|
+
- 좌우 데이터 이동 인터페이스
|
|
1275
|
+
- 다중 선택 및 이동
|
|
1276
|
+
- 검색 및 필터링
|
|
1277
|
+
- 순서 조정 기능
|
|
1278
|
+
|
|
1279
|
+
### 27. 📊 모달 테이블드래그형 (Modal Table Drag)
|
|
1280
|
+
**특징**: 모달창 내에서 테이블 행의 드래그앤드롭 순서 변경
|
|
1281
|
+
**예시**: 추천 상품
|
|
1282
|
+
**대표 URL**: `/NH/display/personal/recommended-product` (추천 상품)
|
|
1283
|
+
**구성요소**:
|
|
1284
|
+
- FormModal (모달 컨테이너)
|
|
1285
|
+
- Draggable Table (드래그 가능 테이블)
|
|
1286
|
+
- Sort Controls (순서 제어)
|
|
1287
|
+
|
|
1288
|
+

|
|
1289
|
+
|
|
1290
|
+
**주요 특징**:
|
|
1291
|
+
- 테이블 행 드래그앤드롭
|
|
1292
|
+
- 실시간 순서 변경
|
|
1293
|
+
- 시각적 드래그 피드백
|
|
1294
|
+
|
|
1295
|
+
## 확장 타입별 개발 순서
|
|
1296
|
+
|
|
1297
|
+
### 7. 🖼️ 이미지 썸네일 그리드 + 모달형 개발 순서
|
|
1298
|
+
|
|
1299
|
+
**Step 1: 기본 구조 설정**
|
|
1300
|
+
```bash
|
|
1301
|
+
image-grid-modal/
|
|
1302
|
+
├── App.tsx # 메인 컨테이너
|
|
1303
|
+
├── ListDataGrid.tsx # 썸네일 포함 그리드
|
|
1304
|
+
├── FormModal.tsx # 이미지 등록/수정 모달
|
|
1305
|
+
├── SortModal.tsx # 순서 관리 모달
|
|
1306
|
+
├── useStore.ts # Store
|
|
1307
|
+
└── components/
|
|
1308
|
+
├── ThumbPreview.tsx # 썸네일 미리보기
|
|
1309
|
+
└── ImageUpload.tsx # 이미지 업로드
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
**Step 2: Store 개발**
|
|
1313
|
+
```typescript
|
|
1314
|
+
interface States {
|
|
1315
|
+
// 탭 상태
|
|
1316
|
+
bannerLocTp: BannerLocTp[];
|
|
1317
|
+
activeBannerLocTp: string;
|
|
1318
|
+
|
|
1319
|
+
// 목록 데이터
|
|
1320
|
+
listData: Banner[];
|
|
1321
|
+
listSpinning: boolean;
|
|
1322
|
+
|
|
1323
|
+
// 이미지 관리
|
|
1324
|
+
uploadedImages: ImageInfo[];
|
|
1325
|
+
sortModalVisible: boolean;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
interface Actions {
|
|
1329
|
+
setActiveBannerLocTp: (key: string) => void;
|
|
1330
|
+
callListApi: (params?: ListRequest) => Promise<void>;
|
|
1331
|
+
openBannerModal: (item?: Banner) => Promise<void>;
|
|
1332
|
+
openSortModal: () => Promise<void>;
|
|
1333
|
+
handleImageUpload: (file: File) => Promise<string>;
|
|
1334
|
+
}
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
**Step 3: 메인 App 컴포넌트**
|
|
1338
|
+
```typescript
|
|
1339
|
+
export default function App() {
|
|
1340
|
+
return (
|
|
1341
|
+
<Container>
|
|
1342
|
+
<Header>
|
|
1343
|
+
<ProgramTitle />
|
|
1344
|
+
</Header>
|
|
1345
|
+
|
|
1346
|
+
{/* 탭 기반 위치 선택 */}
|
|
1347
|
+
<PageSearchBar>
|
|
1348
|
+
<Tabs
|
|
1349
|
+
activeKey={activeBannerLocTp}
|
|
1350
|
+
onChange={setActiveBannerLocTp}
|
|
1351
|
+
items={bannerLocTp.map(item => ({
|
|
1352
|
+
key: item.bannerLocTpcd,
|
|
1353
|
+
label: item.bannerLocTpnm
|
|
1354
|
+
}))}
|
|
1355
|
+
/>
|
|
1356
|
+
</PageSearchBar>
|
|
1357
|
+
|
|
1358
|
+
{/* 검색 조건 */}
|
|
1359
|
+
<Header>
|
|
1360
|
+
<SearchParams
|
|
1361
|
+
params={[
|
|
1362
|
+
{
|
|
1363
|
+
label: "카테고리",
|
|
1364
|
+
name: "ctgrNo",
|
|
1365
|
+
type: SearchParamType.SELECT,
|
|
1366
|
+
options: productCategoryList,
|
|
1367
|
+
hidden: activeBannerLocTp !== "1"
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
label: "채널",
|
|
1371
|
+
name: "mdiaClcd",
|
|
1372
|
+
type: SearchParamType.RADIO,
|
|
1373
|
+
options: MDIA_CLCD?.options
|
|
1374
|
+
}
|
|
1375
|
+
]}
|
|
1376
|
+
/>
|
|
1377
|
+
</Header>
|
|
1378
|
+
|
|
1379
|
+
<Body>
|
|
1380
|
+
<ListDataGrid />
|
|
1381
|
+
</Body>
|
|
1382
|
+
</Container>
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
**Step 4: 썸네일 그리드 구현**
|
|
1388
|
+
```typescript
|
|
1389
|
+
export function ListDataGrid() {
|
|
1390
|
+
const columns = [
|
|
1391
|
+
{
|
|
1392
|
+
key: "bannerNm",
|
|
1393
|
+
label: "배너명",
|
|
1394
|
+
width: 200
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
key: "image",
|
|
1398
|
+
label: "이미지",
|
|
1399
|
+
width: 180,
|
|
1400
|
+
itemRender: ({ values }) => (
|
|
1401
|
+
<Flex gap={6} justify="center">
|
|
1402
|
+
{values.pcImageFlInfo && (
|
|
1403
|
+
<ThumbPreview url={values.pcImageFlInfo.prevewUrlCtnts} />
|
|
1404
|
+
)}
|
|
1405
|
+
{values.moImageFlInfo && (
|
|
1406
|
+
<ThumbPreview url={values.moImageFlInfo.prevewUrlCtnts} />
|
|
1407
|
+
)}
|
|
1408
|
+
</Flex>
|
|
1409
|
+
)
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
key: "expsrBgnDtm",
|
|
1413
|
+
label: "노출기간",
|
|
1414
|
+
width: 150,
|
|
1415
|
+
itemRender: ({ values }) => (
|
|
1416
|
+
values.expsrBgnDtm ? (
|
|
1417
|
+
<Flex wrap gap={5} justify="center">
|
|
1418
|
+
<span>{dayjs(values.expsrBgnDtm).format(DT_FORMAT.DATETIME_HHMM)}</span>
|
|
1419
|
+
<span>~</span>
|
|
1420
|
+
<span>{dayjs(values.expsrEndDtm).format(DT_FORMAT.DATETIME_HHMM)}</span>
|
|
1421
|
+
</Flex>
|
|
1422
|
+
) : "제한없음"
|
|
1423
|
+
)
|
|
1424
|
+
}
|
|
1425
|
+
];
|
|
1426
|
+
|
|
1427
|
+
return (
|
|
1428
|
+
<>
|
|
1429
|
+
<FormHeader>
|
|
1430
|
+
<Flex gap={6}>
|
|
1431
|
+
{programFn?.fn02 && (
|
|
1432
|
+
<Button onClick={handleSort} icon={<IconSettingsVertical />}>
|
|
1433
|
+
순서관리
|
|
1434
|
+
</Button>
|
|
1435
|
+
)}
|
|
1436
|
+
</Flex>
|
|
1437
|
+
<Flex gap={6}>
|
|
1438
|
+
{programFn?.fn02 && (
|
|
1439
|
+
<Button type="primary" onClick={handleAddItem}>
|
|
1440
|
+
등록
|
|
1441
|
+
</Button>
|
|
1442
|
+
)}
|
|
1443
|
+
</Flex>
|
|
1444
|
+
</FormHeader>
|
|
1445
|
+
|
|
1446
|
+
<DataGrid
|
|
1447
|
+
columns={columns}
|
|
1448
|
+
data={listData}
|
|
1449
|
+
onClick={onClickItem}
|
|
1450
|
+
/>
|
|
1451
|
+
</>
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
```
|
|
1455
|
+
|
|
1456
|
+
### 8-27. 기타 확장 타입 개발 가이드
|
|
1457
|
+
|
|
1458
|
+
각 확장 타입은 다음과 같은 공통 패턴을 따릅니다:
|
|
1459
|
+
|
|
1460
|
+
**공통 개발 단계**:
|
|
1461
|
+
1. **타입 특성 파악**: 해당 화면의 고유 기능 식별
|
|
1462
|
+
2. **Store 설계**: 타입별 특수 상태 및 액션 정의
|
|
1463
|
+
3. **컴포넌트 구조**: 타입별 특수 컴포넌트 구현
|
|
1464
|
+
4. **모달/UI 구현**: 특수 UI 패턴 적용
|
|
1465
|
+
5. **이벤트 처리**: 타입별 특수 이벤트 핸들링
|
|
1466
|
+
6. **데이터 플로우**: API 연동 및 데이터 흐름 구성
|
|
1467
|
+
|
|
1468
|
+
**모달 기반 타입 공통 패턴**:
|
|
1469
|
+
- FormModal 컴포넌트 활용
|
|
1470
|
+
- 모달 상태 관리 (open/close)
|
|
1471
|
+
- 모달 간 데이터 전달
|
|
1472
|
+
- 모달 스택 관리 (modal in modal)
|
|
1473
|
+
|
|
1474
|
+
**파일 업로드 타입 공통 패턴**:
|
|
1475
|
+
- 파일 선택/드래그앤드롭 처리
|
|
1476
|
+
- 업로드 진행률 표시
|
|
1477
|
+
- 파일 검증 (크기, 형식)
|
|
1478
|
+
- 썸네일 생성 및 미리보기
|
|
1479
|
+
|
|
1480
|
+
**드래그앤드롭 타입 공통 패턴**:
|
|
1481
|
+
- react-beautiful-dnd 또는 유사 라이브러리
|
|
1482
|
+
- 드래그 피드백 UI
|
|
1483
|
+
- 순서 변경 API 연동
|
|
1484
|
+
- 최적적 업데이트
|
|
1485
|
+
|
|
1486
|
+
---
|
|
1487
|
+
|
|
1488
|
+
## 초급자를 위한 실전 가이드
|
|
1489
|
+
|
|
1490
|
+
### 🎯 개발 시작 전 필수 체크사항
|
|
1491
|
+
|
|
1492
|
+
#### 1. 개발 환경 설정 확인
|
|
1493
|
+
```bash
|
|
1494
|
+
# Node.js 버전 확인 (v18+ 필요)
|
|
1495
|
+
node --version
|
|
1496
|
+
|
|
1497
|
+
# 프로젝트 의존성 설치
|
|
1498
|
+
npm install
|
|
1499
|
+
|
|
1500
|
+
# 서브모듈 초기화 (백엔드 인터페이스)
|
|
1501
|
+
npm run submodule
|
|
1502
|
+
|
|
1503
|
+
# 개발 서버 실행
|
|
1504
|
+
npm run dev
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
#### 2. 프로젝트 구조 이해
|
|
1508
|
+
```
|
|
1509
|
+
src/
|
|
1510
|
+
├── @core/ # 프레임워크 핵심 (수정 금지)
|
|
1511
|
+
├── @types/ # 전역 타입 정의
|
|
1512
|
+
├── components/ # 공통 컴포넌트
|
|
1513
|
+
├── hooks/ # 커스텀 훅
|
|
1514
|
+
├── modals/ # 모달 컴포넌트들
|
|
1515
|
+
├── pages/ # 실제 페이지들
|
|
1516
|
+
│ └── resources/
|
|
1517
|
+
│ └── NH/ # ⭐ 여기서 작업!
|
|
1518
|
+
├── services/ # API 서비스 레이어
|
|
1519
|
+
├── stores/ # 전역 상태 관리
|
|
1520
|
+
└── styles/ # 스타일 정의
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
### 🚀 단계별 개발 가이드
|
|
1524
|
+
|
|
1525
|
+
#### Step 1: 새 페이지 폴더 생성
|
|
1526
|
+
```bash
|
|
1527
|
+
# 예시: 상품 관리 페이지 만들기
|
|
1528
|
+
mkdir -p src/pages/resources/NH/product.management
|
|
1529
|
+
cd src/pages/resources/NH/product.management
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
#### Step 2: 기본 파일 생성
|
|
1533
|
+
```bash
|
|
1534
|
+
# 필수 파일들 생성
|
|
1535
|
+
touch App.tsx # 메인 컴포넌트
|
|
1536
|
+
touch index.ts # 내보내기
|
|
1537
|
+
touch useProductStore.ts # Store (상태관리)
|
|
1538
|
+
|
|
1539
|
+
# 목록이 있다면
|
|
1540
|
+
touch ListDataGrid.tsx # 데이터 그리드
|
|
1541
|
+
|
|
1542
|
+
# 모달이 있다면
|
|
1543
|
+
touch FormModal.tsx # 등록/수정 모달
|
|
1544
|
+
|
|
1545
|
+
# 필요시 추가
|
|
1546
|
+
touch types.ts # 타입 정의
|
|
1547
|
+
```
|
|
1548
|
+
|
|
1549
|
+
#### Step 3: index.ts 작성 (내보내기)
|
|
1550
|
+
```typescript
|
|
1551
|
+
// index.ts - 가장 간단함
|
|
1552
|
+
export { default } from './App';
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
#### Step 4: 기본 App.tsx 템플릿 (복사해서 사용!)
|
|
1556
|
+
```typescript
|
|
1557
|
+
// App.tsx
|
|
1558
|
+
import React from 'react';
|
|
1559
|
+
import { PageLayout } from 'styles/pageStyled';
|
|
1560
|
+
import { ProgramTitle } from '@core/components/common';
|
|
1561
|
+
import { useDidMountEffect, useUnmountEffect } from 'hooks';
|
|
1562
|
+
import { errorHandling } from 'utils';
|
|
1563
|
+
|
|
1564
|
+
// TODO: Store import 추가
|
|
1565
|
+
// import { useProductStore } from './useProductStore';
|
|
1566
|
+
|
|
1567
|
+
interface Props {}
|
|
1568
|
+
|
|
1569
|
+
export default function App({}: Props) {
|
|
1570
|
+
// TODO: Store 구독 추가
|
|
1571
|
+
// const init = useProductStore((s) => s.init);
|
|
1572
|
+
// const destroy = useProductStore((s) => s.destroy);
|
|
1573
|
+
|
|
1574
|
+
// 🔄 컴포넌트 시작할 때 실행
|
|
1575
|
+
useDidMountEffect(() => {
|
|
1576
|
+
(async () => {
|
|
1577
|
+
try {
|
|
1578
|
+
// TODO: 초기화 로직 추가
|
|
1579
|
+
console.log('페이지 로드됨!');
|
|
1580
|
+
} catch (e) {
|
|
1581
|
+
await errorHandling(e);
|
|
1582
|
+
}
|
|
1583
|
+
})();
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
// 🔄 컴포넌트 종료할 때 실행
|
|
1587
|
+
useUnmountEffect(() => {
|
|
1588
|
+
// TODO: 정리 로직 추가
|
|
1589
|
+
console.log('페이지 종료됨!');
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
return (
|
|
1593
|
+
<PageLayout stretch>
|
|
1594
|
+
{/* 📋 상단 헤더: 제목 + 버튼들 */}
|
|
1595
|
+
<PageLayout.Header>
|
|
1596
|
+
<ProgramTitle />
|
|
1597
|
+
{/* TODO: 검색, 등록 버튼 등 추가 */}
|
|
1598
|
+
</PageLayout.Header>
|
|
1599
|
+
|
|
1600
|
+
{/* 📊 메인 컨텐츠 영역 */}
|
|
1601
|
+
<PageLayout.FrameRow>
|
|
1602
|
+
<PageLayout.FrameColumn>
|
|
1603
|
+
{/* TODO: 실제 컨텐츠 추가 */}
|
|
1604
|
+
<div style={{ padding: 20 }}>
|
|
1605
|
+
<h2>🎯 개발 단계</h2>
|
|
1606
|
+
<ol>
|
|
1607
|
+
<li>✅ 기본 구조 생성 (현재 단계)</li>
|
|
1608
|
+
<li>⏳ Store 구현</li>
|
|
1609
|
+
<li>⏳ 컴포넌트 구현</li>
|
|
1610
|
+
<li>⏳ API 연동</li>
|
|
1611
|
+
</ol>
|
|
1612
|
+
</div>
|
|
1613
|
+
</PageLayout.FrameColumn>
|
|
1614
|
+
</PageLayout.FrameRow>
|
|
1615
|
+
</PageLayout>
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
```
|
|
1619
|
+
|
|
1620
|
+
### 🐛 자주 발생하는 오류와 해결법
|
|
1621
|
+
|
|
1622
|
+
#### 1. "Cannot read property of undefined" 에러
|
|
1623
|
+
```typescript
|
|
1624
|
+
// ❌ 잘못된 방법
|
|
1625
|
+
const name = user.profile.name;
|
|
1626
|
+
|
|
1627
|
+
// ✅ 올바른 방법 (옵셔널 체이닝)
|
|
1628
|
+
const name = user?.profile?.name;
|
|
1629
|
+
|
|
1630
|
+
// ✅ 기본값 설정
|
|
1631
|
+
const name = user?.profile?.name || '이름 없음';
|
|
1632
|
+
|
|
1633
|
+
// ✅ 조건부 렌더링
|
|
1634
|
+
{user && <div>{user.name}</div>}
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
#### 2. Store 구독이 안 되는 경우
|
|
1638
|
+
```typescript
|
|
1639
|
+
// ❌ 잘못된 방법 (전체 구독 - 성능 문제)
|
|
1640
|
+
const store = useProductStore();
|
|
1641
|
+
const { listData, loading } = store;
|
|
1642
|
+
|
|
1643
|
+
// ✅ 올바른 방법 (필요한 것만 구독)
|
|
1644
|
+
const listData = useProductStore((s) => s.listData);
|
|
1645
|
+
const loading = useProductStore((s) => s.listLoading);
|
|
1646
|
+
const loadList = useProductStore((s) => s.loadList);
|
|
1647
|
+
```
|
|
1648
|
+
|
|
1649
|
+
### 🎯 개발 순서 체크리스트
|
|
1650
|
+
|
|
1651
|
+
#### 📋 화면 개발 전 체크리스트
|
|
1652
|
+
- [ ] 🎨 화면 타입 결정 (목록형? 모달형? 대시보드형?)
|
|
1653
|
+
- [ ] 📡 필요한 API 엔드포인트 확인
|
|
1654
|
+
- [ ] 📋 디자인 시안 또는 요구사항 문서 검토
|
|
1655
|
+
- [ ] 🔍 기존 유사한 화면 참고 사례 찾기
|
|
1656
|
+
- [ ] 🗂️ 데이터 구조 및 타입 정의
|
|
1657
|
+
|
|
1658
|
+
#### 🛠️ 개발 중 체크리스트
|
|
1659
|
+
- [ ] 📁 폴더 및 파일 구조 생성
|
|
1660
|
+
- [ ] 📝 TypeScript 인터페이스 정의
|
|
1661
|
+
- [ ] 🏪 Store 기본 구조 작성
|
|
1662
|
+
- [ ] 🖼️ App.tsx 기본 레이아웃 작성
|
|
1663
|
+
- [ ] 🎭 더미 데이터로 UI 확인
|
|
1664
|
+
- [ ] 📡 실제 API 연동
|
|
1665
|
+
- [ ] ❌ 에러 처리 추가
|
|
1666
|
+
- [ ] ⏳ 로딩 상태 처리
|
|
1667
|
+
- [ ] ✅ Form 유효성 검증
|
|
1668
|
+
|
|
1669
|
+
#### ✨ 개발 완료 후 체크리스트
|
|
1670
|
+
- [ ] 🧪 모든 기능 동작 확인
|
|
1671
|
+
- [ ] 💥 에러 상황 테스트
|
|
1672
|
+
- [ ] 🔐 권한별 기능 제한 확인
|
|
1673
|
+
- [ ] 📱 반응형 레이아웃 확인
|
|
1674
|
+
- [ ] 🔍 코드 리뷰 요청
|
|
1675
|
+
- [ ] 📚 문서 업데이트
|
|
1676
|
+
|
|
1677
|
+
### 💡 네이밍 컨벤션 가이드
|
|
1678
|
+
|
|
1679
|
+
#### 1. 폴더 및 파일명
|
|
1680
|
+
```bash
|
|
1681
|
+
# 폴더명: kebab-case (소문자 + 하이픈)
|
|
1682
|
+
product.management/
|
|
1683
|
+
user.profile/
|
|
1684
|
+
order.history/
|
|
1685
|
+
|
|
1686
|
+
# 컴포넌트 파일명: PascalCase
|
|
1687
|
+
App.tsx
|
|
1688
|
+
ListDataGrid.tsx
|
|
1689
|
+
FormModal.tsx
|
|
1690
|
+
|
|
1691
|
+
# 훅/스토어 파일명: camelCase with prefix
|
|
1692
|
+
useProductStore.ts
|
|
1693
|
+
useOrderHistory.ts
|
|
1694
|
+
|
|
1695
|
+
# 타입 파일명: camelCase
|
|
1696
|
+
types.ts
|
|
1697
|
+
interfaces.ts
|
|
1698
|
+
```
|
|
1699
|
+
|
|
1700
|
+
#### 2. 컴포넌트 및 함수명
|
|
1701
|
+
```typescript
|
|
1702
|
+
// 컴포넌트명: PascalCase
|
|
1703
|
+
export function ProductManagement() {}
|
|
1704
|
+
export function UserProfile() {}
|
|
1705
|
+
|
|
1706
|
+
// 함수명: camelCase + 동사로 시작
|
|
1707
|
+
const handleClick = () => {};
|
|
1708
|
+
const loadProductList = () => {};
|
|
1709
|
+
const saveUserProfile = () => {};
|
|
1710
|
+
|
|
1711
|
+
// 상태명: camelCase + 명사
|
|
1712
|
+
const [loading, setLoading] = useState(false);
|
|
1713
|
+
const [productList, setProductList] = useState([]);
|
|
1714
|
+
const [modalOpen, setModalOpen] = useState(false);
|
|
1715
|
+
```
|
|
1716
|
+
|
|
1717
|
+
#### 3. Store 및 API 네이밍
|
|
1718
|
+
```typescript
|
|
1719
|
+
// Store 액션: 동사로 시작
|
|
1720
|
+
const loadList = async () => {};
|
|
1721
|
+
const saveItem = async () => {};
|
|
1722
|
+
const deleteItem = async () => {};
|
|
1723
|
+
|
|
1724
|
+
// API 서비스: 동사 + 명사
|
|
1725
|
+
ProductService.getList()
|
|
1726
|
+
ProductService.create()
|
|
1727
|
+
ProductService.update()
|
|
1728
|
+
ProductService.delete()
|
|
1729
|
+
|
|
1730
|
+
// 상태 변수: 명사 + 상태 표시
|
|
1731
|
+
listLoading: boolean
|
|
1732
|
+
saveLoading: boolean
|
|
1733
|
+
listData: Product[]
|
|
1734
|
+
selectedItem: Product
|
|
1735
|
+
```
|
|
1736
|
+
|
|
1737
|
+
### 🆘 문제 해결 가이드
|
|
1738
|
+
|
|
1739
|
+
#### 문제 1: 화면이 하얗게 나옴
|
|
1740
|
+
```typescript
|
|
1741
|
+
// 🔍 확인사항
|
|
1742
|
+
// 1. 브라우저 콘솔 에러 확인
|
|
1743
|
+
// 2. 컴포넌트 return 문 확인
|
|
1744
|
+
// 3. Router 설정 확인
|
|
1745
|
+
|
|
1746
|
+
// ✅ 최소 작동 구조
|
|
1747
|
+
export default function App() {
|
|
1748
|
+
return (
|
|
1749
|
+
<div>
|
|
1750
|
+
<h1>테스트 페이지</h1>
|
|
1751
|
+
<p>정상 작동 중...</p>
|
|
1752
|
+
</div>
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
```
|
|
1756
|
+
|
|
1757
|
+
#### 문제 2: Store 상태가 업데이트 안됨
|
|
1758
|
+
```typescript
|
|
1759
|
+
// 🔍 확인사항
|
|
1760
|
+
// 1. Store 구독 방법 확인
|
|
1761
|
+
// 2. set 함수 사용 확인
|
|
1762
|
+
// 3. 객체/배열 불변성 확인
|
|
1763
|
+
|
|
1764
|
+
// ❌ 잘못된 예 (원본 수정)
|
|
1765
|
+
state.listData.push(newItem);
|
|
1766
|
+
state.user.name = 'New Name';
|
|
1767
|
+
|
|
1768
|
+
// ✅ 올바른 예 (새 객체/배열 생성)
|
|
1769
|
+
set(state => ({
|
|
1770
|
+
listData: [...state.listData, newItem]
|
|
1771
|
+
}));
|
|
1772
|
+
|
|
1773
|
+
set(state => ({
|
|
1774
|
+
user: { ...state.user, name: 'New Name' }
|
|
1775
|
+
}));
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
#### 문제 3: API 호출이 안됨
|
|
1779
|
+
```typescript
|
|
1780
|
+
// 🔍 확인사항
|
|
1781
|
+
// 1. 브라우저 네트워크 탭에서 요청 확인
|
|
1782
|
+
// 2. CORS 오류 확인
|
|
1783
|
+
// 3. 인증 토큰 확인
|
|
1784
|
+
// 4. API 엔드포인트 확인
|
|
1785
|
+
|
|
1786
|
+
// ✅ 에러 처리 개선
|
|
1787
|
+
try {
|
|
1788
|
+
console.log('API 호출 시작:', url);
|
|
1789
|
+
const response = await fetch(url);
|
|
1790
|
+
|
|
1791
|
+
if (!response.ok) {
|
|
1792
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
const data = await response.json();
|
|
1796
|
+
console.log('API 응답:', data);
|
|
1797
|
+
return data;
|
|
1798
|
+
|
|
1799
|
+
} catch (error) {
|
|
1800
|
+
console.error('API 호출 실패:', error);
|
|
1801
|
+
throw error;
|
|
1802
|
+
}
|
|
1803
|
+
```
|
|
1804
|
+
|
|
1805
|
+
이제 초급자도 이 상세한 가이드를 따라 체계적으로 화면을 개발할 수 있습니다! 🎉
|