@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.
Files changed (78) hide show
  1. package/CLAUDE.md +119 -0
  2. package/MCP_TOOL_PLAN.md +710 -0
  3. package/MCP_USAGE.md +914 -0
  4. package/README.md +168 -0
  5. package/REPOSITORY_CONVENTIONS.md +250 -0
  6. package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
  7. package/SEARCH_PARAMS_PLAN.md +2570 -0
  8. package/STORE_PATTERNS.md +1178 -0
  9. package/debug-dto.js +72 -0
  10. package/generate-banner-store.js +62 -0
  11. package/generation-plan.json +2176 -0
  12. package/generation-results.json +1817 -0
  13. package/package.json +45 -0
  14. package/scripts/batch-generate-all.js +159 -0
  15. package/scripts/batch-generate-mcp.js +329 -0
  16. package/scripts/batch-generate-stores-v2.js +272 -0
  17. package/scripts/batch-generate-stores.js +179 -0
  18. package/scripts/batch-plan.json +3810 -0
  19. package/scripts/batch-process.py +90 -0
  20. package/scripts/batch-regenerate.js +356 -0
  21. package/scripts/direct-generate.js +227 -0
  22. package/scripts/execute-batches.js +1911 -0
  23. package/scripts/generate-all-stores.js +144 -0
  24. package/scripts/generate-stores-mcp.js +161 -0
  25. package/scripts/generate-stores-v2.js +450 -0
  26. package/scripts/generate-stores-v3.js +412 -0
  27. package/scripts/generate-stores-v4.js +521 -0
  28. package/scripts/generate-stores.js +382 -0
  29. package/scripts/repos-to-process.json +1899 -0
  30. package/src/config/nh-layout-patterns.ts +166 -0
  31. package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
  32. package/src/docs/NH_STORE_PATTERNS.md +297 -0
  33. package/src/docs/README.md +216 -0
  34. package/src/docs/index.ts +28 -0
  35. package/src/docs/loader.ts +568 -0
  36. package/src/docs/patterns.json +419 -0
  37. package/src/docs/practical-examples.md +732 -0
  38. package/src/docs/quick-start.md +257 -0
  39. package/src/docs/requirements-analysis-guide.md +364 -0
  40. package/src/docs/rules.json +321 -0
  41. package/src/docs/store-pattern-analysis.md +664 -0
  42. package/src/docs/store-patterns-rules.md +1168 -0
  43. package/src/docs/store-patterns-usage-guide.md +1835 -0
  44. package/src/docs/troubleshooting.md +544 -0
  45. package/src/docs/type-selection-guide.md +572 -0
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. package/src/features/store-features.ts +232 -0
  58. package/src/handlers/analyze-requirements.ts +403 -0
  59. package/src/handlers/analyze.ts +1373 -0
  60. package/src/handlers/generate-from-requirements.ts +250 -0
  61. package/src/handlers/generate-hook.ts +950 -0
  62. package/src/handlers/generate-interactive.ts +840 -0
  63. package/src/handlers/generate-listdatagrid.ts +521 -0
  64. package/src/handlers/generate-multi-stores.ts +577 -0
  65. package/src/handlers/generate-requirements-from-layout.ts +160 -0
  66. package/src/handlers/generate-search-params.ts +717 -0
  67. package/src/handlers/generate.ts +911 -0
  68. package/src/handlers/list-templates.ts +104 -0
  69. package/src/handlers/scan-metadata.ts +485 -0
  70. package/src/handlers/suggest-layout.ts +326 -0
  71. package/src/index.ts +959 -0
  72. package/src/prompts/search-params.md +793 -0
  73. package/src/templates/index.ts +107 -0
  74. package/src/templates/unified.ts +462 -0
  75. package/store-generation-error-patterns.md +225 -0
  76. package/test/useAgentStore.ts +136 -0
  77. package/test-server.js +78 -0
  78. package/tsconfig.json +20 -0
