@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,1515 @@
1
+ # Ant Design 컴포넌트 사용법 가이드
2
+
3
+ ## 개요
4
+
5
+ 이 문서는 네이비언하우스 FE-BO 프로젝트에서 실제 사용되고 있는 Ant Design 컴포넌트들의 사용 사례와 패턴을 정리한 가이드입니다.
6
+
7
+ ## 1. 폼 관련 컴포넌트 (Form Components)
8
+
9
+ ### 1.1 Form & Form.Item
10
+
11
+ #### 기본 사용 패턴
12
+ ```tsx
13
+ // 프로젝트 표준 패턴
14
+ const FormItem = Form.Item<DtoItem>;
15
+
16
+ function FormModal() {
17
+ const [form] = Form.useForm<DtoItem>();
18
+
19
+ return (
20
+ <Form
21
+ form={form}
22
+ layout="vertical"
23
+ onFinish={handleSave}
24
+ initialValues={{ useYn: "Y" }}
25
+ >
26
+ <FormItem
27
+ name="itemName"
28
+ label={t("항목명")}
29
+ rules={[{ required: true }]}
30
+ >
31
+ <Input />
32
+ </FormItem>
33
+ </Form>
34
+ );
35
+ }
36
+ ```
37
+
38
+ #### 실제 사용 예시 (department/FormModal.tsx)
39
+ ```tsx
40
+ <Form form={form} layout="vertical" onFinish={handleSave} initialValues={{ useYn: "Y" }}>
41
+ <FormItem name="trtmntDeptNo" noStyle>
42
+ <Input type="hidden" />
43
+ </FormItem>
44
+
45
+ <Row gutter={12}>
46
+ <Col span={24}>
47
+ <FormItem name="trtmntDeptNm" label={t("부서명")} rules={[{ required: true }]}>
48
+ <Input />
49
+ </FormItem>
50
+ </Col>
51
+ <Col span={24}>
52
+ <FormItem
53
+ name="pgSvcMidValue"
54
+ label={t("MID")}
55
+ rules={[{ required: true }]}
56
+ tooltip="KG이니시스의 MID정보를 입력해 주세요."
57
+ >
58
+ <Input />
59
+ </FormItem>
60
+ </Col>
61
+ </Row>
62
+ </Form>
63
+ ```
64
+
65
+ ### 1.2 Input
66
+
67
+ #### 기본 텍스트 입력
68
+ ```tsx
69
+ <FormItem name="title" label={t("제목")} rules={[{ required: true }]}>
70
+ <Input placeholder={t("제목을 입력하세요")} />
71
+ </FormItem>
72
+ ```
73
+
74
+ #### TextArea 사용
75
+ ```tsx
76
+ <FormItem name="description" label={t("설명")}>
77
+ <Input.TextArea
78
+ rows={3}
79
+ maxLength={2000}
80
+ placeholder={t("설명을 입력하세요")}
81
+ />
82
+ </FormItem>
83
+ ```
84
+
85
+ #### Password 입력
86
+ ```tsx
87
+ <FormItem name="password" label={t("비밀번호")}>
88
+ <Input.Password />
89
+ </FormItem>
90
+ ```
91
+
92
+ #### 숨김 필드
93
+ ```tsx
94
+ <FormItem name="hiddenId" noStyle>
95
+ <Input type="hidden" />
96
+ </FormItem>
97
+ ```
98
+
99
+ ### 1.3 InputNumber
100
+
101
+ #### 기본 숫자 입력
102
+ ```tsx
103
+ <FormItem name="quantity" label={t("수량")} rules={[{ required: true }]}>
104
+ <InputNumber
105
+ min={1}
106
+ max={999}
107
+ style={{ width: "100%" }}
108
+ />
109
+ </FormItem>
110
+ ```
111
+
112
+ #### 단위가 있는 숫자 입력 (system.app-banner/FormModal.tsx)
113
+ ```tsx
114
+ <FormItem name="expsrSecndValue" label={t("배너노출시간설정")} rules={[{ required: true }]}>
115
+ <InputNumber
116
+ min={1}
117
+ max={60}
118
+ addonAfter="초"
119
+ placeholder={t("노출할 초값을 입력하세요")}
120
+ style={{ width: "20%" }}
121
+ />
122
+ </FormItem>
123
+ ```
124
+
125
+ ### 1.4 Select
126
+
127
+ #### 기본 셀렉트 박스
128
+ ```tsx
129
+ <FormItem name="categoryCode" label={t("카테고리")} rules={[{ required: true }]}>
130
+ <Select
131
+ options={categoryOptions}
132
+ placeholder={t("카테고리를 선택하세요")}
133
+ />
134
+ </FormItem>
135
+ ```
136
+
137
+ #### 컴팩트 스타일의 셀렉트 (benefit.time-deal/FormModal.tsx)
138
+ ```tsx
139
+ <Space.Compact>
140
+ <Select
141
+ value={getFieldValue("bnefDscntStupTpcd")}
142
+ options={[
143
+ { label: "%", value: "10" },
144
+ { label: "원", value: "20" }
145
+ ]}
146
+ style={{ width: 80 }}
147
+ onChange={(value) => setFieldValue("bnefDscntStupTpcd", value)}
148
+ />
149
+ <Button>{t("할인")}</Button>
150
+ </Space.Compact>
151
+ ```
152
+
153
+ ### 1.5 Radio
154
+
155
+ #### 기본 라디오 그룹
156
+ ```tsx
157
+ <FormItem name="useYn" label={t("사용여부")} rules={[{ required: true }]}>
158
+ <Radio.Group
159
+ options={[
160
+ { value: "Y", label: "Y" },
161
+ { value: "N", label: "N" }
162
+ ]}
163
+ />
164
+ </FormItem>
165
+ ```
166
+
167
+ #### 인라인 라디오 그룹 (system.app-banner/FormModal.tsx)
168
+ ```tsx
169
+ <FormItem name="urlTrgtTpcd" label={t("링크 대상")}>
170
+ <Radio.Group>
171
+ <Radio value="">없음</Radio>
172
+ {URL_TRGT_TPCD?.options?.map((option) => (
173
+ <Radio key={option.value} value={option.value}>
174
+ {option.label}
175
+ </Radio>
176
+ ))}
177
+ </Radio.Group>
178
+ </FormItem>
179
+ ```
180
+
181
+ ### 1.6 Switch
182
+
183
+ #### 토글 스위치 (system.app-banner/FormModal.tsx)
184
+ ```tsx
185
+ <FormItem name="dspyYn" label={t("전시여부")} valuePropName="checked">
186
+ <Switch
187
+ checkedChildren="전시"
188
+ unCheckedChildren="미전시"
189
+ />
190
+ </FormItem>
191
+ ```
192
+
193
+ ### 1.7 날짜/시간 컴포넌트 (Date & Time Components)
194
+
195
+ 프로젝트에서는 Ant Design의 기본 DatePicker 대신 커스텀 `DtPicker` 컴포넌트를 주로 사용합니다.
196
+
197
+ #### DtPicker 타입 정의
198
+ ```typescript
199
+ export enum DtPickerType {
200
+ DATE = 0, // 단일 날짜
201
+ DATE_RANGE = 1, // 날짜 범위
202
+ MONTH = 2, // 월 선택
203
+ YEAR = 3, // 연도 선택
204
+ }
205
+ ```
206
+
207
+ #### 날짜 범위 선택 (system.app-banner/FormModal.tsx)
208
+ ```tsx
209
+ import { DtPicker } from "@core/components/dtPicker";
210
+ import { DtPickerType } from "@core/components/dtPicker/types";
211
+
212
+ <FormItem
213
+ name="dt_range"
214
+ label={t("노출기간")}
215
+ rules={[{ required: true, message: t("노출기간을 선택해주세요") }]}
216
+ >
217
+ <DtPicker
218
+ type={DtPickerType.DATE_RANGE}
219
+ placeholder={[t("노출 시작일시"), t("노출 종료일시")]}
220
+ />
221
+ </FormItem>
222
+ ```
223
+
224
+ #### 단일 날짜 선택 (press-media/PressMediaModal.tsx)
225
+ ```tsx
226
+ <FormItem name="bbscttBgnDt" label={t("노출 일자 지정")} rules={[{ required: true }]}>
227
+ <DtPicker
228
+ type={DtPickerType.DATE}
229
+ isTimeSelector // 시간 선택 포함
230
+ allowClear // 클리어 버튼 표시
231
+ />
232
+ </FormItem>
233
+ ```
234
+
235
+ #### 월/연도 선택
236
+ ```tsx
237
+ // 월 선택
238
+ <FormItem name="targetMonth" label={t("대상월")}>
239
+ <DtPicker type={DtPickerType.MONTH} />
240
+ </FormItem>
241
+
242
+ // 연도 선택
243
+ <FormItem name="targetYear" label={t("대상연도")}>
244
+ <DtPicker type={DtPickerType.YEAR} />
245
+ </FormItem>
246
+ ```
247
+
248
+ #### 검색 화면용 날짜 프리셋 (DtPickerPresetRender)
249
+ 검색 화면에서는 `DtPickerPresetRender`를 사용하여 일반적인 날짜 범위 프리셋을 제공합니다.
250
+
251
+ ```tsx
252
+ import { DtPickerPresetRender } from "components/presetRender/DtPickerPresetRender";
253
+
254
+ // 검색 파라미터에서 사용
255
+ const searchParams: SearchParams = [
256
+ {
257
+ prop: "dateRange",
258
+ label: t("기간"),
259
+ type: SearchParamType.DATE_RANGE,
260
+ defaultValue: [],
261
+ presetRender: DtPickerPresetRender({ type: "all" }),
262
+ },
263
+ // 기타 검색 파라미터들...
264
+ ];
265
+ ```
266
+
267
+ #### DtPickerPresetRender 타입
268
+ ```tsx
269
+ // 모든 프리셋 (오늘, 어제, 이번주, 지난주, 이번달, 지난달, 최근 3개월)
270
+ presetRender: DtPickerPresetRender({ type: "all" })
271
+
272
+ // 기본 프리셋 (오늘, 어제, 이번주, 이번달)
273
+ presetRender: DtPickerPresetRender({ type: "basic" })
274
+
275
+ // 커스텀 프리셋
276
+ presetRender: DtPickerPresetRender({})
277
+ ```
278
+
279
+ #### 날짜 데이터 처리 패턴
280
+ ```tsx
281
+ // 폼에서 날짜 범위를 API 파라미터로 변환
282
+ import { dateRangeToDts } from "utils";
283
+ import { DT_FORMAT } from "@types";
284
+
285
+ const handleSave = async () => {
286
+ const values = await form.validateFields();
287
+
288
+ const saveItem = {
289
+ ...values,
290
+ // 날짜 범위를 시작일/종료일로 분리
291
+ ...dateRangeToDts(values.dt_range, {
292
+ expsrBgnDtm: DT_FORMAT.DATETIME,
293
+ expsrEndDtm: DT_FORMAT.DATETIME,
294
+ }),
295
+ };
296
+
297
+ await ApiService.save(saveItem);
298
+ };
299
+
300
+ // 폼 초기값 설정 시
301
+ const formValues = {
302
+ ...params.query,
303
+ // API에서 받은 시작일/종료일을 날짜 범위로 결합
304
+ dt_range: params.query.expsrBgnDtm && params.query.expsrEndDtm
305
+ ? [params.query.expsrBgnDtm, params.query.expsrEndDtm]
306
+ : undefined,
307
+ };
308
+ form.setFieldsValue(formValues);
309
+ ```
310
+
311
+ #### 날짜 제한 옵션들
312
+
313
+ 프로젝트에서는 다양한 날짜 제한 패턴을 사용할 수 있습니다.
314
+
315
+ ```tsx
316
+ import dayjs from "dayjs";
317
+
318
+ // 과거 날짜 선택 불가 (오늘 이후만 선택 가능)
319
+ <FormItem name="futureDate" label={t("미래 날짜")}>
320
+ <DatePicker
321
+ disabledDate={(date) => date && date < dayjs().startOf('day')}
322
+ placeholder={t("오늘 이후 날짜만 선택 가능")}
323
+ />
324
+ </FormItem>
325
+
326
+ // 미래 날짜 선택 불가 (오늘 이전만 선택 가능)
327
+ <FormItem name="pastDate" label={t("과거 날짜")}>
328
+ <DatePicker
329
+ disabledDate={(date) => date && date > dayjs().endOf('day')}
330
+ placeholder={t("오늘 이전 날짜만 선택 가능")}
331
+ />
332
+ </FormItem>
333
+
334
+ // 특정 기간 내에서만 선택 가능
335
+ <FormItem name="limitedDate" label={t("제한 날짜")}>
336
+ <DatePicker
337
+ disabledDate={(date) => {
338
+ const startDate = dayjs().subtract(30, 'day');
339
+ const endDate = dayjs().add(30, 'day');
340
+ return date && (date < startDate || date > endDate);
341
+ }}
342
+ placeholder={t("30일 전후 범위에서만 선택 가능")}
343
+ />
344
+ </FormItem>
345
+
346
+ // 주말 선택 불가
347
+ <FormItem name="weekdayOnly" label={t("평일만")}>
348
+ <DatePicker
349
+ disabledDate={(date) => {
350
+ const day = date.day();
351
+ return day === 0 || day === 6; // 일요일(0), 토요일(6)
352
+ }}
353
+ placeholder={t("평일만 선택 가능")}
354
+ />
355
+ </FormItem>
356
+
357
+ // 특정 날짜들 선택 불가
358
+ <FormItem name="excludeSpecificDates" label={t("제외 날짜")}>
359
+ <DatePicker
360
+ disabledDate={(date) => {
361
+ const disabledDates = [
362
+ '2024-01-01', // 신정
363
+ '2024-12-25', // 크리스마스
364
+ ];
365
+ return disabledDates.includes(date.format('YYYY-MM-DD'));
366
+ }}
367
+ placeholder={t("공휴일 제외")}
368
+ />
369
+ </FormItem>
370
+ ```
371
+
372
+ #### 날짜 범위 제한 패턴
373
+
374
+ ```tsx
375
+ // 날짜 범위에서 시작일 이후에만 종료일 선택 가능
376
+ const [startDate, setStartDate] = useState(null);
377
+
378
+ <Row gutter={16}>
379
+ <Col span={12}>
380
+ <FormItem name="startDate" label={t("시작일")}>
381
+ <DatePicker
382
+ onChange={(date) => setStartDate(date)}
383
+ disabledDate={(date) => date && date < dayjs().startOf('day')}
384
+ />
385
+ </FormItem>
386
+ </Col>
387
+ <Col span={12}>
388
+ <FormItem name="endDate" label={t("종료일")}>
389
+ <DatePicker
390
+ disabledDate={(date) => {
391
+ if (!startDate) return date && date < dayjs().startOf('day');
392
+ return date && (date < dayjs(startDate) || date < dayjs().startOf('day'));
393
+ }}
394
+ />
395
+ </FormItem>
396
+ </Col>
397
+ </Row>
398
+
399
+ // RangePicker에서 날짜 제한
400
+ <FormItem name="dateRange" label={t("기간")}>
401
+ <DatePicker.RangePicker
402
+ disabledDate={(date) => date && date < dayjs().startOf('day')}
403
+ placeholder={[t("시작일"), t("종료일")]}
404
+ />
405
+ </FormItem>
406
+ ```
407
+
408
+ #### 시간 제한 옵션들
409
+
410
+ ```tsx
411
+ // 특정 시간대만 선택 가능
412
+ <FormItem name="businessHours" label={t("업무시간")}>
413
+ <DatePicker
414
+ showTime
415
+ disabledTime={(date) => ({
416
+ disabledHours: () => {
417
+ const hours = [];
418
+ for (let i = 0; i < 9; i++) hours.push(i); // 9시 이전
419
+ for (let i = 18; i < 24; i++) hours.push(i); // 18시 이후
420
+ return hours;
421
+ },
422
+ disabledMinutes: () => [],
423
+ disabledSeconds: () => [],
424
+ })}
425
+ />
426
+ </FormItem>
427
+
428
+ // 현재 시간 이후만 선택 가능
429
+ <FormItem name="futureDateTime" label={t("미래 일시")}>
430
+ <DatePicker
431
+ showTime
432
+ disabledDate={(date) => date && date < dayjs().startOf('day')}
433
+ disabledTime={(date) => {
434
+ if (date && dayjs(date).isSame(dayjs(), 'day')) {
435
+ const currentHour = dayjs().hour();
436
+ const currentMinute = dayjs().minute();
437
+ return {
438
+ disabledHours: () => Array.from({ length: currentHour }, (_, i) => i),
439
+ disabledMinutes: (selectedHour) =>
440
+ selectedHour === currentHour
441
+ ? Array.from({ length: currentMinute }, (_, i) => i)
442
+ : [],
443
+ disabledSeconds: () => [],
444
+ };
445
+ }
446
+ return {};
447
+ }}
448
+ />
449
+ </FormItem>
450
+ ```
451
+
452
+ #### DtPicker에서 날짜 제한 (프로젝트 커스텀)
453
+
454
+ 프로젝트의 DtPicker는 내부적으로 일부 제한사항이 구현되어 있습니다:
455
+
456
+ ```tsx
457
+ // 내부 구현 예시 (DateRangePicker.tsx 참고)
458
+ disabledDate={(date) => {
459
+ return !!dayjs(date).isAfter(dayjs("9999-12-31")) || !!dayjs(date).isBefore(dayjs(firstDate));
460
+ }}
461
+ ```
462
+
463
+ #### Ant Design 기본 DatePicker 사용 (필요시)
464
+ ```tsx
465
+ import { DatePicker } from "antd";
466
+
467
+ // 단일 날짜
468
+ <FormItem name="date" label={t("날짜")}>
469
+ <DatePicker />
470
+ </FormItem>
471
+
472
+ // 날짜 범위
473
+ <FormItem name="dateRange" label={t("기간")}>
474
+ <DatePicker.RangePicker />
475
+ </FormItem>
476
+
477
+ // 시간 포함 날짜
478
+ <FormItem name="datetime" label={t("일시")}>
479
+ <DatePicker showTime />
480
+ </FormItem>
481
+
482
+ // 월 선택
483
+ <FormItem name="month" label={t("월")}>
484
+ <DatePicker picker="month" />
485
+ </FormItem>
486
+
487
+ // 연도 선택
488
+ <FormItem name="year" label={t("연도")}>
489
+ <DatePicker picker="year" />
490
+ </FormItem>
491
+ ```
492
+
493
+ ## 2. 레이아웃 컴포넌트 (Layout Components)
494
+
495
+ ### 2.1 Row & Col
496
+
497
+ #### 반응형 그리드 레이아웃
498
+ ```tsx
499
+ <Row gutter={16}>
500
+ <Col xs={24} sm={12} md={8}>
501
+ <FormItem name="field1" label={t("필드1")}>
502
+ <Input />
503
+ </FormItem>
504
+ </Col>
505
+ <Col xs={24} sm={12} md={8}>
506
+ <FormItem name="field2" label={t("필드2")}>
507
+ <Input />
508
+ </FormItem>
509
+ </Col>
510
+ <Col xs={24} sm={24} md={8}>
511
+ <FormItem name="field3" label={t("필드3")}>
512
+ <Input />
513
+ </FormItem>
514
+ </Col>
515
+ </Row>
516
+ ```
517
+
518
+ #### 기본 2컬럼 레이아웃 (department/FormModal.tsx)
519
+ ```tsx
520
+ <Row gutter={12}>
521
+ <Col span={24}>
522
+ <FormItem name="trtmntDeptNm" label={t("부서명")}>
523
+ <Input />
524
+ </FormItem>
525
+ </Col>
526
+ <Col xs={24} sm={12}>
527
+ <FormItem name="useYn" label={t("사용여부")}>
528
+ <Radio.Group options={[...]} />
529
+ </FormItem>
530
+ </Col>
531
+ </Row>
532
+ ```
533
+
534
+ ### 2.2 Space
535
+
536
+ #### 기본 간격 조정
537
+ ```tsx
538
+ <Space>
539
+ <Button type="primary" onClick={handleSave}>
540
+ {t("저장")}
541
+ </Button>
542
+ <Button onClick={onCancel}>
543
+ {t("취소")}
544
+ </Button>
545
+ </Space>
546
+ ```
547
+
548
+ #### 컴팩트 스타일 (benefit.time-deal/FormModal.tsx)
549
+ ```tsx
550
+ <Space.Compact>
551
+ <Select options={options} style={{ width: 80 }} />
552
+ <Button>{t("할인")}</Button>
553
+ </Space.Compact>
554
+ ```
555
+
556
+ ### 2.3 Flex
557
+
558
+ #### 정렬과 간격 조정 (benefit.time-deal/FormModal.tsx)
559
+ ```tsx
560
+ <Flex gap={8} align="center">
561
+ <FormItem name="field1" style={{ margin: 0 }}>
562
+ <Input />
563
+ </FormItem>
564
+ <span>~</span>
565
+ <FormItem name="field2" style={{ margin: 0 }}>
566
+ <Input />
567
+ </FormItem>
568
+ </Flex>
569
+ ```
570
+
571
+ ### 2.4 Divider
572
+
573
+ #### 섹션 구분
574
+ ```tsx
575
+ <Divider orientation="left">{t("기본 정보")}</Divider>
576
+ <Form>
577
+ {/* 폼 필드들 */}
578
+ </Form>
579
+
580
+ <Divider orientation="left">{t("추가 정보")}</Divider>
581
+ <Form>
582
+ {/* 추가 폼 필드들 */}
583
+ </Form>
584
+ ```
585
+
586
+ ## 3. 데이터 표시 컴포넌트 (Data Display)
587
+
588
+ ### 3.1 Table
589
+
590
+ #### 기본 테이블 (StatisticsListData.tsx)
591
+ ```tsx
592
+ const columns: TableColumnsType<DtoItem> = [
593
+ {
594
+ title: <div style={{ textAlign: "center" }}>번호</div>,
595
+ dataIndex: "num",
596
+ key: "num",
597
+ width: 80,
598
+ align: "center",
599
+ },
600
+ {
601
+ title: <div style={{ textAlign: "center" }}>세부항목</div>,
602
+ dataIndex: "cstItmNm",
603
+ key: "cstItmNm",
604
+ sorter: (a, b) => (a.cstItmNm ?? "").localeCompare(b.cstItmNm ?? ""),
605
+ },
606
+ {
607
+ title: <div style={{ textAlign: "center" }}>제품금액(원)</div>,
608
+ dataIndex: "amt",
609
+ key: "amt",
610
+ width: 150,
611
+ align: "right",
612
+ render: (value) => toMoney(value),
613
+ sorter: (a, b) => (a.amt ?? 0) - (b.amt ?? 0),
614
+ }
615
+ ];
616
+
617
+ <Table<DtoItem>
618
+ columns={columns}
619
+ dataSource={listData}
620
+ pagination={false}
621
+ size="small"
622
+ />
623
+ ```
624
+
625
+ ### 3.2 Descriptions
626
+
627
+ #### 상세 정보 표시 (order.subscription.direct-rental/FormModal.tsx)
628
+ ```tsx
629
+ <Descriptions
630
+ bordered
631
+ column={3}
632
+ size="small"
633
+ style={{ width: "100%" }}
634
+ styles={{
635
+ label: { textAlign: "left", minWidth: 100, width: 140 },
636
+ content: { textAlign: "left" }
637
+ }}
638
+ className="fixed-descriptions"
639
+ items={[
640
+ { label: t("주문번호"), children: params.query?.ifId },
641
+ {
642
+ label: t("주문일자"),
643
+ children: params.query?.creatDtm
644
+ ? dayjs(params.query.creatDtm).format("YYYY-MM-DD HH:mm")
645
+ : "-",
646
+ },
647
+ {
648
+ label: t("주문자명/아이디"),
649
+ children: (
650
+ <Space>
651
+ {`${params.query?.usrNm || ""}(${params.query?.rmsUsrId || ""})`}
652
+ <Button type="default" size="small" onClick={handleLink}>
653
+ CRM
654
+ </Button>
655
+ </Space>
656
+ ),
657
+ },
658
+ { label: t("휴대폰"), children: params.query?.usrHpNo },
659
+ { label: t("이메일"), span: 2, children: params.query?.email || "-" },
660
+ ]}
661
+ />
662
+ ```
663
+
664
+ #### 중첩된 Descriptions.Item
665
+ ```tsx
666
+ <Descriptions.Item
667
+ label={
668
+ <span>
669
+ <span style={{ color: "#ff4d4f", fontSize: "12px", marginRight: "4px" }}>*</span>
670
+ {t("할인 설정")}
671
+ </span>
672
+ }
673
+ >
674
+ <Flex gap={8} align="center">
675
+ <FormItem name="discountType" style={{ margin: 0 }}>
676
+ <Select options={discountOptions} />
677
+ </FormItem>
678
+ </Flex>
679
+ </Descriptions.Item>
680
+ ```
681
+
682
+ ## 4. 내비게이션 컴포넌트 (Navigation)
683
+
684
+ ### 4.1 Tabs
685
+
686
+ #### 기본 탭 (MemberInstallerDetailDrawer.tsx)
687
+ ```tsx
688
+ <Tabs
689
+ items={Object.values(MemberInstallerTabType).map((value) => ({
690
+ key: value,
691
+ label: value,
692
+ }))}
693
+ activeKey={activeTabType}
694
+ type="line"
695
+ size="small"
696
+ onChange={(key) => setActiveTabType(key as MemberInstallerTabType)}
697
+ />
698
+ ```
699
+
700
+ #### 동적 탭 콘텐츠
701
+ ```tsx
702
+ const tabItems = [
703
+ {
704
+ key: '1',
705
+ label: t('기본정보'),
706
+ children: <BasicInfoPanel />,
707
+ },
708
+ {
709
+ key: '2',
710
+ label: t('상세정보'),
711
+ children: <DetailInfoPanel />,
712
+ },
713
+ ];
714
+
715
+ <Tabs items={tabItems} />
716
+ ```
717
+
718
+ ## 5. 피드백 컴포넌트 (Feedback)
719
+
720
+ ### 5.1 Button
721
+
722
+ #### 기본 버튼 스타일
723
+ ```tsx
724
+ // Primary 버튼
725
+ <Button type="primary" onClick={handleSave} loading={saveSpinning}>
726
+ {t("저장")}
727
+ </Button>
728
+
729
+ // Default 버튼
730
+ <Button onClick={onCancel}>
731
+ {t("취소")}
732
+ </Button>
733
+
734
+ // Danger 버튼
735
+ <Button onClick={handleDelete} loading={deleteSpinning} danger>
736
+ {t("삭제")}
737
+ </Button>
738
+
739
+ // Link 스타일 버튼
740
+ <Button type="link" onClick={handleEdit}>
741
+ {t("수정")}
742
+ </Button>
743
+
744
+ // Text 버튼 (아이콘과 함께)
745
+ <Button type="text" onClick={onClose} icon={<IconClose />} />
746
+ ```
747
+
748
+ #### 크기별 버튼
749
+ ```tsx
750
+ // 작은 버튼
751
+ <Button type="default" size="small" onClick={handleAction}>
752
+ CRM
753
+ </Button>
754
+
755
+ // 큰 버튼
756
+ <Button type="primary" size="large">
757
+ {t("확인")}
758
+ </Button>
759
+ ```
760
+
761
+ ### 5.2 Modal (AXModal 사용)
762
+
763
+ 프로젝트에서는 Ant Design Modal 대신 커스텀 AXModal을 주로 사용하지만, 기본 패턴은 유사합니다.
764
+
765
+ ```tsx
766
+ <AXModal width={1400} {...{ open, onCancel, onOk: onOk as any, afterClose }}>
767
+ <AXModal.Header title={t("제목")}>
768
+ <Button type="primary" onClick={handleSave}>
769
+ {t("저장")}
770
+ </Button>
771
+ <Button onClick={onCancel}>
772
+ {t("취소")}
773
+ </Button>
774
+ </AXModal.Header>
775
+
776
+ <AXModal.Body>
777
+ {/* 모달 콘텐츠 */}
778
+ </AXModal.Body>
779
+
780
+ <AXModal.Footer>
781
+ {/* 푸터 버튼들 */}
782
+ </AXModal.Footer>
783
+ </AXModal>
784
+ ```
785
+
786
+ ## 6. 기타 유틸리티 컴포넌트
787
+
788
+ ### 6.1 Popover
789
+
790
+ #### 정보 표시용 팝오버
791
+ ```tsx
792
+ <Popover
793
+ content={<div>{t("도움말 내용")}</div>}
794
+ title={t("도움말")}
795
+ trigger="hover"
796
+ >
797
+ <Button type="link" icon={<InfoIcon />} />
798
+ </Popover>
799
+ ```
800
+
801
+ ### 6.2 Tooltip
802
+
803
+ #### 간단한 툴팁
804
+ ```tsx
805
+ <Tooltip title={t("이 기능에 대한 설명입니다.")}>
806
+ <Button icon={<QuestionIcon />} />
807
+ </Tooltip>
808
+ ```
809
+
810
+ ### 6.3 Tag
811
+
812
+ #### 상태 표시용 태그
813
+ ```tsx
814
+ <Tag color={status === 'active' ? 'green' : 'red'}>
815
+ {status === 'active' ? t('활성') : t('비활성')}
816
+ </Tag>
817
+ ```
818
+
819
+ ### 6.4 Badge
820
+
821
+ #### 알림 배지
822
+ ```tsx
823
+ <Badge count={5} size="small">
824
+ <Button icon={<NotificationIcon />} />
825
+ </Badge>
826
+ ```
827
+
828
+ ## 7. 누락되었던 주요 컴포넌트들
829
+
830
+ ### 7.1 Tree
831
+
832
+ Tree 컴포넌트는 계층적 데이터를 트리 구조로 표시하는 데 사용됩니다. 프로젝트에서는 메뉴 관리, 카테고리 관리 등에 활용됩니다.
833
+
834
+ #### 기본 트리 구조 (MenuDirectory.tsx 참고)
835
+ ```tsx
836
+ import { Tree } from "antd";
837
+ import { TreeProps } from "antd/lib/tree";
838
+
839
+ interface TreeNode {
840
+ key: string;
841
+ title: string;
842
+ children?: TreeNode[];
843
+ isLeaf?: boolean;
844
+ icon?: string;
845
+ }
846
+
847
+ const treeData: TreeNode[] = [
848
+ {
849
+ key: '0',
850
+ title: '루트 메뉴',
851
+ children: [
852
+ {
853
+ key: '0:0',
854
+ title: '하위 메뉴 1',
855
+ isLeaf: true,
856
+ },
857
+ {
858
+ key: '0:1',
859
+ title: '하위 메뉴 2',
860
+ children: [
861
+ {
862
+ key: '0:1:0',
863
+ title: '세부 메뉴',
864
+ isLeaf: true,
865
+ },
866
+ ],
867
+ },
868
+ ],
869
+ },
870
+ ];
871
+
872
+ <Tree
873
+ treeData={treeData}
874
+ defaultExpandAll
875
+ onSelect={(selectedKeys, info) => {
876
+ console.log('선택된 노드:', selectedKeys, info);
877
+ }}
878
+ />
879
+ ```
880
+
881
+ #### 드래그 앤 드롭 지원 트리 (CategoryDirectory.tsx 참고)
882
+ ```tsx
883
+ const onDrop: TreeProps["onDrop"] = useCallback((info) => {
884
+ const {
885
+ dropPosition,
886
+ dropToGap,
887
+ node: { seqPath: dropNodeKey },
888
+ dragNode: { seqPath: dragNodeKey },
889
+ } = info;
890
+
891
+ console.log('드래그 앤 드롭:', { dropPosition, dropToGap, dropNodeKey, dragNodeKey });
892
+
893
+ // 트리 구조 업데이트 로직
894
+ const newTreeData = updateTreeData(treeData, dragNodeKey, dropNodeKey);
895
+ setTreeData(newTreeData);
896
+ }, [treeData]);
897
+
898
+ <Tree
899
+ draggable
900
+ blockNode
901
+ onDrop={onDrop}
902
+ treeData={treeData}
903
+ expandedKeys={expandedKeys}
904
+ onExpand={(keys) => setExpandedKeys(keys)}
905
+ onSelect={(selectedKeys, info) => {
906
+ setSelectedItem(info.node);
907
+ }}
908
+ />
909
+ ```
910
+
911
+ #### 커스텀 트리 노드 렌더링
912
+ ```tsx
913
+ const renderTreeNode = (nodeData) => (
914
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
915
+ <MenuIcon type={nodeData.icon} />
916
+ <span>{nodeData.title}</span>
917
+ <Space size={4}>
918
+ <Tooltip title={t("추가")}>
919
+ <Button
920
+ type="text"
921
+ size="small"
922
+ icon={<IconAdd />}
923
+ onClick={(e) => {
924
+ e.stopPropagation();
925
+ handleAddNode(nodeData.key);
926
+ }}
927
+ />
928
+ </Tooltip>
929
+ <Tooltip title={t("삭제")}>
930
+ <Button
931
+ type="text"
932
+ size="small"
933
+ icon={<IconBin />}
934
+ onClick={(e) => {
935
+ e.stopPropagation();
936
+ handleDelNode(nodeData.key);
937
+ }}
938
+ />
939
+ </Tooltip>
940
+ </Space>
941
+ </div>
942
+ );
943
+
944
+ <Tree
945
+ treeData={treeData.map(node => ({
946
+ ...node,
947
+ title: renderTreeNode(node),
948
+ }))}
949
+ />
950
+ ```
951
+
952
+ ### 7.2 Drawer
953
+
954
+ Drawer는 화면 측면에서 슬라이드되는 패널입니다. 프로젝트에서는 AXDrawer로 커스터마이징되어 사용됩니다.
955
+
956
+ #### 기본 Drawer (AXDrawer.tsx 참고)
957
+ ```tsx
958
+ import { AXDrawer } from "@core/components/AXDrawer";
959
+
960
+ function DetailDrawer({ open, onClose, selectedItem }) {
961
+ const [width, setWidth] = useState(70);
962
+
963
+ return (
964
+ <AXDrawer
965
+ widthPercent={width}
966
+ onResize={(w) => setWidth(w)}
967
+ styles={{
968
+ minWidth: 1000,
969
+ }}
970
+ open={open}
971
+ onClose={onClose}
972
+ loading={!selectedItem}
973
+ >
974
+ <AXDrawer.Header>
975
+ <h2>{t("상세 정보")}</h2>
976
+ <Space>
977
+ <Button type="primary" onClick={handleSave}>
978
+ {t("저장")}
979
+ </Button>
980
+ <Button type="text" onClick={onClose} icon={<IconClose />} />
981
+ </Space>
982
+ </AXDrawer.Header>
983
+
984
+ <AXDrawer.Body>
985
+ {/* 드로어 내용 */}
986
+ <Form form={form}>
987
+ <FormItem name="title" label={t("제목")}>
988
+ <Input />
989
+ </FormItem>
990
+ {/* 기타 폼 필드들 */}
991
+ </Form>
992
+ </AXDrawer.Body>
993
+ </AXDrawer>
994
+ );
995
+ }
996
+ ```
997
+
998
+ #### 리사이즈 가능한 Drawer
999
+ ```tsx
1000
+ const [drawerWidth, setDrawerWidth] = useState(50);
1001
+
1002
+ <AXDrawer
1003
+ widthPercent={drawerWidth}
1004
+ onResize={(width) => setDrawerWidth(width)}
1005
+ open={open}
1006
+ onClose={onClose}
1007
+ >
1008
+ <AXDrawer.Header>
1009
+ <h2>리사이즈 가능한 드로어</h2>
1010
+ </AXDrawer.Header>
1011
+ <AXDrawer.Body>
1012
+ <p>드로어 우측 경계를 드래그하여 크기를 조절할 수 있습니다.</p>
1013
+ </AXDrawer.Body>
1014
+ </AXDrawer>
1015
+ ```
1016
+
1017
+ ### 7.3 Upload
1018
+
1019
+ 파일 업로드 컴포넌트는 다양한 형태로 활용됩니다.
1020
+
1021
+ #### 기본 파일 업로드 (MultiFileUploader.tsx 참고)
1022
+ ```tsx
1023
+ import { Upload, Button } from "antd";
1024
+ import { UploadOutlined } from "@ant-design/icons";
1025
+ import type { UploadProps } from "antd";
1026
+
1027
+ const uploadProps: UploadProps = {
1028
+ action: '/api/upload',
1029
+ listType: 'text',
1030
+ multiple: true,
1031
+ onChange: (info) => {
1032
+ const { status } = info.file;
1033
+ if (status === 'done') {
1034
+ console.log('업로드 성공:', info.file.name);
1035
+ } else if (status === 'error') {
1036
+ console.log('업로드 실패:', info.file.name);
1037
+ }
1038
+ },
1039
+ };
1040
+
1041
+ <Upload {...uploadProps}>
1042
+ <Button icon={<UploadOutlined />}>
1043
+ {t("파일 업로드")}
1044
+ </Button>
1045
+ </Upload>
1046
+ ```
1047
+
1048
+ #### 이미지 업로드 (드래그 앤 드롭)
1049
+ ```tsx
1050
+ const { Dragger } = Upload;
1051
+
1052
+ <Dragger
1053
+ name="file"
1054
+ multiple
1055
+ action="/api/upload"
1056
+ accept="image/*"
1057
+ listType="picture"
1058
+ onChange={(info) => {
1059
+ const { status } = info.file;
1060
+ if (status === 'uploading') {
1061
+ console.log('업로드 중:', info.fileList);
1062
+ }
1063
+ if (status === 'done') {
1064
+ console.log('업로드 완료:', info.file.name);
1065
+ }
1066
+ }}
1067
+ >
1068
+ <p className="ant-upload-drag-icon">
1069
+ <UploadOutlined />
1070
+ </p>
1071
+ <p className="ant-upload-text">
1072
+ {t("클릭하거나 파일을 여기로 드래그하세요")}
1073
+ </p>
1074
+ <p className="ant-upload-hint">
1075
+ {t("단일 또는 다중 파일 업로드를 지원합니다")}
1076
+ </p>
1077
+ </Dragger>
1078
+ ```
1079
+
1080
+ #### 커스텀 업로드 버튼
1081
+ ```tsx
1082
+ <Upload
1083
+ customRequest={({ file, onSuccess, onError }) => {
1084
+ // 커스텀 업로드 로직
1085
+ uploadFile(file)
1086
+ .then(response => {
1087
+ onSuccess(response);
1088
+ })
1089
+ .catch(error => {
1090
+ onError(error);
1091
+ });
1092
+ }}
1093
+ showUploadList={false}
1094
+ >
1095
+ <Button type="primary" icon={<UploadOutlined />}>
1096
+ {t("커스텀 업로드")}
1097
+ </Button>
1098
+ </Upload>
1099
+ ```
1100
+
1101
+ ### 7.4 Transfer
1102
+
1103
+ Transfer 컴포넌트는 두 그룹 간의 데이터 이동을 위해 사용됩니다.
1104
+
1105
+ #### 기본 Transfer
1106
+ ```tsx
1107
+ import { Transfer } from "antd";
1108
+
1109
+ const [targetKeys, setTargetKeys] = useState<string[]>([]);
1110
+ const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
1111
+
1112
+ const mockData = Array.from({ length: 20 }).map((_, i) => ({
1113
+ key: i.toString(),
1114
+ title: `항목 ${i + 1}`,
1115
+ description: `설명 ${i + 1}`,
1116
+ }));
1117
+
1118
+ <Transfer
1119
+ dataSource={mockData}
1120
+ titles={[t('사용 가능'), t('선택된 항목')]}
1121
+ targetKeys={targetKeys}
1122
+ selectedKeys={selectedKeys}
1123
+ onChange={(nextTargetKeys) => {
1124
+ setTargetKeys(nextTargetKeys);
1125
+ }}
1126
+ onSelectChange={(sourceSelectedKeys, targetSelectedKeys) => {
1127
+ setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys]);
1128
+ }}
1129
+ render={(item) => item.title}
1130
+ />
1131
+ ```
1132
+
1133
+ ### 7.5 Cascader
1134
+
1135
+ Cascader는 계층적 선택을 위한 컴포넌트입니다.
1136
+
1137
+ #### 기본 Cascader
1138
+ ```tsx
1139
+ import { Cascader } from "antd";
1140
+
1141
+ const options = [
1142
+ {
1143
+ value: 'seoul',
1144
+ label: '서울',
1145
+ children: [
1146
+ {
1147
+ value: 'gangnam',
1148
+ label: '강남구',
1149
+ },
1150
+ {
1151
+ value: 'gangbuk',
1152
+ label: '강북구',
1153
+ },
1154
+ ],
1155
+ },
1156
+ {
1157
+ value: 'busan',
1158
+ label: '부산',
1159
+ children: [
1160
+ {
1161
+ value: 'haeundae',
1162
+ label: '해운대구',
1163
+ },
1164
+ ],
1165
+ },
1166
+ ];
1167
+
1168
+ <FormItem name="location" label={t("지역")}>
1169
+ <Cascader
1170
+ options={options}
1171
+ placeholder={t("지역을 선택하세요")}
1172
+ changeOnSelect
1173
+ />
1174
+ </FormItem>
1175
+ ```
1176
+
1177
+ ### 7.6 TreeSelect
1178
+
1179
+ TreeSelect는 트리 형태의 드롭다운 선택기입니다.
1180
+
1181
+ #### 기본 TreeSelect
1182
+ ```tsx
1183
+ import { TreeSelect } from "antd";
1184
+
1185
+ const treeData = [
1186
+ {
1187
+ title: '전체',
1188
+ value: 'all',
1189
+ children: [
1190
+ {
1191
+ title: '카테고리 1',
1192
+ value: 'category1',
1193
+ children: [
1194
+ {
1195
+ title: '하위 카테고리 1-1',
1196
+ value: 'subcategory1-1',
1197
+ },
1198
+ ],
1199
+ },
1200
+ {
1201
+ title: '카테고리 2',
1202
+ value: 'category2',
1203
+ },
1204
+ ],
1205
+ },
1206
+ ];
1207
+
1208
+ <FormItem name="category" label={t("카테고리")}>
1209
+ <TreeSelect
1210
+ style={{ width: '100%' }}
1211
+ dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
1212
+ treeData={treeData}
1213
+ placeholder={t("카테고리를 선택하세요")}
1214
+ treeDefaultExpandAll
1215
+ />
1216
+ </FormItem>
1217
+ ```
1218
+
1219
+ ### 7.7 AutoComplete
1220
+
1221
+ AutoComplete는 자동 완성 입력 컴포넌트입니다.
1222
+
1223
+ #### 기본 AutoComplete
1224
+ ```tsx
1225
+ import { AutoComplete } from "antd";
1226
+
1227
+ const [options, setOptions] = useState<{value: string}[]>([]);
1228
+
1229
+ const onSearch = (searchText: string) => {
1230
+ const filteredOptions = mockData
1231
+ .filter(item => item.includes(searchText))
1232
+ .map(item => ({ value: item }));
1233
+ setOptions(filteredOptions);
1234
+ };
1235
+
1236
+ <FormItem name="search" label={t("검색")}>
1237
+ <AutoComplete
1238
+ options={options}
1239
+ onSearch={onSearch}
1240
+ placeholder={t("검색어를 입력하세요")}
1241
+ filterOption={(inputValue, option) =>
1242
+ option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
1243
+ }
1244
+ />
1245
+ </FormItem>
1246
+ ```
1247
+
1248
+ ### 7.8 기타 유용한 컴포넌트들
1249
+
1250
+ #### Slider (범위 슬라이더)
1251
+ ```tsx
1252
+ import { Slider } from "antd";
1253
+
1254
+ <FormItem name="range" label={t("범위")}>
1255
+ <Slider
1256
+ range
1257
+ min={0}
1258
+ max={100}
1259
+ defaultValue={[20, 50]}
1260
+ marks={{
1261
+ 0: '0',
1262
+ 25: '25',
1263
+ 50: '50',
1264
+ 75: '75',
1265
+ 100: '100',
1266
+ }}
1267
+ />
1268
+ </FormItem>
1269
+ ```
1270
+
1271
+ #### Rate (평점)
1272
+ ```tsx
1273
+ import { Rate } from "antd";
1274
+
1275
+ <FormItem name="rating" label={t("평점")}>
1276
+ <Rate allowHalf defaultValue={2.5} />
1277
+ </FormItem>
1278
+ ```
1279
+
1280
+ #### Mention (멘션)
1281
+ ```tsx
1282
+ import { Mentions } from "antd";
1283
+
1284
+ const { Option } = Mentions;
1285
+
1286
+ <FormItem name="comment" label={t("댓글")}>
1287
+ <Mentions
1288
+ rows={3}
1289
+ placeholder={t("@사용자명을 입력하면 멘션됩니다")}
1290
+ >
1291
+ <Option value="user1">사용자1</Option>
1292
+ <Option value="user2">사용자2</Option>
1293
+ <Option value="user3">사용자3</Option>
1294
+ </Mentions>
1295
+ </FormItem>
1296
+ ```
1297
+
1298
+ ## 8. 고급 사용 패턴
1299
+
1300
+ ### 7.1 조건부 렌더링과 함께 사용
1301
+
1302
+ ```tsx
1303
+ const watchedValue = Form.useWatch("parentField", form);
1304
+
1305
+ return (
1306
+ <>
1307
+ <FormItem name="parentField" label={t("부모필드")}>
1308
+ <Select options={parentOptions} />
1309
+ </FormItem>
1310
+
1311
+ {watchedValue === "specific_value" && (
1312
+ <Row gutter={16}>
1313
+ <Col span={12}>
1314
+ <FormItem name="childField1" label={t("자식필드1")}>
1315
+ <Input />
1316
+ </FormItem>
1317
+ </Col>
1318
+ <Col span={12}>
1319
+ <FormItem name="childField2" label={t("자식필드2")}>
1320
+ <Select options={childOptions} />
1321
+ </FormItem>
1322
+ </Col>
1323
+ </Row>
1324
+ )}
1325
+ </>
1326
+ );
1327
+ ```
1328
+
1329
+ ### 7.2 shouldUpdate 패턴
1330
+
1331
+ ```tsx
1332
+ <Form.Item
1333
+ shouldUpdate={(prevValues, currentValues) =>
1334
+ prevValues.triggerField !== currentValues.triggerField
1335
+ }
1336
+ >
1337
+ {({ getFieldValue }) => {
1338
+ const triggerValue = getFieldValue("triggerField");
1339
+ return (
1340
+ <Row gutter={16}>
1341
+ <Col span={triggerValue === "Y" ? 12 : 24}>
1342
+ <FormItem name="conditionalField1">
1343
+ <Input />
1344
+ </FormItem>
1345
+ </Col>
1346
+ {triggerValue === "Y" && (
1347
+ <Col span={12}>
1348
+ <FormItem name="conditionalField2">
1349
+ <Select options={options} />
1350
+ </FormItem>
1351
+ </Col>
1352
+ )}
1353
+ </Row>
1354
+ );
1355
+ }}
1356
+ </Form.Item>
1357
+ ```
1358
+
1359
+ ### 7.3 복합 입력 컴포넌트
1360
+
1361
+ ```tsx
1362
+ <FormItem label={t("기간 설정")}>
1363
+ <Input.Group compact>
1364
+ <FormItem name="startDate" noStyle rules={[{ required: true }]}>
1365
+ <DatePicker placeholder="시작일" style={{ width: '50%' }} />
1366
+ </FormItem>
1367
+ <FormItem name="endDate" noStyle rules={[{ required: true }]}>
1368
+ <DatePicker placeholder="종료일" style={{ width: '50%' }} />
1369
+ </FormItem>
1370
+ </Input.Group>
1371
+ </FormItem>
1372
+ ```
1373
+
1374
+ ### 7.4 동적 리스트 관리
1375
+
1376
+ ```tsx
1377
+ <Form.List name="items">
1378
+ {(fields, { add, remove }) => (
1379
+ <>
1380
+ {fields.map(({ key, name, ...restField }) => (
1381
+ <Row key={key} gutter={16} align="middle">
1382
+ <Col span={20}>
1383
+ <FormItem
1384
+ {...restField}
1385
+ name={[name, 'itemName']}
1386
+ label={t("항목명")}
1387
+ rules={[{ required: true }]}
1388
+ >
1389
+ <Input />
1390
+ </FormItem>
1391
+ </Col>
1392
+ <Col span={4}>
1393
+ <Button
1394
+ type="link"
1395
+ danger
1396
+ onClick={() => remove(name)}
1397
+ icon={<DeleteIcon />}
1398
+ >
1399
+ 삭제
1400
+ </Button>
1401
+ </Col>
1402
+ </Row>
1403
+ ))}
1404
+ <Button
1405
+ type="dashed"
1406
+ onClick={() => add()}
1407
+ block
1408
+ icon={<PlusIcon />}
1409
+ >
1410
+ + 항목 추가
1411
+ </Button>
1412
+ </>
1413
+ )}
1414
+ </Form.List>
1415
+ ```
1416
+
1417
+ ## 8. 성능 최적화 팁
1418
+
1419
+ ### 8.1 Form.Item preserve 속성
1420
+ ```tsx
1421
+ <FormItem
1422
+ name="temporaryField"
1423
+ preserve={false} // 컴포넌트 언마운트 시 값 제거
1424
+ >
1425
+ <Input />
1426
+ </FormItem>
1427
+ ```
1428
+
1429
+ ### 8.2 Table 가상화
1430
+ ```tsx
1431
+ <Table
1432
+ virtual
1433
+ scroll={{ y: 400 }} // 높이 제한으로 가상 스크롤 활성화
1434
+ dataSource={largeDataset}
1435
+ columns={columns}
1436
+ />
1437
+ ```
1438
+
1439
+ ### 8.3 Select 검색 최적화
1440
+ ```tsx
1441
+ <Select
1442
+ showSearch
1443
+ filterOption={(input, option) =>
1444
+ (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
1445
+ }
1446
+ options={options}
1447
+ />
1448
+ ```
1449
+
1450
+ ## 9. 접근성 개선
1451
+
1452
+ ### 9.1 적절한 라벨링
1453
+ ```tsx
1454
+ <FormItem
1455
+ name="field"
1456
+ label={t("필수 입력 필드")}
1457
+ required // 시각적 필수 표시
1458
+ rules={[{ required: true, message: t("이 필드는 필수입니다.") }]}
1459
+ >
1460
+ <Input aria-describedby="field-help" />
1461
+ </FormItem>
1462
+ <div id="field-help">{t("이 필드에 대한 추가 설명")}</div>
1463
+ ```
1464
+
1465
+ ### 9.2 키보드 내비게이션
1466
+ ```tsx
1467
+ <Space>
1468
+ <Button tabIndex={1} onClick={handleSave}>
1469
+ {t("저장")}
1470
+ </Button>
1471
+ <Button tabIndex={2} onClick={handleCancel}>
1472
+ {t("취소")}
1473
+ </Button>
1474
+ </Space>
1475
+ ```
1476
+
1477
+ ## 10. 프로젝트 특화 패턴
1478
+
1479
+ ### 10.1 다국어 지원 (i18n)
1480
+ 모든 텍스트는 `useI18n` 훅을 통해 다국어 처리:
1481
+ ```tsx
1482
+ const { t } = useI18n();
1483
+
1484
+ <FormItem name="title" label={t("제목")}>
1485
+ <Input placeholder={t("제목을 입력하세요")} />
1486
+ </FormItem>
1487
+ ```
1488
+
1489
+ ### 10.2 코드 스토어 활용
1490
+ 공통 코드는 `useCodeStore`를 통해 관리:
1491
+ ```tsx
1492
+ const SLE_TPCD = useCodeStore((s) => s.SLE_TPCD);
1493
+
1494
+ <FormItem name="saleType" label={t("판매유형")}>
1495
+ <Select options={SLE_TPCD?.options} />
1496
+ </FormItem>
1497
+ ```
1498
+
1499
+ ### 10.3 에러 처리 패턴
1500
+ ```tsx
1501
+ const handleSave = useCallback(async () => {
1502
+ try {
1503
+ setSaveSpinning(true);
1504
+ const values = await form.validateFields();
1505
+ await ApiService.save(values);
1506
+ messageApi.success(t("저장되었습니다."));
1507
+ } catch (err) {
1508
+ await errorHandling(err); // 프로젝트 공통 에러 처리
1509
+ } finally {
1510
+ setSaveSpinning(false);
1511
+ }
1512
+ }, [form, messageApi, t]);
1513
+ ```
1514
+
1515
+ 이 가이드를 참고하여 일관성 있고 효율적인 Ant Design 컴포넌트를 사용하시기 바랍니다.