@opsnow-mcp/opsnow-mcp-common-ui-server 1.0.40 → 1.0.41

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 (37) hide show
  1. package/build/components/all-component-tools.js +2 -0
  2. package/build/components/data/icon-names.generated.js +341 -0
  3. package/build/components/data/icon-names.js +32 -16
  4. package/build/components/examples/opsnow-common-chart-examples-data.js +73 -78
  5. package/build/components/examples/opsnow-common-forms-examples-data.js +1 -11
  6. package/build/components/examples/opsnow-common-grid-examples-data.js +13 -20
  7. package/build/components/examples/opsnow-common-layout-examples-data.js +12 -13
  8. package/build/components/examples/opsnow-common-popup-examples-data.js +14 -28
  9. package/build/components/examples/opsnow-common-progress-examples-data.js +0 -5
  10. package/build/components/examples/opsnow-common-stepper-examples-data.js +2 -3
  11. package/build/components/examples/opsnow-common-storage-examples-data.js +23 -4
  12. package/build/components/examples/opsnow-common-tooltip-examples-data.js +1 -21
  13. package/build/components/opsnow-common-calendar.js +36 -15
  14. package/build/components/opsnow-common-chart.js +213 -132
  15. package/build/components/opsnow-common-coach-mark.js +3 -2
  16. package/build/components/opsnow-common-data-status.js +17 -11
  17. package/build/components/opsnow-common-dropdown.js +80 -0
  18. package/build/components/opsnow-common-examples.js +4 -2
  19. package/build/components/opsnow-common-file-upload.js +4 -1
  20. package/build/components/opsnow-common-forms.js +73 -36
  21. package/build/components/opsnow-common-grid.js +57 -29
  22. package/build/components/opsnow-common-icons.js +23 -39
  23. package/build/components/opsnow-common-jsx-helper.js +36 -0
  24. package/build/components/opsnow-common-layout.js +66 -25
  25. package/build/components/opsnow-common-notification.js +3 -2
  26. package/build/components/opsnow-common-pagination.js +4 -4
  27. package/build/components/opsnow-common-popup.js +12 -11
  28. package/build/components/opsnow-common-progress.js +10 -16
  29. package/build/components/opsnow-common-select.js +26 -13
  30. package/build/components/opsnow-common-stepper.js +30 -7
  31. package/build/components/opsnow-common-tab.js +5 -3
  32. package/build/components/opsnow-common-toast-popup.js +1 -0
  33. package/build/components/opsnow-common-tooltip.js +44 -46
  34. package/build/components/opsnow-common-typography.js +11 -5
  35. package/build/components/opsnow-common-validate-props.js +0 -0
  36. package/build/index.js +2 -0
  37. package/package.json +2 -1
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { jsxAttrs, jsxTag, jsxResult } from "./opsnow-common-jsx-helper.js";
2
3
  const gridOptionsSchema = z.object({
3
4
  animateRows: z.boolean().optional().describe('행 애니메이션 사용 여부'),
4
5
  rowSelection: z.object({
@@ -9,7 +10,7 @@ const gridOptionsSchema = z.object({
9
10
  enableSelectionWithoutKeys: z.boolean().optional().describe('키 없이 선택 허용'),
10
11
  }).optional().describe('행 선택 옵션'),
11
12
  tooltipShowDelay: z.number().optional().describe('툴팁 표시 지연 시간'),
12
- domLayout: z.enum(['normal', 'autoHeight', 'print']).optional().describe('그리드 레이아웃 방식'),
13
+ domLayout: z.enum(['autoHeight', 'print']).optional().describe("그리드 레이아웃 방식 — 'autoHeight' 권장(최대 maxGridHeight까지 콘텐츠 맞춤). 고정 높이는 이 키를 빼고 gridHeight. ⚠️ 'normal'은 쓰지 말 것: 컨테이너 높이가 비워져 그리드가 0px로 사라짐(부모 높이와 무관, grid 2.0.7 브라우저 실측)"),
13
14
  suppressCellFocus: z.boolean().optional().describe('셀 포커스 비활성화 (셀 클릭 이벤트 사용 시 필수: true)'),
14
15
  suppressPaginationPanel: z.boolean().optional().describe('페이지네이션 패널 숨김'),
15
16
  masterDetail: z.boolean().optional().describe('마스터/디테일 모드 사용'),
@@ -38,18 +39,20 @@ const columnDefsSchema = z.lazy(() => z.object({
38
39
  colId: z.string().optional().describe('컬럼 고유 ID'),
39
40
  groupId: z.string().optional().describe('컬럼 그룹 고유 ID'),
40
41
  width: z.number().optional().describe('컬럼 너비'),
41
- minWidth: z.number().describe('컬럼 최소 너비'),
42
+ minWidth: z.number().optional().describe('컬럼 최소 너비'),
42
43
  maxWidth: z.number().optional().describe('컬럼 최대 너비'),
43
44
  pinned: z.enum(['left', 'right']).optional().describe('컬럼 고정 위치'),
44
45
  type: z.array(z.enum([
46
+ 'default',
45
47
  'number',
48
+ 'integer',
46
49
  'cost',
47
50
  'ratio',
48
- 'mixFormat',
49
51
  'numberTooltip',
52
+ 'mixFormat',
50
53
  'ratioBar',
51
54
  'dataBar',
52
- 'value'
55
+ 'date'
53
56
  ])).optional().describe(`컬럼 타입.
54
57
  - ratioBar: 여러 색상/세그먼트로 구성 비율을 보여주는 스택 차트 스타일 바. 여러 필드의 비율을 시각화할 때 사용 (예: Cost Composition by Type). 'Ratio Bar' 예제 참고.
55
58
  - dataBar: 단일 색상으로 셀 배경을 값에 비례하여 채우는 Excel 스타일 바. 단일 값의 크기를 시각화할 때 사용 (예: 월별 비용 트렌드). 'Data Bar' 예제 참고.`),
@@ -60,6 +63,8 @@ const columnDefsSchema = z.lazy(() => z.object({
60
63
  digits: z.number().optional().describe('소수점 자리수'),
61
64
  iconRight: z.boolean().optional().describe('오른쪽 아이콘 표시'),
62
65
  isPercent: z.boolean().optional().describe('퍼센트 표시 여부'),
66
+ vendor: z.string().optional().describe('cost 타입: 벤더별 통화·포맷 (예: \'aws\')'),
67
+ isNegative: z.boolean().optional().describe('cost 타입: 음수 표시 처리'),
63
68
  fields: z.array(z.string()).optional().describe('비율바 등에서 여러 필드'),
64
69
  label: z.object({
65
70
  postfix: z.string().optional().describe('라벨 접미사'),
@@ -123,9 +128,6 @@ const defaultColDefSchema = z.object({
123
128
  });
124
129
  // ag-grid 기반 OpsnowCommonDataGrid 스키마 정의
125
130
  export const DataGridSchema = z.object({
126
- totalPage: z.number().optional().describe('전체 페이지 수'),
127
- initPageNum: z.number().optional().describe('초기 페이지 번호'),
128
- dropdownSearchDefaultValue: z.string().optional().describe('드롭다운 검색 기본값'),
129
131
  gridOptions: gridOptionsSchema.optional().describe('ag-grid 옵션'),
130
132
  columnDefs: z.array(columnDefsSchema).describe("ag-grid 컬럼 정의").min(1),
131
133
  defaultColDef: defaultColDefSchema.optional().describe('ag-grid 기본 컬럼 옵션'),
@@ -153,11 +155,12 @@ export const DataGridSchema = z.object({
153
155
  setStatus(CommonConst.GRID_STATUS.ERROR)
154
156
  })
155
157
  }, [])`),
156
- gridHeight: z.number().optional().describe('그리드 높이'),
158
+ gridHeight: z.number().optional().describe("고정 높이(px, 기본 483). gridOptions.domLayout을 지정하지 않았을 때만 적용 — domLayout에 값이 있으면 무시됨 ('normal'이면 그리드가 0px로 사라짐)"),
159
+ maxGridHeight: z.number().optional().describe("domLayout:'autoHeight'일 때 최대 높이(px, 헤더 42px 포함, 기본 600). 넘치면 본문 세로 스크롤. grid ≥2.0.6"),
160
+ unlimitedHeight: z.boolean().optional().describe("true면 autoHeight 높이 제한 해제(모든 행 펼침). 기본 false. 짧은 고정 목록에만 사용"),
157
161
  setClass: z.string().optional().describe('그리드 클래스명'),
158
- localRowClass: z.string().optional().describe('로컬 행 클래스명'),
159
162
  pagination: z.boolean().optional().describe('페이지네이션 사용 여부 (true로 설정 시 내부적으로 opsnow-common-pagination 컴포넌트 사용)'),
160
- paginationPageSize: z.number().optional().describe('페이지당 행 수'),
163
+ paginationPageSize: z.number().optional().describe('페이지당 행 수 (기본값: 10)'),
161
164
  textNoData: z.string().optional().describe('데이터 없음 텍스트'),
162
165
  noDataDescription: z.string().optional().describe('데이터 없음 설명'),
163
166
  textLoading: z.string().optional().describe('로딩 중 텍스트'),
@@ -166,25 +169,24 @@ export const DataGridSchema = z.object({
166
169
  filterSearchText: z.string().optional().describe('필터 검색 텍스트'),
167
170
  searchTextNoData: z.string().optional().describe('검색 결과 없음 텍스트'),
168
171
  searchTextNoDataDescription: z.string().optional().describe('검색 결과 없음 설명'),
169
- pagingFromServer: z.boolean().optional().describe('서버 페이징 사용 여부'),
170
172
  pageSizeList: z.array(z.object({
171
173
  label: z.string().describe('페이지 사이즈 라벨'),
172
174
  value: z.number().describe('페이지 사이즈 값')
173
- })).optional().describe('페이지 사이즈 리스트 (예: [{ label: "5", value: 5 }, { label: "10", value: 10 }])'),
175
+ })).optional().describe('페이지 사이즈 리스트 (기본값: 10/20/50, 예: [{ label: "5", value: 5 }, { label: "10", value: 10 }])'),
174
176
  placeholderText: z.string().optional().describe('플레이스홀더 텍스트'),
175
- usedRowClass: z.boolean().optional().describe('행 클래스 사용 여부 (셀 클릭 이벤트 사용 시 필수: true)'),
177
+ usedRowClass: z.boolean().optional().describe('클릭 가능한 행 스타일(hover·커서) 적용 여부 — onCellClick이 있으면 자동 적용되므로 보통 지정 불필요'),
176
178
  usedSearch: z.boolean().optional().describe('검색창 사용 여부'),
177
179
  usedSearchAndDropdown: z.boolean().optional().describe('검색+드롭다운 동시 사용'),
178
180
  useOpsnowUs: z.boolean().optional().describe('Opsnow US 스타일 사용'),
179
181
  inTile: z.boolean().optional().describe('타일 내 그리드 여부'),
180
182
  noDataCustomComponent: z.any().optional().describe('커스텀 No Data 컴포넌트'),
181
- rowModelType: z.string().optional().describe('행 모델 타입'),
183
+ rowModelType: z.enum(['clientSide', 'serverSide']).optional().describe("행 모델 타입 (기본값: 'clientSide')"),
182
184
  serverSideDatasource: z.record(z.any()).optional().describe('서버사이드 데이터소스'),
183
185
  cacheBlockSize: z.number().optional().describe('서버사이드 캐시 블록 크기'),
184
186
  usedMenuItem: z.boolean().optional().describe('메뉴 아이템 사용 여부'),
185
187
  showSelectedRowCount: z.boolean().optional().describe('선택 행 개수 표시'),
186
- isAutoSizeAllColumns: z.boolean().optional().describe('모든 컬럼 자동 크기 조정'),
187
- langCd: z.string().optional().describe('언어 코드'),
188
+ isAutoSizeAllColumns: z.boolean().optional().describe('모든 컬럼 자동 크기 조정. 모바일(≤767.95px)에서는 자동으로 true — 컬럼이 내용 폭에 맞춰지고, 컨테이너보다 넓으면 가로 스크롤이 생김 (flex/sizeColumnsToFit 전제 레이아웃은 모바일에서 달라질 수 있음)'),
189
+ langCd: z.string().optional().describe("언어 코드 (기본값: 'ko') — 보통 i18n.getLocale()"),
188
190
  useTreeDataDragDrop: z.boolean().optional().describe('트리 데이터 드래그 사용'),
189
191
  getDropTargetNode: z.string().optional().describe('드롭 타겟 노드 반환 함수(stringified)'),
190
192
  emitQuickFilterChange: z.boolean().optional().describe('빠른 필터 변경 이벤트 발생'),
@@ -201,6 +203,12 @@ export const DataGridSchema = z.object({
201
203
  onSetPage: z.string().optional().describe('페이지 변경 이벤트 핸들러(stringified)'),
202
204
  serverSideTotalPageSize: z.number().optional().describe('서버사이드 전체 페이지 수')
203
205
  });
206
+ export const HeaderFilterSchema = z.object({
207
+ gridData: z.string().describe("AG Grid가 headerComponent에 넘기는 header params 변수명 (column, displayName, enableSorting, setSort, context) (필수)"),
208
+ textRight: z.boolean().optional().describe("헤더 텍스트 우측 정렬 (숫자 컬럼)"),
209
+ isUsedMenuFilter: z.boolean().optional().describe("헤더 필터 메뉴 사용 여부"),
210
+ children: z.string().optional().describe("헤더 텍스트 옆에 넣을 JSX (예: 툴팁 아이콘)"),
211
+ });
204
212
  // DataGrid 컴포넌트 함수 - 배열 반환
205
213
  export function createDataGridComponent() {
206
214
  return [
@@ -242,18 +250,19 @@ export function createDataGridComponent() {
242
250
 
243
251
  ⚠️ defaultColDef에 wrapText, autoHeight가 없으면 텍스트가 넘칩니다!
244
252
 
245
- **CRITICAL: 행 수가 많을 수 있으면 - 높이 분기 필수**
246
- autoHeight(domLayout:'autoHeight')는 모든 행을 펼쳐서 그리드가 무한정 길어짐.
247
- 행 수가 가변적이거나 많아질 수 있으면 행 수 기준으로 분기:
253
+ **그리드 높이 정책 (grid ≥2.0.6) — 행 수로 분기하지 말 것**
254
+ - 기본: gridOptions.domLayout: 'autoHeight' 하나만. 행이 적으면 콘텐츠 높이에 맞추고,
255
+ maxGridHeight(기본 600px, 헤더 42px 포함)를 넘으면 본문에 세로 스크롤이 자동으로 생긴다.
256
+ - 상한 변경: maxGridHeight={800} / 상한 해제(모든 행 펼침): unlimitedHeight (짧은 고정 목록에만)
257
+ - 고정 높이: gridOptions에서 domLayout 키를 아예 빼고 gridHeight={N} (기본 483)
258
+ - ⚠️ domLayout: 'normal'을 명시하면 컨테이너 높이가 비워져 그리드가 0px로 사라진다 (gridHeight·부모 높이와 무관 — 브라우저 실측).
259
+ isScroll ? 'normal' : 'autoHeight' 같은 행 수 분기 패턴 금지.
260
+ - 상한은 헤더 1줄(42px) 기준이라 헤더가 여러 줄이면 그만큼 커지고, 페이지네이션 영역은 상한 밖이다.
261
+ - 모바일(≤767.95px)에서는 컬럼을 autoSizeAllColumns로 맞추므로 가로 스크롤이 생길 수 있다.
248
262
 
249
- const ROW_LIMIT = 10;
250
- const isScroll = rowData.length > ROW_LIMIT;
251
- const gridOptions = {
252
- // ... 다른 설정들
253
- domLayout: isScroll ? 'normal' : 'autoHeight', // 10행 이하: 콘텐츠 맞춤 / 초과: 고정+스크롤
254
- };
255
- // 10행 초과 시에만 고정 높이 부여 (autoHeight 셀은 가변이라 픽셀 고정값 사용)
256
- <OpsnowCommonDataGrid gridOptions={gridOptions} gridHeight={isScroll ? 600 : undefined} />
263
+ const gridOptions = { /* ... */ domLayout: 'autoHeight' };
264
+ <OpsnowCommonDataGrid gridOptions={gridOptions} /> // 최대 600px, 넘치면 스크롤
265
+ <OpsnowCommonDataGrid gridOptions={gridOptions} maxGridHeight={800} /> // 상한 변경
257
266
 
258
267
  **셀·행 단위 부분 로딩 (Partial Loading) — status로 하지 말 것**
259
268
  status={GRID_STATUS.LOADING}은 그리드 전체를 가립니다. "이 셀만 재계산 중", "선택한 행만 동기화 중" 같은 부분 로딩은
@@ -264,7 +273,8 @@ export function createDataGridComponent() {
264
273
  4. 인디케이터로 셀 콘텐츠를 치환하지 말고, 원래 콘텐츠를 visibility: hidden 으로 남기고 인디케이터를 position: absolute 로 겹친다 (autoHeight라 치환하면 행 높이가 바뀜)
265
274
  5. columnDefs/셀 렌더러는 useMemo/useCallback 으로 고정 — 매 렌더마다 새로 만들면 그리드가 통째로 다시 그려진다
266
275
  6. 선택 노드 수집: forEachNode + node.isSelected() (getSelectedNodes는 ref API에 없음). onSelectionChanged는 top-level prop (인자 = 선택 노드 배열)
267
- ref API 요약: refreshCells / redrawRows / forEachNode / node.setDataValue(field, value) / downloadExcel / downloadCsv
276
+ ref API (gridRef.current.*, 26종): getColumnState, applyColumnState, setColumnDefs, setAutoGroupColumnDef, sizeColumnsToFit / setRowData, applyTransaction, getGridData, getDisplayedRowAtIndex, forEachNode, forEachNodeAfterFilter, refreshCells, redrawRows / setNodesSelected, checkBoxSelectAll, saveSelectionState, loadSelectionState, setSelectionState, clearSelectionState / updateFilter / downloadExcel, downloadCsv / getRowDropZoneParams, addRowDropZone / setServerSideDatasource, setServerSideFilterList (노드 값 변경은 node.setDataValue(field, value))
277
+ ⚠️ DataGrid는 AG Grid 옵션을 최상위 prop으로 받지 않습니다 (rowHeight, getRowId, context, onRowClicked 등) — 모두 gridOptions 안에 넣으세요.
268
278
  rowModelType: 'serverSide' 그리드는 AG Grid 내장 loadingCellRenderer를 쓰는 것이 맞습니다.
269
279
 
270
280
  **import:**
@@ -375,6 +385,24 @@ export function createDataGridComponent() {
375
385
  ]
376
386
  };
377
387
  }
378
- }
388
+ },
389
+ {
390
+ name: "createHeaderFilter",
391
+ description: `HeaderFilter 컴포넌트 - DataGrid 컬럼 헤더에 정렬·필터 메뉴를 붙이는 커스텀 헤더
392
+ JSX로 직접 배치하지 않고 columnDefs의 headerComponent로 등록합니다. gridData에는 AG Grid가 넘기는 header params를 그대로 전달합니다.
393
+ 예: { field: 'cost', headerComponent: (params) => <OpsnowCommonHeaderFilter gridData={params} textRight /> }
394
+
395
+ **import:**
396
+ \`\`\`javascript
397
+ import { useCommonComponents } from '@opsnow-common/opsnow-finops-common-ui-loader';
398
+ const { OpsnowCommonHeaderFilter } = useCommonComponents();
399
+ \`\`\``,
400
+ parameters: HeaderFilterSchema,
401
+ handler: async (args) => {
402
+ const { children, ...rest } = args;
403
+ const tag = jsxTag("OpsnowCommonHeaderFilter", jsxAttrs(rest, { exprKeys: ["gridData"] }), children);
404
+ return jsxResult(`const headerFilterColumn = {\n field: 'fieldName',\n headerComponent: (${args.gridData}) => (\n${tag}\n ),\n}\n// columnDefs={[ ..., headerFilterColumn ]}`);
405
+ },
406
+ },
379
407
  ];
380
408
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { jsxAttrs, jsxTag, jsxResult } from "./opsnow-common-jsx-helper.js";
2
3
  import { IconNamesMap, generateIconDescription } from "./data/icon-names.js";
3
4
  export const IconSchema = z.object({
4
5
  iconName: z.enum(Object.keys(IconNamesMap)).describe(generateIconDescription({ format: 'nested' })),
@@ -8,21 +9,20 @@ export const IconSchema = z.object({
8
9
  onClick: z.string().optional().describe("클릭 이벤트 핸들러 함수명 (HTMLAttributes 상속으로 span에 적용됨)"),
9
10
  });
10
11
  export const MuiIconSchema = z.object({
11
- theme: z.string().optional().describe("테마명"),
12
+ theme: z.enum(["mixed-circle-line-legend", "mixed-stack-line-legend", "diagonal-legend"]).optional().describe("레전드 마커 모양 (범례용일 때만) — 아이콘 이름은 theme이 아니라 label"),
12
13
  hexColor: z.string().optional().describe("HEX 색상"),
13
14
  size: z.number().optional().default(24).describe("아이콘 크기(px)"),
14
15
  noCircle: z.boolean().optional().describe("원형 배경 없음"),
15
16
  alpha: z.number().optional().describe("투명도"),
16
- color: z.string().optional().describe("MUI 색상"),
17
- label: z.string().optional().describe("MUI 아이콘 이름")
17
+ color: z.enum(["primary", "secondary", "success", "error", "info", "warning", "inherit"]).optional().describe("MUI 색상"),
18
+ label: z.string().optional().describe("MUI(Material) 아이콘 이름 (예: 'close', 'menu') — 기본값 add_icon")
19
+ });
20
+ export const MarkersSchema = z.object({
21
+ theme: z.enum(["square", "line-graph", "line-trend", "line-dashed", "mixed-line-stack"]).optional().describe("마커 모양 — square(사각), line-graph/line-trend/line-dashed(선), mixed-line-stack(선+막대)"),
22
+ hexColor: z.string().optional().describe("마커 색상 HEX (미지정 시 primary.main)"),
23
+ lineAlpha: z.number().optional().describe("선 부분 투명도 (0~1)"),
24
+ shapesAlpha: z.number().optional().describe("도형 부분 투명도 (0~1). mixed-line-stack이면 0.3으로 고정"),
18
25
  });
19
- // export const MarkersSchema = z.object({
20
- // theme: z.string().describe("마커 테마"),
21
- // hexColor: z.string().optional().describe("HEX 색상"),
22
- // lineAlpha: z.number().optional().describe("선 투명도"),
23
- // size: z.number().optional().default(24).describe("마커 크기(px)")
24
- // });
25
- // export type MarkersProps = z.infer<typeof MarkersSchema>;
26
26
  export function createIconsComponent() {
27
27
  return [
28
28
  {
@@ -103,34 +103,18 @@ export function createIconsComponent() {
103
103
  };
104
104
  }
105
105
  },
106
- // {
107
- // name: "createMarkers",
108
- // description: `마커 컴포넌트 - 그래프/차트용 심볼
109
- // - 그래프, 차트 등에서 포인트/마커로 활용
110
- // - theme(마커 테마), hexColor, lineAlpha, size 등 다양한 옵션 지원
111
- // - 아이콘은 useOpsnowCommonIcons 훅을 통해 생성해야 합니다.
112
- // **import 예시:**
113
- // \`\`\`javascript
114
- // import { useOpsnowCommonIcons } from '@opsnow-common/opsnow-finops-common-ui-loader';
115
- // const { OpsnowCommonMarkers } = useOpsnowCommonIcons();
116
- // \`\`\``,
117
- // parameters: MarkersSchema,
118
- // handler: async (args: MarkersProps) => {
119
- // const props: string[] = [];
120
- // props.push(`theme="${args.theme}"`);
121
- // if (args.hexColor) props.push(`hexColor="${args.hexColor}"`);
122
- // if (args.lineAlpha) props.push(`lineAlpha={${args.lineAlpha}}`);
123
- // if (args.size) props.push(`size={${args.size}}`);
124
- // const code = `<OpsnowCommonMarkers ${props.join(" ")} />`;
125
- // return {
126
- // content: [
127
- // {
128
- // type: "text" as const,
129
- // text: `\`\`\`jsx\n${code}\n\`\`\``
130
- // }
131
- // ]
132
- // };
133
- // }
134
- // }
106
+ {
107
+ name: "createMarkers",
108
+ description: `Markers 컴포넌트 - 차트 범례·커스텀 레전드에 쓰는 시리즈 마커 (선/도형 조합)
109
+ 크기는 고정이라 size prop이 없습니다.
110
+
111
+ **import:**
112
+ \`\`\`javascript
113
+ import { useOpsnowCommonIcons } from '@opsnow-common/opsnow-finops-common-ui-loader';
114
+ const { OpsnowCommonMarkers } = useOpsnowCommonIcons();
115
+ \`\`\``,
116
+ parameters: MarkersSchema,
117
+ handler: async (args) => jsxResult(jsxTag("OpsnowCommonMarkers", jsxAttrs(args))),
118
+ },
135
119
  ];
136
120
  }
@@ -0,0 +1,36 @@
1
+ // 생성기 공용: 툴 입력(args)을 JSX 속성 문자열로 변환
2
+ // - exprKeys에 든 키(상태 변수명·함수명·JSX)는 {값}으로, 그 외 문자열은 "값"으로 출력
3
+ // - true → 속성만, false → {false}, 숫자 → {n}, 객체/배열 → {JSON}
4
+ // - skipKeys는 출력하지 않음 (생성기 입력 전용 키 등)
5
+ export function jsxAttrs(args, opts = {}) {
6
+ const exprKeys = new Set(opts.exprKeys ?? []);
7
+ const skipKeys = new Set(opts.skipKeys ?? []);
8
+ const out = [];
9
+ for (const [k, v] of Object.entries(args)) {
10
+ if (v === undefined || v === null || skipKeys.has(k))
11
+ continue;
12
+ if (typeof v === "string")
13
+ out.push(exprKeys.has(k) ? `${k}={${v}}` : `${k}="${v}"`);
14
+ else if (typeof v === "boolean")
15
+ out.push(v ? k : `${k}={false}`);
16
+ else if (typeof v === "number")
17
+ out.push(`${k}={${v}}`);
18
+ else
19
+ out.push(`${k}={${JSON.stringify(v)}}`);
20
+ }
21
+ return out;
22
+ }
23
+ // 여러 줄 JSX 태그 조립 (children 없으면 self-closing)
24
+ export function jsxTag(tag, attrs, children) {
25
+ const open = attrs.length ? `<${tag}\n ${attrs.join("\n ")}\n` : `<${tag}`;
26
+ if (!children)
27
+ return attrs.length ? `${open}/>` : `${open} />`;
28
+ return `${attrs.length ? open : open + " "}>\n ${children}\n</${tag}>`.replace(`<${tag} >`, `<${tag}>`);
29
+ }
30
+ // JSX 표현식 값: 문자열은 변수명·함수·JSX로 보고 그대로, 그 외(객체·배열·숫자·불리언)는 JSON 리터럴로
31
+ export function jsxExpr(value) {
32
+ return typeof value === "string" ? value : JSON.stringify(value);
33
+ }
34
+ export function jsxResult(code) {
35
+ return { content: [{ type: "text", text: `\`\`\`jsx\n${code}\n\`\`\`` }] };
36
+ }
@@ -1,21 +1,19 @@
1
1
  import { z } from "zod";
2
+ import { jsxAttrs, jsxTag, jsxResult } from "./opsnow-common-jsx-helper.js";
3
+ // MUI 9 Grid size 값: 컬럼 수(1~12) | 'auto'(콘텐츠 폭) | 'grow'(남은 폭 채움)
4
+ const GridSizeValue = z.union([z.number(), z.enum(["auto", "grow"])]);
5
+ const GridBreakpointSize = z.object({
6
+ xs: GridSizeValue.optional(),
7
+ sm: GridSizeValue.optional(),
8
+ md: GridSizeValue.optional(),
9
+ lg: GridSizeValue.optional(),
10
+ xl: GridSizeValue.optional(),
11
+ });
2
12
  const GridLayoutSchema = z.object({
3
13
  items: z.array(z.string()).describe("렌더링할 JSX 문자열 배열 (각 항목은 컴포넌트 호출 코드)"),
4
- layout: z.array(z.object({
5
- xs: z.number().optional(),
6
- sm: z.number().optional(),
7
- md: z.number().optional(),
8
- lg: z.number().optional(),
9
- xl: z.number().optional(),
10
- }).partial()).optional().describe("각 항목별 브레이크포인트 레이아웃 배열"),
11
- defaultLayout: z.object({
12
- xs: z.number().optional(),
13
- sm: z.number().optional(),
14
- md: z.number().optional(),
15
- lg: z.number().optional(),
16
- xl: z.number().optional(),
17
- }).optional().describe("기본 브레이크포인트 레이아웃"),
18
- spacing: z.number().optional().describe("Grid 간격 (MUI spacing 단위)"),
14
+ layout: z.array(GridBreakpointSize.partial()).optional().describe("각 항목별 브레이크포인트 size 배열 (MUI 9 Grid size prop으로 출력, 예: { xs: 12, md: 6 })"),
15
+ defaultLayout: GridBreakpointSize.optional().describe("기본 브레이크포인트 size (기본 { xs: 'grow' })"),
16
+ spacing: z.number().optional().describe("Grid 간격 (MUI spacing 단위). 미지정 시 섹션 간격 토큰(sectionGap 24px) 사용"),
19
17
  tileProps: z.record(z.any()).optional().describe("OpsnowCommonTile에 전달할 추가 props"),
20
18
  });
21
19
  const MegaFilterSchema = z.object({
@@ -23,10 +21,12 @@ const MegaFilterSchema = z.object({
23
21
  vendors: z.string().optional().describe("벤더 옵션 배열 변수명"),
24
22
  selectedVendor: z.string().optional().describe("선택된 벤더 상태 변수명"),
25
23
  onVendorChange: z.string().optional().describe("벤더 변경 이벤트 핸들러"),
26
- onFilterChange: z.string().optional().describe("필터 변경 이벤트 핸들러"),
24
+ onFilterChange: z.string().describe("필터 변경 이벤트 핸들러 (필수)"),
27
25
  selectedDateRange: z.string().optional().describe("선택된 날짜 범위 상태 변수명"),
28
26
  onDateRangeChange: z.string().optional().describe("날짜 범위 변경 이벤트 핸들러"),
29
- locale: z.enum(["en", "ko"]).optional().describe("로케일 설정"),
27
+ locale: z.enum(["en", "ko", "ja", "zh"]).optional().describe("로케일 설정"),
28
+ chipStateOptions: z.string().optional().describe("필터 칩 상태 옵션 배열 변수명"),
29
+ filterCountText: z.string().optional().describe("선택 개수 텍스트 함수명 ((selected, total) => string)"),
30
30
  title: z.string().optional().describe("필터 제목"),
31
31
  clearAllText: z.string().optional().describe("전체 초기화 버튼 텍스트"),
32
32
  clearText: z.string().optional().describe("개별 초기화 버튼 텍스트"),
@@ -36,6 +36,17 @@ const MegaFilterSchema = z.object({
36
36
  noItemsText: z.string().optional().describe("검색 결과 없음 텍스트"),
37
37
  addText: z.string().optional().describe("개별 필터 추가 버튼 텍스트"),
38
38
  });
39
+ export const TileSchema = z.object({
40
+ size: z.union([z.number(), z.enum(["auto", "grow"])]).optional().describe("flex 크기 — 'grow'(남은 폭 채움, 기본값) | 'auto'(콘텐츠 폭) | 숫자(flex 비율)"),
41
+ theme: z.enum(["selectable", "gray", "white", "blue", "dash", "special"]).optional().describe("타일 테마 (dash = 점선 테두리)"),
42
+ borderColor: z.string().optional().describe("테두리 색상"),
43
+ borderStyle: z.string().optional().describe("테두리 스타일 (기본값: solid)"),
44
+ height: z.string().optional().describe("높이 (CSS 값, 예: '240px')"),
45
+ className: z.string().optional().describe("클래스명 — Tile은 sx를 받지 않으므로 스타일은 className으로"),
46
+ onClick: z.string().optional().describe("클릭 핸들러 함수명 (selectable 테마와 함께)"),
47
+ header: z.string().optional().describe("Tile.Header 안에 넣을 JSX (생성기 입력 — <OpsnowCommonTile.Header>로 출력)"),
48
+ content: z.string().optional().describe("Tile.Content 안에 넣을 JSX (생성기 입력 — <OpsnowCommonTile.Content>로 출력)"),
49
+ });
39
50
  export function createGridLayoutComponent() {
40
51
  return [
41
52
  {
@@ -44,7 +55,8 @@ export function createGridLayoutComponent() {
44
55
 
45
56
  **간격 규칙 (px 하드코딩 금지, 색상 토큰처럼 theme.commonSpacing 참조):**
46
57
  - 페이지/그리드 컨테이너 패딩: theme.commonSpacing.pagePadding (24px)
47
- - 섹션(카드 그룹) 사이 간격: theme.commonSpacing.sectionGap (24px) — Grid container의 gap
58
+ - 섹션(카드 그룹) 사이 간격: sectionGap (24px) — Grid container의 spacing="var(--opsnow-spacing-section-gap)" (sx gap 금지: size 폭 계산에 안 들어가 줄바꿈이 깨짐)
59
+ - MUI 9 Grid: <Grid size={{ xs: 12, md: 6 }}> (item/xs/md prop 금지)
48
60
  - 카드(Box) 내부 패딩: theme.commonSpacing.cardPadding ('16px 20px')
49
61
  - 카드 내부 요소 간 간격: theme.commonSpacing.cardGap (16px)
50
62
  - 값이 여러 개이므로 sx는 콜백 형태로: sx={theme => ({ padding: theme.commonSpacing.pagePadding, gap: theme.commonSpacing.sectionGap })}
@@ -56,13 +68,13 @@ export function createGridLayoutComponent() {
56
68
  `,
57
69
  parameters: GridLayoutSchema,
58
70
  handler: async (args) => {
59
- const { items, layout = [], defaultLayout = { xs: true }, spacing = 2, } = args;
71
+ const { items, layout = [], defaultLayout = { xs: 'grow' }, spacing, } = args;
60
72
  const gridItems = items.map((item, i) => {
61
73
  const bp = layout[i] || defaultLayout;
62
- const bpProps = Object.entries(bp)
63
- .map(([k, v]) => `${k}={${v}}`)
64
- .join(" ");
65
- return ` <Grid item ${bpProps} key={${i}}>` +
74
+ const sizeObj = Object.entries(bp)
75
+ .map(([k, v]) => `${k}: ${typeof v === "string" ? `'${v}'` : v}`)
76
+ .join(", ");
77
+ return ` <Grid size={{ ${sizeObj} }} key={${i}}>` +
66
78
  `\n <Box sx={theme => ({` +
67
79
  `\n border: '1px solid #ddd',` +
68
80
  `\n borderRadius: '12px',` +
@@ -77,7 +89,8 @@ export function createGridLayoutComponent() {
77
89
  `\n </Grid>`;
78
90
  }).join("\n");
79
91
  const code = [
80
- `<Grid container wrap="nowrap" spacing={0} sx={theme => ({ padding: theme.commonSpacing.pagePadding, gap: theme.commonSpacing.sectionGap })}>`,
92
+ // 간격은 sx gap이 아니라 spacing으로 (gap은 size 폭 계산에 안 들어감)
93
+ `<Grid container spacing={${spacing !== undefined ? spacing : '"var(--opsnow-spacing-section-gap)"'}} sx={theme => ({ padding: theme.commonSpacing.pagePadding })}>`,
81
94
  gridItems,
82
95
  `</Grid>`,
83
96
  ].join("\n");
@@ -112,6 +125,10 @@ export function createGridLayoutComponent() {
112
125
  props.push(`onVendorChange={${args.onVendorChange}}`);
113
126
  if (args.onFilterChange)
114
127
  props.push(`onFilterChange={${args.onFilterChange}}`);
128
+ if (args.chipStateOptions)
129
+ props.push(`chipStateOptions={${args.chipStateOptions}}`);
130
+ if (args.filterCountText)
131
+ props.push(`filterCountText={${args.filterCountText}}`);
115
132
  if (args.selectedDateRange)
116
133
  props.push(`selectedDateRange={${args.selectedDateRange}}`);
117
134
  if (args.onDateRangeChange)
@@ -144,6 +161,30 @@ export function createGridLayoutComponent() {
144
161
  ]
145
162
  };
146
163
  },
147
- }
164
+ },
165
+ {
166
+ name: "createTile",
167
+ description: `Tile 컴포넌트 - 카드형 컨테이너 (헤더 + 본문)
168
+ <OpsnowCommonTile>
169
+ <OpsnowCommonTile.Header>제목</OpsnowCommonTile.Header> // props: paddingX, paddingY (기본 16), borderColor
170
+ <OpsnowCommonTile.Content>본문</OpsnowCommonTile.Content> // props: paddingX, paddingY (기본 16)
171
+ </OpsnowCommonTile>
172
+ ⚠️ Tile은 sx를 받지 않습니다 (rest props 미전달) — 스타일은 className 또는 감싸는 Box의 sx로.
173
+ ⚠️ layout 패키지는 로더 의존성이 아니라 앱 package.json에 @opsnow-common/opsnow-common-layout@^2 를 직접 설치해야 합니다.
174
+
175
+ **import:**
176
+ \`\`\`javascript
177
+ import { OpsnowCommonTile } from '@opsnow-common/opsnow-common-layout';
178
+ \`\`\``,
179
+ parameters: TileSchema,
180
+ handler: async (args) => {
181
+ const { header, content, ...rest } = args;
182
+ const children = [
183
+ header ? `<OpsnowCommonTile.Header>${header}</OpsnowCommonTile.Header>` : "",
184
+ `<OpsnowCommonTile.Content>${content ?? ""}</OpsnowCommonTile.Content>`,
185
+ ].filter(Boolean).join("\n ");
186
+ return jsxResult(jsxTag("OpsnowCommonTile", jsxAttrs(rest, { exprKeys: ["onClick"] }), children));
187
+ },
188
+ },
148
189
  ];
149
190
  }
@@ -11,6 +11,7 @@ export const NotificationSchema = z.object({
11
11
  "warning",
12
12
  "info",
13
13
  ])
14
+ .optional()
14
15
  .describe(`알림 타입(색상/스타일):
15
16
  - "info": 회색/ 흰색 배경 + 배경과 동일한 색상의 아이콘 + 컬러 라인 없음
16
17
  - "infoSuccess": 흰/회색 배경 + 왼쪽 초록 컬러 라인 + 초록 아이콘(placement: page 전용)
@@ -32,7 +33,7 @@ export const NotificationSchema = z.object({
32
33
  - "card": 배경색 자체가 타입(success/ error/ warning/ info)에 따라 컬러풀하게 채워짐.
33
34
  ※ infoSuccess/infoWarning/infoError는 "page"에서만 사용 가능(배경 흰색, 컬러 라인 색상만 변함).
34
35
  ※ "card"는 배경색이 타입에 따라 바뀌며, infoSuccess/infoWarning/infoError는 지원하지 않음.`),
35
- closable: z.boolean().optional().describe("수동 닫기 가능 여부"),
36
+ closable: z.boolean().optional().describe("수동 닫기 가능 여부 — 생성기 입력 (실제 prop 아님, 생성 코드에서 onClose + 상태로 변환)"),
36
37
  bgWhite: z
37
38
  .boolean()
38
39
  .default(false)
@@ -42,7 +43,7 @@ export const NotificationSchema = z.object({
42
43
  isCustom: z
43
44
  .boolean()
44
45
  .default(false)
45
- .describe("커스텀 action 버튼 사용 여부 - action랑 같이 사용, default 버튼이 아닌 custom시 사용"),
46
+ .describe("커스텀 action 버튼 사용 여부 - action랑 같이 사용 — 생성기 입력 (실제 prop 아님, 생성 코드에서 customActionBtn으로 변환)"),
46
47
  }).describe("알림 컴포넌트 - 박스 형태로 알림을 제공하는 컴포넌트. 알림 컴포넌트 내에 버튼 사용 시 (action, isCustom 옵션 사용 시) 반드시 버튼 컴포넌트 동시 호출 하여 action에 OpsnowCommonButton 컴포넌트를 string으로 전달");
47
48
  // Notification 컴포넌트 함수 - 배열 반환
48
49
  export function createNotificationComponent() {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  // Pagination 컴포넌트 관련 스키마 정의
3
3
  export const PaginationSchema = z.object({
4
- page: z.number().describe("현재 페이지 번호"),
4
+ page: z.number().optional().describe("현재 페이지 번호 (기본값: 1)"),
5
5
  rowsPerPage: z.number().optional().default(10).describe("페이지당 항목 수"),
6
6
  size: z
7
7
  .enum(["medium", "small", "large"])
@@ -18,7 +18,7 @@ export const PaginationSchema = z.object({
18
18
  .string()
19
19
  .optional()
20
20
  .describe("rowsPerPage가 변경될 때 호출되는 핸들러 함수명 - 함수명만 필요함"),
21
- isLocale: z.boolean().default(true).describe("locale(언어) 설정 여부"),
21
+ isLocale: z.boolean().default(true).describe("locale(언어) 설정 여부 — 생성기 입력 (실제 prop 아님, true면 locale={i18n.getLocale()}로 출력. 실제 prop locale 기본값은 'en')"),
22
22
  onChangePageNum: z
23
23
  .string()
24
24
  .optional()
@@ -26,7 +26,7 @@ export const PaginationSchema = z.object({
26
26
  onChangePageSize: z
27
27
  .string()
28
28
  .optional()
29
- .describe("페이지당 항목수 변경(pageSize) 및 현재 페이지(pageNum) 변경되었을때 핸들러 함수. 함수 내부 코드(문자열)로 전달하세요. 예시: console.log('페이지당 항목 수가',pageSize,'로 변경되고, 현재 페이지는',pageNum,'입니다.')"),
29
+ .describe("페이지당 항목수 변경(pageSize) 및 현재 페이지(pageNum) 변경되었을때 핸들러 함수. 콜백 인자 순서는 (pageNum, pageSize). 함수 내부 코드(문자열)로 전달하세요. 예시: console.log('페이지당 항목 수가',pageSize,'로 변경되고, 현재 페이지는',pageNum,'입니다.')"),
30
30
  onNext: z
31
31
  .string()
32
32
  .optional()
@@ -130,7 +130,7 @@ export function createPaginationComponent() {
130
130
  }}`);
131
131
  }
132
132
  if (args.onChangePageSize) {
133
- props.push(`onChangePageSize={(pageSize, pageNum) => {
133
+ props.push(`onChangePageSize={(pageNum, pageSize) => {
134
134
  ${args.onChangePageSize}
135
135
  }}`);
136
136
  }
@@ -2,19 +2,20 @@ import { z } from "zod";
2
2
  // Popup 컴포넌트 관련 스키마 정의
3
3
  export const PopupSchema = z.object({
4
4
  title: z.string().describe("팝업 제목"),
5
- visible: z.boolean().optional().default(false).describe("팝업 표시 여부"),
5
+ visible: z.boolean().optional().default(false).describe("팝업 표시 여부 — 생성기 입력. 실제 prop은 open (생성 코드에서 open={...}으로 출력)"),
6
6
  closable: z.boolean().optional().default(true).describe("닫기 버튼 표시 여부 - false 시 생성 코드에서 hideHeaderClose로 변환 출력됨 (lib에 closable prop 없음). 크기 조절은 width/height 대신 size(sm/md/lg) 사용"),
7
- disableEscapeKeyDown: z.boolean().optional().default(false).describe('ESC 키 입력으로 닫기 방지 여부'),
8
7
  type: z.enum(['wing', 'standard', 'bottom']).optional().default('standard').describe("팝업 타입"),
9
- size: z.enum(['sm', 'md', 'lg']).optional().describe('팝업 크기 (sm, md, lg)'),
8
+ size: z.enum(['sm', 'md', 'lg']).optional().describe("팝업 크기 (sm, md, lg) — 기본값: wing 'md', standard 'sm'"),
10
9
  confirmButton: z.object({
11
- color: z.string().describe('확인 버튼 색상'),
12
- variant: z.string().describe('확인 버튼 변형'),
13
- }).optional().describe('확인 버튼 설정'),
10
+ text: z.string().optional().describe('확인 버튼 텍스트'),
11
+ variant: z.enum(['text', 'outlined', 'contained']).optional().describe('확인 버튼 변형'),
12
+ disabled: z.boolean().optional().describe('확인 버튼 비활성화 여부'),
13
+ color: z.string().optional().describe('확인 버튼 색상'),
14
+ }).optional().describe('확인 버튼 설정 객체 - { text, variant, disabled, color }'),
14
15
  onConfirm: z.string().optional().describe('확인(Confirm) 버튼 클릭 시 실행할 함수명 (() => void 타입) - 예시: handleConfirm'),
15
16
  onAction: z.string().optional().describe('액션(Action) 버튼 클릭 시 실행할 함수명 (() => void 타입) - 예시: handleAction'),
16
17
  useSpecialTitle: z.boolean().optional().describe('특수 타이틀 스타일 사용 여부 (boolean, 기본값 없음)'),
17
- useFooterCancelBtn: z.boolean().optional().describe('푸터에 취소 버튼 표시 여부 (boolean)'),
18
+ useFooterCancelBtn: z.boolean().optional().describe('푸터에 취소 버튼 표시 여부 (boolean, 기본값: true — 숨기려면 false)'),
18
19
  useFooterActionBtn: z.boolean().optional().describe('푸터에 액션 버튼 표시 여부 (boolean)'),
19
20
  cancelButton: z.object({
20
21
  text: z.string().optional().describe('취소 버튼 텍스트'),
@@ -50,20 +51,20 @@ export function createPopupComponent() {
50
51
  parameters: PopupSchema,
51
52
  handler: async (args) => {
52
53
  // <OpsnowCommonPopup ...> 코드만 동적으로 생성
53
- const { title = 'Example Popup', visible = false, closable = true, disableEscapeKeyDown = false, size, confirmButton, onConfirm, onAction, useSpecialTitle, useFooterCancelBtn, useFooterActionBtn, cancelButton, actionButton, hideHeaderClose, hideFooter, useCustomHeader = false, useCustomFooter = false, headerContent, footerContent, type = 'standard', } = args;
54
+ const { title = 'Example Popup', visible = false, closable = true, size, confirmButton, onConfirm, onAction, useSpecialTitle, useFooterCancelBtn, useFooterActionBtn, cancelButton, actionButton, hideHeaderClose, hideFooter, useCustomHeader = false, useCustomFooter = false, headerContent, footerContent, type = 'standard', } = args;
54
55
  // prop 문자열 생성
55
56
  const propStr = [
56
57
  `title=\"${title}\"`,
57
58
  `open={${visible}}`,
58
59
  `onClose={handleClose}`,
59
- disableEscapeKeyDown ? 'disableEscapeKeyDown' : '',
60
60
  `type=\"${type}\"`,
61
- `size=\"${size}\"`,
61
+ size ? `size=\"${size}\"` : '',
62
62
  confirmButton ? `confirmButton={${JSON.stringify(confirmButton)}}` : '',
63
63
  onConfirm ? `onConfirm={${onConfirm}}` : '',
64
64
  onAction ? `onAction={${onAction}}` : '',
65
65
  useSpecialTitle ? 'useSpecialTitle' : '',
66
- useFooterCancelBtn ? 'useFooterCancelBtn' : '',
66
+ // useFooterCancelBtn은 런타임 기본값이 true → false일 때만 명시
67
+ useFooterCancelBtn === false ? 'useFooterCancelBtn={false}' : '',
67
68
  useFooterActionBtn ? 'useFooterActionBtn' : '',
68
69
  cancelButton ? `cancelButton={${JSON.stringify(cancelButton)}}` : '',
69
70
  actionButton ? `actionButton={${JSON.stringify(actionButton)}}` : '',