@@ -0,0 +1,903 @@
1
+ # FormItem 사용법 가이드
2
+
3
+ ## 개요
4
+
5
+ FormItem은 Ant Design의 Form.Item 컴포넌트를 TypeScript 제네릭과 함께 사용하는 패턴입니다. AXBoot 프로젝트에서는 타입 안전성을 위해 `const FormItem = Form.Item<DtoItem>;` 형태로 정의하여 사용합니다.
6
+
7
+ ## 기본 정의 패턴
8
+
9
+ ```typescript
10
+ interface DtoItem extends SomeInterface {}
11
+ const FormItem = Form.Item<DtoItem>;
12
+ ```
13
+
14
+ ## 사용 케이스별 정리
15
+
16
+ ### 1. 기본 입력 필드 (Basic Input Fields)
17
+
18
+ #### 1.1 필수 입력 필드
19
+ ```tsx
20
+ <FormItem
21
+ name={"itemName"}
22
+ label={t("항목명")}
23
+ rules={[{ required: true }]}
24
+ >
25
+ <Input />
26
+ </FormItem>
27
+ ```
28
+
29
+ #### 1.2 최대 길이 제한
30
+ ```tsx
31
+ <FormItem
32
+ name={"description"}
33
+ label={t("설명")}
34
+ rules={[
35
+ { required: true },
36
+ { max: 100, message: t("최대 100자까지 입력 가능합니다.") }
37
+ ]}
38
+ >
39
+ <Input.TextArea rows={3} maxLength={100} />
40
+ </FormItem>
41
+ ```
42
+
43
+ #### 1.3 정규식 패턴 검증
44
+ ```tsx
45
+ <FormItem
46
+ name={"email"}
47
+ label={t("이메일")}
48
+ rules={[
49
+ { required: true },
50
+ { type: 'email', message: t("올바른 이메일 형식을 입력하세요.") }
51
+ ]}
52
+ >
53
+ <Input />
54
+ </FormItem>
55
+ ```
56
+
57
+ ### 2. 선택 필드 (Selection Fields)
58
+
59
+ #### 2.1 Select 드롭다운
60
+ ```tsx
61
+ <FormItem
62
+ name={"categoryCode"}
63
+ label={t("카테고리")}
64
+ rules={[{ required: true }]}
65
+ >
66
+ <Select
67
+ options={categoryOptions}
68
+ placeholder={t("카테고리를 선택하세요")}
69
+ />
70
+ </FormItem>
71
+ ```
72
+
73
+ #### 2.2 Radio Group
74
+ ```tsx
75
+ <FormItem
76
+ name={"useYn"}
77
+ label={t("사용여부")}
78
+ rules={[{ required: true }]}
79
+ >
80
+ <Radio.Group
81
+ options={[
82
+ { value: "Y", label: "Y" },
83
+ { value: "N", label: "N" }
84
+ ]}
85
+ />
86
+ </FormItem>
87
+ ```
88
+
89
+ #### 2.3 Checkbox (Switch)
90
+ ```tsx
91
+ <FormItem
92
+ name={"displayYn"}
93
+ label={t("전시여부")}
94
+ valuePropName="checked"
95
+ >
96
+ <Switch
97
+ checkedChildren="전시"
98
+ unCheckedChildren="미전시"
99
+ />
100
+ </FormItem>
101
+ ```
102
+
103
+ ### 3. 숫자 입력 필드 (Number Fields)
104
+
105
+ #### 3.1 기본 숫자 입력
106
+ ```tsx
107
+ <FormItem
108
+ name={"quantity"}
109
+ label={t("수량")}
110
+ rules={[{ required: true }]}
111
+ >
112
+ <InputNumber
113
+ min={1}
114
+ max={999}
115
+ style={{ width: "100%" }}
116
+ />
117
+ </FormItem>
118
+ ```
119
+
120
+ #### 3.2 단위가 있는 숫자 입력
121
+ ```tsx
122
+ <FormItem
123
+ name={"exposureTime"}
124
+ label={t("노출시간")}
125
+ rules={[{ required: true }]}
126
+ >
127
+ <InputNumber
128
+ min={1}
129
+ max={60}
130
+ addonAfter="초"
131
+ placeholder={t("노출할 초값을 입력하세요")}
132
+ style={{ width: "50%" }}
133
+ />
134
+ </FormItem>
135
+ ```
136
+
137
+ ### 4. 날짜/시간 필드 (Date/Time Fields)
138
+
139
+ #### 4.1 날짜 범위 선택
140
+ ```tsx
141
+ <FormItem
142
+ name={"dateRange"}
143
+ label={t("기간")}
144
+ rules={[{ required: true }]}
145
+ >
146
+ <DtPicker
147
+ type={DtPickerType.DATE_RANGE}
148
+ placeholder={[t("시작일"), t("종료일")]}
149
+ />
150
+ </FormItem>
151
+ ```
152
+
153
+ #### 4.2 단일 날짜 선택
154
+ ```tsx
155
+ <FormItem
156
+ name={"targetDate"}
157
+ label={t("대상일자")}
158
+ rules={[{ required: true }]}
159
+ >
160
+ <DtPicker
161
+ type={DtPickerType.DATE}
162
+ />
163
+ </FormItem>
164
+ ```
165
+
166
+ ### 5. 파일 업로드 (File Upload)
167
+
168
+ #### 5.1 이미지 파일 업로드
169
+ ```tsx
170
+ <FormItem
171
+ label={t("이미지")}
172
+ required
173
+ >
174
+ <div style={{ display: "flex", alignItems: "flex-start", gap: "16px" }}>
175
+ <div style={{ flex: "0 0 auto" }}>
176
+ <ImgFileUploaderById
177
+ value={files}
178
+ spinning={uploading}
179
+ onChange={setFiles}
180
+ handleUpload={uploadFile}
181
+ handleDelete={setDeleted}
182
+ handleDownload={handleDownload}
183
+ accept="image/png,image/svg+xml"
184
+ />
185
+ </div>
186
+ <div style={{ flex: "1 1 auto" }}>
187
+ <UploadGuideLine type="banner" />
188
+ </div>
189
+ </div>
190
+ </FormItem>
191
+ ```
192
+
193
+ ### 6. 조건부 렌더링 (Conditional Rendering)
194
+
195
+ #### 6.1 Form.useWatch를 활용한 조건부 필드
196
+ ```tsx
197
+ const watchedValue = Form.useWatch("parentField", form);
198
+
199
+ return (
200
+ <>
201
+ <FormItem name={"parentField"} label={t("부모필드")}>
202
+ <Select options={parentOptions} />
203
+ </FormItem>
204
+
205
+ {watchedValue === "specific_value" && (
206
+ <FormItem
207
+ name={"childField"}
208
+ label={t("자식필드")}
209
+ rules={[{ required: true }]}
210
+ >
211
+ <Input />
212
+ </FormItem>
213
+ )}
214
+ </>
215
+ );
216
+ ```
217
+
218
+ #### 6.2 조건부 필수 입력 검증
219
+ ```tsx
220
+ <FormItem
221
+ name={"conditionalField"}
222
+ label={t("조건부 필드")}
223
+ rules={[{ required: watchedValue === "Y" }]}
224
+ >
225
+ <Input />
226
+ </FormItem>
227
+ ```
228
+
229
+ ### 7. 동적 폼 필드 (Dynamic Form Fields)
230
+
231
+ #### 7.1 shouldUpdate를 사용한 동적 렌더링
232
+ ```tsx
233
+ <FormItem
234
+ shouldUpdate={(prevValues, currentValues) =>
235
+ prevValues.triggerField !== currentValues.triggerField
236
+ }
237
+ >
238
+ {({ getFieldValue }) => {
239
+ const triggerValue = getFieldValue("triggerField");
240
+ return triggerValue === "show" ? (
241
+ <FormItem name={"dynamicField"} label={t("동적필드")}>
242
+ <Input />
243
+ </FormItem>
244
+ ) : null;
245
+ }}
246
+ </FormItem>
247
+ ```
248
+
249
+ #### 7.2 배열 형태의 동적 필드
250
+ ```tsx
251
+ <Form.List name="items">
252
+ {(fields, { add, remove }) => (
253
+ <>
254
+ {fields.map(({ key, name, ...restField }) => (
255
+ <Row key={key} gutter={16}>
256
+ <Col span={20}>
257
+ <FormItem
258
+ {...restField}
259
+ name={[name, 'itemName']}
260
+ label={t("항목명")}
261
+ rules={[{ required: true }]}
262
+ >
263
+ <Input />
264
+ </FormItem>
265
+ </Col>
266
+ <Col span={4}>
267
+ <Button
268
+ type="link"
269
+ danger
270
+ onClick={() => remove(name)}
271
+ >
272
+ 삭제
273
+ </Button>
274
+ </Col>
275
+ </Row>
276
+ ))}
277
+ <Button type="dashed" onClick={() => add()} block>
278
+ + 항목 추가
279
+ </Button>
280
+ </>
281
+ )}
282
+ </Form.List>
283
+ ```
284
+
285
+ ### 8. 숨김 필드 (Hidden Fields)
286
+
287
+ #### 8.1 noStyle 속성 사용
288
+ ```tsx
289
+ <FormItem name={"hiddenId"} noStyle>
290
+ <Input type="hidden" />
291
+ </FormItem>
292
+ ```
293
+
294
+ #### 8.2 기본값이 있는 숨김 필드
295
+ ```tsx
296
+ <FormItem
297
+ name={"defaultValue"}
298
+ noStyle
299
+ initialValue="default"
300
+ >
301
+ <Input type="hidden" />
302
+ </FormItem>
303
+ ```
304
+
305
+ ### 9. 복합 입력 필드 (Complex Input Fields)
306
+
307
+ #### 9.1 여러 컴포넌트 조합
308
+ ```tsx
309
+ <FormItem label={t("가격 범위")}>
310
+ <Input.Group compact>
311
+ <FormItem
312
+ name={"minPrice"}
313
+ noStyle
314
+ rules={[{ required: true }]}
315
+ >
316
+ <InputNumber
317
+ placeholder="최소가격"
318
+ style={{ width: '50%' }}
319
+ />
320
+ </FormItem>
321
+ <FormItem
322
+ name={"maxPrice"}
323
+ noStyle
324
+ rules={[{ required: true }]}
325
+ >
326
+ <InputNumber
327
+ placeholder="최대가격"
328
+ style={{ width: '50%' }}
329
+ />
330
+ </FormItem>
331
+ </Input.Group>
332
+ </FormItem>
333
+ ```
334
+
335
+ #### 9.2 주소 입력 필드
336
+ ```tsx
337
+ <FormItem label={t("주소")}>
338
+ <Row gutter={8}>
339
+ <Col span={6}>
340
+ <FormItem name={"zipCode"} noStyle>
341
+ <Input placeholder="우편번호" />
342
+ </FormItem>
343
+ </Col>
344
+ <Col span={4}>
345
+ <Button onClick={openAddressModal}>
346
+ 주소검색
347
+ </Button>
348
+ </Col>
349
+ <Col span={14}>
350
+ <FormItem name={"address"} noStyle>
351
+ <Input placeholder="상세주소" />
352
+ </FormItem>
353
+ </Col>
354
+ </Row>
355
+ </FormItem>
356
+ ```
357
+
358
+ ### 10. 커스텀 검증 (Custom Validation)
359
+
360
+ #### 10.1 비동기 검증
361
+ ```tsx
362
+ <FormItem
363
+ name={"username"}
364
+ label={t("사용자명")}
365
+ rules={[
366
+ { required: true },
367
+ {
368
+ validator: async (_, value) => {
369
+ if (value) {
370
+ const exists = await checkUsernameExists(value);
371
+ if (exists) {
372
+ return Promise.reject(new Error('이미 존재하는 사용자명입니다.'));
373
+ }
374
+ }
375
+ return Promise.resolve();
376
+ }
377
+ }
378
+ ]}
379
+ >
380
+ <Input />
381
+ </FormItem>
382
+ ```
383
+
384
+ #### 10.2 다른 필드와의 비교 검증
385
+ ```tsx
386
+ <FormItem
387
+ name={"confirmPassword"}
388
+ label={t("비밀번호 확인")}
389
+ dependencies={['password']}
390
+ rules={[
391
+ { required: true },
392
+ ({ getFieldValue }) => ({
393
+ validator(_, value) {
394
+ if (!value || getFieldValue('password') === value) {
395
+ return Promise.resolve();
396
+ }
397
+ return Promise.reject(new Error('비밀번호가 일치하지 않습니다.'));
398
+ },
399
+ })
400
+ ]}
401
+ >
402
+ <Input.Password />
403
+ </FormItem>
404
+ ```
405
+
406
+ ## 고급 활용 패턴
407
+
408
+ ### 1. 레이아웃과 스타일링
409
+
410
+ #### 1.1 반응형 레이아웃
411
+ ```tsx
412
+ <Row gutter={16}>
413
+ <Col xs={24} sm={12} md={8}>
414
+ <FormItem name={"field1"} label={t("필드1")}>
415
+ <Input />
416
+ </FormItem>
417
+ </Col>
418
+ <Col xs={24} sm={12} md={8}>
419
+ <FormItem name={"field2"} label={t("필드2")}>
420
+ <Input />
421
+ </FormItem>
422
+ </Col>
423
+ <Col xs={24} sm={24} md={8}>
424
+ <FormItem name={"field3"} label={t("필드3")}>
425
+ <Input />
426
+ </FormItem>
427
+ </Col>
428
+ </Row>
429
+ ```
430
+
431
+ #### 1.2 인라인 폼 항목
432
+ ```tsx
433
+ <FormItem label={t("기간 설정")}>
434
+ <Flex gap="small" align="center">
435
+ <FormItem name={"startDate"} noStyle>
436
+ <DatePicker />
437
+ </FormItem>
438
+ <span>~</span>
439
+ <FormItem name={"endDate"} noStyle>
440
+ <DatePicker />
441
+ </FormItem>
442
+ </Flex>
443
+ </FormItem>
444
+ ```
445
+
446
+ ### 2. 접근성과 UX 향상
447
+
448
+ #### 2.1 툴팁과 도움말
449
+ ```tsx
450
+ <FormItem
451
+ name={"complexField"}
452
+ label={t("복잡한 필드")}
453
+ tooltip="이 필드는 특별한 규칙이 있습니다."
454
+ rules={[{ required: true }]}
455
+ >
456
+ <Input placeholder="규칙에 맞게 입력하세요" />
457
+ </FormItem>
458
+ ```
459
+
460
+ #### 2.2 Extra 정보 제공
461
+ ```tsx
462
+ <FormItem
463
+ name={"passwordField"}
464
+ label={t("비밀번호")}
465
+ extra="8자 이상, 영문/숫자/특수문자 포함"
466
+ rules={[
467
+ { required: true },
468
+ { min: 8, message: "8자 이상 입력하세요" }
469
+ ]}
470
+ >
471
+ <Input.Password />
472
+ </FormItem>
473
+ ```
474
+
475
+ ### 3. 성능 최적화
476
+
477
+ #### 3.1 preserve 속성 사용
478
+ ```tsx
479
+ <FormItem
480
+ name={"preservedField"}
481
+ label={t("보존 필드")}
482
+ preserve={false} // 언마운트 시 값 제거
483
+ >
484
+ <Input />
485
+ </FormItem>
486
+ ```
487
+
488
+ #### 3.2 dependencies 최적화
489
+ ```tsx
490
+ <FormItem
491
+ name={"dependentField"}
492
+ label={t("의존 필드")}
493
+ dependencies={['triggerField']} // 특정 필드 변경시만 리렌더링
494
+ rules={[
495
+ ({ getFieldValue }) => ({
496
+ validator(_, value) {
497
+ const trigger = getFieldValue('triggerField');
498
+ // 조건부 검증 로직
499
+ return Promise.resolve();
500
+ }
501
+ })
502
+ ]}
503
+ >
504
+ <Input />
505
+ </FormItem>
506
+ ```
507
+
508
+ ## 프로젝트 실제 폼 검증 로직 (Real-world Validation Patterns)
509
+
510
+ 프로젝트에서 실제 사용되는 다양한 폼 검증 패턴들을 케이스별로 정리했습니다.
511
+
512
+ ### 1. 기본 검증 규칙들
513
+
514
+ #### 필수 입력 검증
515
+ ```tsx
516
+ // 기본 필수 입력
517
+ <FormItem name="itemName" label={t("항목명")} rules={[{ required: true }]}>
518
+ <Input />
519
+ </FormItem>
520
+
521
+ // 커스텀 메시지가 있는 필수 입력
522
+ <FormItem
523
+ name="mngrMemoCtnts"
524
+ rules={[
525
+ {
526
+ required: true,
527
+ message: t("관리자 메모를 입력해주세요."),
528
+ }
529
+ ]}
530
+ >
531
+ <Input.TextArea />
532
+ </FormItem>
533
+ ```
534
+
535
+ #### 길이 제한 검증 (order.subscription.direct-rental/FormModal.tsx)
536
+ ```tsx
537
+ <FormItem
538
+ name="mngrMemoCtnts"
539
+ rules={[
540
+ {
541
+ required: true,
542
+ message: t("관리자 메모를 입력해주세요."),
543
+ },
544
+ {
545
+ max: 2000,
546
+ message: t("관리자 메모는 최대 2000자까지 입력 가능합니다."),
547
+ },
548
+ ]}
549
+ >
550
+ <Input.TextArea rows={3} maxLength={2000} placeholder="관리자 메모를 입력하세요..." />
551
+ </FormItem>
552
+ ```
553
+
554
+ ### 2. 비밀번호 검증 패턴
555
+
556
+ #### 비밀번호 복잡도 검증 (ResetPwdModal.tsx)
557
+ ```tsx
558
+ <FormItem
559
+ name="usrPw"
560
+ label={t("새비밀번호")}
561
+ rules={[
562
+ { required: true },
563
+ ({}) => ({
564
+ validator(_, value) {
565
+ const checkList = checkPasswordValidation(
566
+ value,
567
+ form.getFieldsValue().usrLoginId,
568
+ form.getFieldsValue().usrHpNo,
569
+ );
570
+ if (checkList.length === 0) {
571
+ return Promise.resolve();
572
+ }
573
+ return Promise.reject(new Error(checkList[0].message));
574
+ },
575
+ }),
576
+ ]}
577
+ hasFeedback
578
+ >
579
+ <Input.Password placeholder="영문자, 숫자, 특수문자를 조합하여 8자리 이상을 입력해 주세요" />
580
+ </FormItem>
581
+ ```
582
+
583
+ #### 비밀번호 확인 검증 (ChangePasswordModal.tsx)
584
+ ```tsx
585
+ <FormItem
586
+ name="usrPw2"
587
+ label={t("새비밀번호 확인")}
588
+ dependencies={["usrPw"]}
589
+ hasFeedback
590
+ rules={[
591
+ { required: true },
592
+ ({ getFieldValue }) => ({
593
+ validator(_, value) {
594
+ if (!value || getFieldValue("usrPw") === value) {
595
+ return Promise.resolve();
596
+ }
597
+ return Promise.reject(new Error("비밀번호가 일치하지 않습니다."));
598
+ },
599
+ }),
600
+ ]}
601
+ >
602
+ <Input.Password placeholder="새비밀번호를 다시한번 입력해 주세요" />
603
+ </FormItem>
604
+ ```
605
+
606
+ ### 3. 날짜/시간 검증 패턴
607
+
608
+ #### 날짜 범위 검증 (benefit.live-deal/FormModal.tsx)
609
+ ```tsx
610
+ <FormItem
611
+ name="dateRange"
612
+ style={{ margin: 0 }}
613
+ rules={[
614
+ { required: true, message: "나비엔라이브 기간을 설정해주세요." },
615
+ ({ getFieldValue: _ }) => ({
616
+ validator(__, value) {
617
+ if (value && value[0] && value[1]) {
618
+ const startDate = dayjs(value[0]);
619
+ const endDate = dayjs(value[1]);
620
+ const now = dayjs();
621
+
622
+ // 신규 등록 시 과거 날짜 체크
623
+ if (startDate.isBefore(now, "minute") && !params.query?.bnefNo) {
624
+ return Promise.reject(new Error("시작일시는 현재 시간 이후로 설정해주세요."));
625
+ }
626
+
627
+ // 종료일이 시작일보다 늦어야 함
628
+ if (endDate.isBefore(startDate) || endDate.isSame(startDate)) {
629
+ return Promise.reject(new Error("종료일시는 시작일시보다 늦어야 합니다."));
630
+ }
631
+ }
632
+ return Promise.resolve();
633
+ },
634
+ }),
635
+ ]}
636
+ >
637
+ <DtPicker type={DtPickerType.DATE_RANGE} isTimeSelector allowClear />
638
+ </FormItem>
639
+ ```
640
+
641
+ ### 4. 조건부 검증 패턴
642
+
643
+ #### 다른 필드 값에 따른 조건부 검증 (benefit.live-deal/FormModal.tsx)
644
+ ```tsx
645
+ <FormItem
646
+ name="sleLmttStupYn"
647
+ rules={[
648
+ { required: true },
649
+ {
650
+ validator: () => {
651
+ // 제품이 선택되지 않은 경우 검증 실패
652
+ if (products.length === 0) {
653
+ return Promise.reject(new Error(t("제품을 선택해주세요.")));
654
+ }
655
+ return Promise.resolve();
656
+ },
657
+ },
658
+ ]}
659
+ style={{ margin: 0 }}
660
+ >
661
+ <Radio.Group
662
+ options={[
663
+ { label: "미사용", value: "N" },
664
+ { label: "사용", value: "Y" },
665
+ ]}
666
+ />
667
+ </FormItem>
668
+ ```
669
+
670
+ #### Watch를 활용한 조건부 필수 검증
671
+ ```tsx
672
+ const watchedValue = Form.useWatch("parentField", form);
673
+
674
+ <FormItem
675
+ name="childField"
676
+ label={t("자식 필드")}
677
+ rules={[{ required: watchedValue === "Y" }]}
678
+ >
679
+ <Input />
680
+ </FormItem>
681
+ ```
682
+
683
+ ### 5. 복합 검증 패턴
684
+
685
+ #### 배열/리스트 검증
686
+ ```tsx
687
+ <FormItem
688
+ name="itemList"
689
+ rules={[
690
+ {
691
+ validator: (_, value) => {
692
+ if (!value || value.length === 0) {
693
+ return Promise.reject(new Error(t("최소 1개 이상 선택해주세요.")));
694
+ }
695
+ if (value.length > 10) {
696
+ return Promise.reject(new Error(t("최대 10개까지 선택 가능합니다.")));
697
+ }
698
+ return Promise.resolve();
699
+ },
700
+ },
701
+ ]}
702
+ >
703
+ <Select mode="multiple" options={options} />
704
+ </FormItem>
705
+ ```
706
+
707
+ #### 숫자 범위 검증
708
+ ```tsx
709
+ <FormItem
710
+ name="quantity"
711
+ label={t("수량")}
712
+ rules={[
713
+ { required: true },
714
+ {
715
+ type: "number",
716
+ min: 1,
717
+ max: 999,
718
+ message: t("1~999 사이의 숫자를 입력해주세요."),
719
+ },
720
+ ]}
721
+ >
722
+ <InputNumber style={{ width: "100%" }} />
723
+ </FormItem>
724
+ ```
725
+
726
+ ### 6. 실시간 검증과 피드백
727
+
728
+ #### hasFeedback 속성 활용
729
+ ```tsx
730
+ <FormItem
731
+ name="email"
732
+ label={t("이메일")}
733
+ rules={[
734
+ { required: true },
735
+ { type: "email", message: t("올바른 이메일 형식을 입력하세요.") },
736
+ ]}
737
+ hasFeedback // 실시간 검증 결과 표시
738
+ >
739
+ <Input />
740
+ </FormItem>
741
+ ```
742
+
743
+ #### 정규식 패턴 검증 (주석 처리된 예시)
744
+ ```tsx
745
+ <FormItem
746
+ name="password"
747
+ rules={[
748
+ { required: true },
749
+ // 정규식 패턴 예시 (실제 프로젝트에서는 커스텀 validator 사용)
750
+ // {
751
+ // pattern: /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+|~\-={}[\]:";'<>?,./\\]).{8,16}$/,
752
+ // message: "8~16자 영문+숫자+특수문자를 조합하여 사용하세요.",
753
+ // },
754
+ ]}
755
+ >
756
+ <Input.Password />
757
+ </FormItem>
758
+ ```
759
+
760
+ ### 7. 에러 처리 및 사용자 경험
761
+
762
+ #### 검증 실패 시 첫 번째 필드로 스크롤
763
+ ```tsx
764
+ <Form
765
+ form={form}
766
+ layout="vertical"
767
+ onFinish={handleSubmit}
768
+ scrollToFirstError // 검증 실패 시 첫 번째 오류 필드로 스크롤
769
+ >
770
+ {/* FormItem들 */}
771
+ </Form>
772
+ ```
773
+
774
+ #### 동적 검증 메시지
775
+ ```tsx
776
+ <FormItem
777
+ name="customField"
778
+ rules={[
779
+ {
780
+ validator: (_, value) => {
781
+ if (!value) {
782
+ return Promise.reject(new Error(t("필수 입력 항목입니다.")));
783
+ }
784
+ if (value.length < 3) {
785
+ return Promise.reject(new Error(t("최소 3자 이상 입력해주세요.")));
786
+ }
787
+ if (!/^[a-zA-Z0-9]+$/.test(value)) {
788
+ return Promise.reject(new Error(t("영문과 숫자만 입력 가능합니다.")));
789
+ }
790
+ return Promise.resolve();
791
+ },
792
+ },
793
+ ]}
794
+ >
795
+ <Input />
796
+ </FormItem>
797
+ ```
798
+
799
+ ### 8. 검증 유틸리티 활용
800
+
801
+ 프로젝트에서는 공통 검증 함수들을 활용합니다:
802
+
803
+ ```tsx
804
+ import { checkPasswordValidation } from "utils/validation";
805
+
806
+ // 비밀번호 검증 함수 활용
807
+ <FormItem
808
+ name="password"
809
+ rules={[
810
+ { required: true },
811
+ ({}) => ({
812
+ validator(_, value) {
813
+ const checkList = checkPasswordValidation(value);
814
+ if (checkList.length === 0) {
815
+ return Promise.resolve();
816
+ }
817
+ return Promise.reject(new Error(checkList[0].message));
818
+ },
819
+ }),
820
+ ]}
821
+ >
822
+ <Input.Password />
823
+ </FormItem>
824
+ ```
825
+
826
+ ### 9. 종합 검증 예시
827
+
828
+ 실제 프로젝트에서 사용되는 복합적인 검증 패턴:
829
+
830
+ ```tsx
831
+ <FormItem
832
+ name="complexField"
833
+ label={t("복합 검증 필드")}
834
+ dependencies={["relatedField"]}
835
+ rules={[
836
+ { required: true, message: t("필수 입력 항목입니다.") },
837
+ { max: 100, message: t("최대 100자까지 입력 가능합니다.") },
838
+ ({ getFieldValue }) => ({
839
+ validator: async (_, value) => {
840
+ // 관련 필드 값 확인
841
+ const relatedValue = getFieldValue("relatedField");
842
+
843
+ // 조건부 검증
844
+ if (relatedValue === "special" && value.length < 10) {
845
+ return Promise.reject(new Error(t("특수 모드에서는 10자 이상 입력해야 합니다.")));
846
+ }
847
+
848
+ // 비동기 검증 (서버 확인)
849
+ if (value) {
850
+ try {
851
+ const isValid = await validateFieldOnServer(value);
852
+ if (!isValid) {
853
+ return Promise.reject(new Error(t("서버 검증에 실패했습니다.")));
854
+ }
855
+ } catch (error) {
856
+ return Promise.reject(new Error(t("검증 중 오류가 발생했습니다.")));
857
+ }
858
+ }
859
+
860
+ return Promise.resolve();
861
+ },
862
+ }),
863
+ ]}
864
+ hasFeedback
865
+ >
866
+ <Input />
867
+ </FormItem>
868
+ ```
869
+
870
+ ## 모범 사례 (Best Practices)
871
+
872
+ ### 1. 타입 안전성 확보
873
+ - 항상 `const FormItem = Form.Item<DtoItem>` 패턴 사용
874
+ - 인터페이스 확장을 통한 타입 정의
875
+
876
+ ### 2. 검증 규칙 일관성
877
+ - 공통 검증 규칙을 상수로 정의
878
+ - 메시지는 i18n을 통한 다국어 지원
879
+ - 프로젝트의 `checkPasswordValidation` 같은 공통 유틸리티 활용
880
+
881
+ ### 3. 성능 고려사항
882
+ - shouldUpdate 사용 시 필요한 경우에만 적용
883
+ - dependencies를 정확히 명시하여 불필요한 리렌더링 방지
884
+
885
+ ### 4. 접근성 향상
886
+ - label, placeholder 적절히 활용
887
+ - 필수 필드 명시적 표현
888
+ - 에러 메시지 명확하게 제공
889
+ - `hasFeedback` 속성으로 실시간 피드백 제공
890
+
891
+ ### 5. 검증 패턴별 활용 가이드
892
+ - **기본 검증**: required, max, min, type 등 기본 규칙 활용
893
+ - **비밀번호**: dependencies와 getFieldValue를 활용한 확인 필드 구현
894
+ - **날짜**: dayjs를 활용한 시간 비교 로직
895
+ - **조건부**: 다른 상태나 필드에 따른 동적 검증
896
+ - **복합**: 여러 조건을 조합한 복잡한 검증 로직
897
+
898
+ ### 6. 코드 재사용성
899
+ - 공통 FormItem 패턴을 컴포넌트화
900
+ - 비슷한 검증 로직의 경우 훅으로 분리
901
+ - 검증 유틸리티 함수 활용
902
+
903
+ 이러한 검증 패턴들을 조합하여 사용자에게 명확한 피드백을 제공하고, 데이터 무결성을 보장할 수 있습니다.