@opsnow-mcp/opsnow-mcp-common-ui-server 1.0.12 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -209,3 +209,108 @@ MCP 플랫폼의 설정 파일(예: `mcp.config.json` 또는 유사 파일)에
209
209
 
210
210
  ### 3. MCP 플랫폼에서 서버 인식 확인
211
211
  MCP 플랫폼을 재시작하거나 서버 목록을 새로고침하면, `opsnow-mcp-common-ui-server`가 정상적으로 등록되어야 합니다.
212
+
213
+ ---
214
+
215
+ ## Claude Code Skills 생성
216
+
217
+ MCP 서버를 Claude Code에서 사용할 수 있는 **정적 Skills**로 변환할 수 있습니다.
218
+
219
+ ### MCP vs Skills 비교
220
+
221
+ | 구분 | MCP Server | Claude Code Skills |
222
+ |------|------------|-------------------|
223
+ | 실행 방식 | 런타임 (Node.js 프로세스) | 정적 문서 (마크다운) |
224
+ | 호출 방식 | Tool 호출 | `/skill-name` 슬래시 커맨드 |
225
+ | 배포 | npm 패키지 | Plugin Marketplace 또는 로컬 |
226
+ | 오프라인 | ❌ | ✅ |
227
+
228
+ ### Skills 빌드 명령어
229
+
230
+ ```bash
231
+ # Skills 빌드 (기존 파일 유지)
232
+ npm run build:skill
233
+
234
+ # Skills 클린 빌드 (기존 파일 삭제 후 새로 생성)
235
+ npm run build:skill:clean
236
+ ```
237
+
238
+ ### 빌드 출력물
239
+
240
+ ```
241
+ skills/plugins/opsnow-common-ui-skill/
242
+ ├── .claude-plugin/
243
+ │ └── plugin.json # 플러그인 매니페스트
244
+ └── skills/opsnow-common-ui/
245
+ ├── SKILL.md # 메인 스킬 정의 (컴포넌트 카탈로그)
246
+ ├── references/ # Props 레퍼런스 (35개)
247
+ │ ├── button.md
248
+ │ ├── datagrid.md
249
+ │ └── ...
250
+ ├── examples/ # 상세 예제 (40개 파일, 463개 예제)
251
+ │ ├── button.md
252
+ │ ├── datagrid.md
253
+ │ └── ...
254
+ └── components/ # Zod 스키마 복사본 (18개)
255
+ ├── opsnow-common-button.ts
256
+ └── ...
257
+ ```
258
+
259
+ ### 문서 구조 (Progressive Disclosure)
260
+
261
+ | 깊이 | 경로 | 용도 | 언제 참조? |
262
+ |------|------|------|-----------|
263
+ | 1 | `SKILL.md` | 컴포넌트 카탈로그, 개요 | 뭐가 있는지 파악할 때 |
264
+ | 2 | `references/*.md` | Props 테이블, 기본 사용법 | 컴포넌트 처음 사용할 때 |
265
+ | 3 | `examples/*.md` | 케이스별 상세 예제 | 특정 패턴 구현할 때 |
266
+ | 4 | `components/*.ts` | Zod 스키마, 타입 정의 | 타입/옵션 상세 확인할 때 |
267
+
268
+ ### 로컬 테스트
269
+
270
+ 생성된 Skills를 로컬에서 테스트하려면:
271
+
272
+ ```bash
273
+ # 프로젝트 루트에서
274
+ cd skills/plugins/opsnow-common-ui-skill
275
+
276
+ # Claude Code에서 직접 테스트
277
+ claude
278
+
279
+ # 또는 skills 폴더를 Claude Code 설정에 추가
280
+ ```
281
+
282
+ ### 새 컴포넌트 추가 시 수정 필요 사항
283
+
284
+ `scripts/build-skill.ts`에서 다음 5개 매핑을 업데이트해야 합니다:
285
+
286
+ 1. **`CATEGORIES[]`** - 카탈로그 분류 (차트, 폼, 레이아웃 등)
287
+ 2. **`COMPONENT_NAME_MAP{}`** - React 컴포넌트명 매핑
288
+ 3. **`COMPONENT_PURPOSE{}`** - 용도 설명
289
+ 4. **`SCHEMA_TO_COMPONENT{}`** - Zod 스키마 → 컴포넌트 매핑
290
+ 5. **`INTERNAL_SCHEMAS`** - 제외할 내부 스키마
291
+
292
+ ### MCP 소스 작성 규칙
293
+
294
+ Skills가 올바르게 생성되려면 MCP 소스 파일이 다음 규칙을 따라야 합니다:
295
+
296
+ **Zod 스키마** (`src/components/*.ts`):
297
+ ```typescript
298
+ // .describe()로 Props 설명 필수
299
+ export const ButtonPropsSchema = z.object({
300
+ label: z.string().describe('버튼 라벨'),
301
+ size: z.enum(['large', 'medium', 'small']).describe('버튼 크기'),
302
+ }).describe('버튼 컴포넌트');
303
+ ```
304
+
305
+ **예제 파일** (`src/components/examples/*-examples-data.ts`):
306
+ ```typescript
307
+ // 파일명: opsnow-common-{component}-examples-data.ts
308
+ // Export 명명: {PascalCase}Examples
309
+ export const ButtonExamples: Example[] = [
310
+ {
311
+ title: '기본 버튼',
312
+ props: { label: '버튼', size: 'medium' },
313
+ code: `<OpsnowCommonButton label="버튼" size="medium" />`,
314
+ },
315
+ ];
316
+ ```
@@ -1346,6 +1346,208 @@ export const LinkExamples = [
1346
1346
  description: 'Focusable Inherit Link 예제입니다.'
1347
1347
  }
1348
1348
  ];
1349
+ export const TreeViewExamples = [
1350
+ {
1351
+ title: 'basic-treeview-with-mui-icons',
1352
+ code: `<OpsnowCommonTreeView
1353
+ data={[
1354
+ {
1355
+ id: 'c1',
1356
+ label: 'Settings',
1357
+ muiIconName: 'settings',
1358
+ children: [
1359
+ {
1360
+ id: 'c11',
1361
+ label: 'Profile',
1362
+ muiIconName: 'person',
1363
+ },
1364
+ {
1365
+ id: 'c12',
1366
+ label: 'Security',
1367
+ muiIconName: 'lock',
1368
+ },
1369
+ {
1370
+ id: 'c13',
1371
+ label: 'Notifications',
1372
+ muiIconName: 'notifications',
1373
+ },
1374
+ ],
1375
+ },
1376
+ {
1377
+ id: 'c2',
1378
+ label: 'Help',
1379
+ muiIconName: 'help',
1380
+ children: [
1381
+ {
1382
+ id: 'c21',
1383
+ label: 'Documentation',
1384
+ muiIconName: 'menu_book',
1385
+ },
1386
+ {
1387
+ id: 'c22',
1388
+ label: 'Support',
1389
+ muiIconName: 'support_agent',
1390
+ },
1391
+ ],
1392
+ },
1393
+ ]}
1394
+ onNodeClick={handleNodeClick}
1395
+ selectedNodeId={selectedNode?.id}
1396
+ />`,
1397
+ description: 'Material-UI 아이콘을 사용한 기본 TreeView 예제입니다.'
1398
+ },
1399
+ {
1400
+ title: 'treeview-no-search-no-total',
1401
+ code: `<OpsnowCommonTreeView
1402
+ data={treeData}
1403
+ searchable={false}
1404
+ showTotal={false}
1405
+ defaultExpandAll={true}
1406
+ onNodeClick={handleNodeClick}
1407
+ selectedNodeId={selectedNode?.id}
1408
+ />`,
1409
+ description: '검색 기능과 Total 라벨을 숨긴 TreeView 예제입니다.'
1410
+ },
1411
+ {
1412
+ title: 'treeview-with-total-label',
1413
+ code: `<OpsnowCommonTreeView
1414
+ data={treeData}
1415
+ totalLabel="All Items"
1416
+ totalIconName="FolderOpen"
1417
+ onTotalClick={handleTotalClick}
1418
+ onNodeClick={handleNodeClick}
1419
+ selectedNodeId={selectedNode?.id}
1420
+ />`,
1421
+ description: 'Total 라벨과 아이콘이 있는 TreeView 예제입니다.'
1422
+ },
1423
+ {
1424
+ title: 'treeview-with-render-content',
1425
+ code: `<OpsnowCommonTreeView
1426
+ data={treeData}
1427
+ renderRightContent={(node) => (
1428
+ <Chip label={node.count || 0} size="small" />
1429
+ )}
1430
+ onNodeClick={handleNodeClick}
1431
+ selectedNodeId={selectedNode?.id}
1432
+ />`,
1433
+ description: '각 노드 우측에 커스텀 콘텐츠를 렌더링하는 TreeView 예제입니다.'
1434
+ },
1435
+ {
1436
+ title: 'treeview-expanded-by-default',
1437
+ code: `<OpsnowCommonTreeView
1438
+ data={treeData}
1439
+ defaultExpandAll={true}
1440
+ searchable={true}
1441
+ onNodeClick={handleNodeClick}
1442
+ selectedNodeId={selectedNode?.id}
1443
+ />`,
1444
+ description: '기본적으로 모든 노드가 확장된 TreeView 예제입니다.'
1445
+ },
1446
+ {
1447
+ title: 'treeview-cloud-providers',
1448
+ code: `<OpsnowCommonTreeView
1449
+ data={[
1450
+ {
1451
+ id: 'aws',
1452
+ label: 'AWS',
1453
+ muiIconName: 'cloud',
1454
+ children: [
1455
+ { id: 'ec2', label: 'EC2', muiIconName: 'storage' },
1456
+ { id: 's3', label: 'S3', muiIconName: 'folder' },
1457
+ { id: 'rds', label: 'RDS', muiIconName: 'database' },
1458
+ ],
1459
+ },
1460
+ {
1461
+ id: 'azure',
1462
+ label: 'Azure',
1463
+ muiIconName: 'cloud',
1464
+ children: [
1465
+ { id: 'vm', label: 'Virtual Machines', muiIconName: 'computer' },
1466
+ { id: 'blob', label: 'Blob Storage', muiIconName: 'folder' },
1467
+ ],
1468
+ },
1469
+ {
1470
+ id: 'gcp',
1471
+ label: 'GCP',
1472
+ muiIconName: 'cloud',
1473
+ children: [
1474
+ { id: 'compute', label: 'Compute Engine', muiIconName: 'dns' },
1475
+ { id: 'gcs', label: 'Cloud Storage', muiIconName: 'cloud_upload' },
1476
+ ],
1477
+ },
1478
+ ]}
1479
+ totalLabel="All Cloud Providers"
1480
+ searchable={true}
1481
+ onNodeClick={handleNodeClick}
1482
+ selectedNodeId={selectedNode?.id}
1483
+ />`,
1484
+ description: '클라우드 프로바이더 트리 구조 예제입니다.'
1485
+ },
1486
+ {
1487
+ title: 'treeview-with-checkbox-multiselect',
1488
+ code: `<OpsnowCommonTreeView
1489
+ data={sampleTreeData}
1490
+ checkboxSelection={true}
1491
+ multiSelect={true}
1492
+ onCheckedItemsChange={handleMultiCheckedItemsChange}
1493
+ defaultCheckedItems={multiCheckedItems}
1494
+ showTotal={true}
1495
+ totalLabel="Total"
1496
+ totalIconName="Building"
1497
+ searchable={false}
1498
+ defaultExpandAll={true}
1499
+ />`,
1500
+ description: '체크박스와 다중 선택 기능이 있는 TreeView 예제입니다.'
1501
+ },
1502
+ {
1503
+ title: 'treeview-checkbox-with-custom-content',
1504
+ code: `<OpsnowCommonTreeView
1505
+ data={sampleTreeData}
1506
+ checkboxSelection={true}
1507
+ multiSelect={true}
1508
+ onCheckedItemsChange={handleMultiCheckedItemsChange}
1509
+ defaultCheckedItems={multiCheckedItems}
1510
+ showTotal={true}
1511
+ totalLabel="Total"
1512
+ totalIconName="Building"
1513
+ onTotalClick={handleTotalClick}
1514
+ renderTotalRightContent={() => (
1515
+ <OpsnowCommonChip
1516
+ label={\`\${String(sampleTreeData.length)} BU\`}
1517
+ size="small"
1518
+ sx={{
1519
+ backgroundColor: 'white',
1520
+ border: '1px solid',
1521
+ }}
1522
+ />
1523
+ )}
1524
+ searchable={false}
1525
+ defaultExpandAll={true}
1526
+ renderRightContent={(node) => (
1527
+ <OpsnowCommonChip
1528
+ label={
1529
+ node.children && node.children.length > 0
1530
+ ? \`\${String(node.children.length)} Teams\`
1531
+ : String(node.data?.memberCount ?? '0')
1532
+ }
1533
+ size="small"
1534
+ sx={{
1535
+ backgroundColor: 'white',
1536
+ border: '1px solid',
1537
+ }}
1538
+ />
1539
+ )}
1540
+ />`,
1541
+ description: '체크박스, 커스텀 콘텐츠, Chip이 함께 사용된 TreeView 예제입니다.'
1542
+ }
1543
+ ];
1544
+ export const InsightCardExamples = [
1545
+ {
1546
+ title: 'basic-insight-card-with-loading',
1547
+ code: `<Box sx={{ mt: 2 }}><OpsnowCommonInsightCard loading={loading} loadingStage={loadingStage} insightText={insightText} title="Opsnow Insight AI" subtitle="AI-powered cost analysis and recommendations" /></Box>`,
1548
+ description: '로딩 상태와 AI insight 텍스트를 표시하는 InsightCard 예제입니다.'
1549
+ }
1550
+ ];
1349
1551
  export const ToggleButtonExamples = [
1350
1552
  {
1351
1553
  title: 'large-toggle-group',
@@ -57,4 +57,16 @@ export const GridLayoutExamples = [
57
57
  </Grid>
58
58
  `,
59
59
  },
60
+ {
61
+ title: 'Tile with Checkbox UI',
62
+ description: 'Tile 내부에 체크박스와 텍스트가 포함된 2열 레이아웃 예제입니다.',
63
+ code: `<Grid container spacing={3} sx={{ marginTop: '20px' }}><Grid item xs={12} md={6} sx={{ display: 'flex' }}><OpsnowCommonTile sx={{ width: '100%' }}><OpsnowCommonTile.Content><Stack direction="row" alignItems="flex-start" spacing={2}><OpsnowCommonCheckbox name="organizationScope" checked={checkboxStates.organizationScope} onChange={handleCheckboxChange} color="primary" /><Stack spacing={1}><OpsnowCommonTypography variant="body1" fontWeight={600}>Use Organization Scope(Cost Allocation)</OpsnowCommonTypography><OpsnowCommonTypography variant="body2" color="textSecondary">The same scope defined in Organization Settings is used as the baseline for Budget and KPI tracking.</OpsnowCommonTypography></Stack></Stack></OpsnowCommonTile.Content></OpsnowCommonTile></Grid><Grid item xs={12} md={6} sx={{ display: 'flex' }}><OpsnowCommonTile sx={{ width: '100%' }}><OpsnowCommonTile.Content><Stack direction="row" alignItems="flex-start" spacing={2}><OpsnowCommonCheckbox name="customizeScope" checked={checkboxStates.customizeScope} onChange={handleCheckboxChange} color="primary" /><Stack spacing={1}><OpsnowCommonTypography variant="body1" fontWeight={600}>Customize Scope</OpsnowCommonTypography><OpsnowCommonTypography variant="body2" color="textSecondary">Apply only to selected dimensions, such as specific services (e.g., EC2)</OpsnowCommonTypography></Stack></Stack></OpsnowCommonTile.Content></OpsnowCommonTile></Grid></Grid>`,
64
+ },
65
+ ];
66
+ export const MegaFilterExamples = [
67
+ {
68
+ title: 'basic-megafilter-with-vendors-daterange',
69
+ description: '벤더 선택, 날짜 범위, 필터 기능이 포함된 MegaFilter 예제입니다.',
70
+ code: `<OpsnowCommonMegaFilter filters={filters} vendors={vendors} selectedVendor={selectedVendor} onVendorChange={handleVendorChange} onFilterChange={handleFilterChange} selectedDateRange={selectedDateRange} onDateRangeChange={handleDateRangeChange} locale="ko" title="필터" clearAllText="전체 초기화" applyText="적용" cancelText="취소" />`,
71
+ },
60
72
  ];
@@ -79,4 +79,9 @@ export const TabExamples = [
79
79
  code: `<OpsnowCommonTab value = {tabIndex} items={[{ label: '탭1', iconName: 'LogoAws', content: <div>탭1 내용</div> }, { label: '탭2', iconName: 'LogoAzure', content: <div>탭2 내용</div>}]} />`,
80
80
  description: "탭 선택이 tabIndex라는 state 변수에 따라 변경되는 상태로 탭이 2개이고 각 탭에 아이콘과 내용이 포함된 예제입니다.",
81
81
  },
82
+ {
83
+ title: "2-depth-button-tab-ui",
84
+ code: `<OpsnowCommonTab value={activeTab} onChange={handleTabChange} items={[{ label: 'Overview', content: (<div style={{ marginTop: '20px' }}><p>Overview content</p></div>) }, { label: 'Explorer', content: (<div style={{ marginTop: '20px' }}>{buttons.map((button) => (<OpsnowCommonButton key={button.value} label={button.label} onClick={() => handleButtonClick(button.value)} variant={selectedButton === button.value ? 'contained' : 'outlined'} style={{ marginRight: '10px', marginBottom: '10px' }} />))}{selectedButton && (<div style={{ marginTop: '20px' }}><p>Selected: {selectedButton}</p></div>)}</div>) }]} />`,
85
+ description: "2개의 탭(Overview, Explorer)이 있고, Explorer 탭에 동적 버튼 그룹이 포함된 2-Depth Button Tab UI 예제입니다.",
86
+ },
82
87
  ];
@@ -1,5 +1,5 @@
1
1
  // Toast Popup 컴포넌트 예제 데이터
2
- export const opsnowCommonToastPopupExamples = [
2
+ export const ToastPopupExamples = [
3
3
  {
4
4
  title: "message-info-duration-bottom-left",
5
5
  code: `
@@ -1,4 +1,4 @@
1
- import { ButtonExamples, BadgeExamples, TextareaExamples, SwitchExamples, ChipExamples, TextFieldExamples, AvatarExamples, CheckboxExamples, RadioGroupExamples, CollapseExamples, SliderExamples, LinkExamples, ToggleButtonExamples } from "./examples/opsnow-common-forms-examples-data.js";
1
+ import { ButtonExamples, BadgeExamples, TextareaExamples, SwitchExamples, ChipExamples, TextFieldExamples, AvatarExamples, CheckboxExamples, RadioGroupExamples, CollapseExamples, SliderExamples, LinkExamples, TreeViewExamples, InsightCardExamples, ToggleButtonExamples } from "./examples/opsnow-common-forms-examples-data.js";
2
2
  import { IconExamples, MuiIconExamples } from "./examples/opsnow-common-icons-examples-data.js";
3
3
  import { CalendarExamples } from "./examples/opsnow-common-calendar-examples-data.js";
4
4
  import { GaugeChartExamples, PieChartExamples, BarChartExamples, LineChartExamples, StackChartExamples, XyMultiChartExamples } from "./examples/opsnow-common-chart-examples-data.js";
@@ -14,7 +14,7 @@ import { PaginationExamples } from "./examples/opsnow-common-pagination-examples
14
14
  import { DataStatusExamples } from "./examples/opsnow-common-data-status-examples-data.js";
15
15
  import { ProgressExamples } from "./examples/opsnow-common-progress-examples-data.js";
16
16
  import { TabExamples } from "./examples/opsnow-common-tab-examples-data.js";
17
- import { GridLayoutExamples } from "./examples/opsnow-common-layout-examples-data.js";
17
+ import { GridLayoutExamples, MegaFilterExamples } from "./examples/opsnow-common-layout-examples-data.js";
18
18
  import { TypographyExamples } from "./examples/opsnow-common-typography-examples-data.js";
19
19
  import { AutocompleteExamples } from "./examples/opsnow-common-select-examples-data.js";
20
20
  import { z } from "zod";
@@ -31,6 +31,8 @@ const EXAMPLES_MAP = {
31
31
  Collapse: CollapseExamples,
32
32
  Slider: SliderExamples,
33
33
  Link: LinkExamples,
34
+ TreeView: TreeViewExamples,
35
+ InsightCard: InsightCardExamples,
34
36
  ToggleButtonGroup: ToggleButtonExamples,
35
37
  Icon: IconExamples,
36
38
  MuiIcon: MuiIconExamples,
@@ -54,6 +56,7 @@ const EXAMPLES_MAP = {
54
56
  Progress: ProgressExamples,
55
57
  Tab: TabExamples,
56
58
  GridLayout: GridLayoutExamples,
59
+ MegaFilter: MegaFilterExamples,
57
60
  Typography: TypographyExamples,
58
61
  Autocomplete: AutocompleteExamples,
59
62
  };
@@ -204,6 +204,55 @@ export const ToggleButtonSchema = z.object({
204
204
  disabled: z.boolean().optional().describe('비활성화 여부'),
205
205
  })).describe('토글 버튼 목록'),
206
206
  });
207
+ // TreeView 컴포넌트 관련 스키마 정의
208
+ export const TreeViewSchema = z.object({
209
+ data: z.string().describe(`트리 데이터 배열 변수명 (예: sampleTreeData)
210
+
211
+ 트리 데이터 구조 예시:
212
+ - id: 노드 고유 ID
213
+ - label: 노드 라벨 텍스트
214
+ - muiIconName: Material-UI 아이콘 이름 (예: 'settings', 'person', 'lock' 등)
215
+ - children: 자식 노드 배열 (선택사항)
216
+ - data: 노드에 추가 데이터 (선택사항, 예: { memberCount: 5 })
217
+
218
+ 예시 데이터:
219
+ [
220
+ {
221
+ id: 'c1',
222
+ label: 'Settings',
223
+ muiIconName: 'settings',
224
+ children: [
225
+ { id: 'c11', label: 'Profile', muiIconName: 'person' },
226
+ { id: 'c12', label: 'Security', muiIconName: 'lock' }
227
+ ]
228
+ }
229
+ ]`),
230
+ totalLabel: z.string().optional().describe("최상단 Total 라벨 텍스트"),
231
+ totalIconName: z.enum(Object.keys(IconNamesMap)).optional().describe(generateIconDescription({ format: 'nested' })),
232
+ showTotal: z.boolean().optional().describe("Total 라벨 표시 여부 (기본값: true)"),
233
+ searchable: z.boolean().optional().describe("검색 기능 활성화 여부 (기본값: true)"),
234
+ defaultExpandAll: z.boolean().optional().describe("기본 전체 확장 여부 (기본값: false)"),
235
+ checkboxSelection: z.boolean().optional().describe("체크박스 선택 모드 활성화"),
236
+ multiSelect: z.boolean().optional().describe("다중 선택 허용 여부"),
237
+ onCheckedItemsChange: z.string().optional().describe("체크된 항목 변경 이벤트 핸들러 (예: handleMultiCheckedItemsChange)"),
238
+ defaultCheckedItems: z.string().optional().describe("기본 체크된 항목 배열 상태 변수명 (예: multiCheckedItems)"),
239
+ onTotalClick: z.string().optional().describe("Total 클릭 이벤트 핸들러 함수명 (예: handleTotalClick)"),
240
+ renderTotalRightContent: z.string().optional().describe("Total 우측에 표시할 JSX 코드 문자열 (함수 형태)"),
241
+ renderRightContent: z.string().optional().describe("각 노드 우측에 표시할 JSX 코드 문자열 (함수 형태, node 파라미터 사용)"),
242
+ onNodeClick: z.string().optional().describe("노드 클릭 이벤트 핸들러 함수명 (예: handleNodeClick)"),
243
+ selectedNodeId: z.string().optional().describe("선택된 노드 ID 상태 변수명 (예: selectedNode?.id)"),
244
+ });
245
+ // InsightCard 컴포넌트 관련 스키마 정의
246
+ export const InsightCardSchema = z.object({
247
+ loading: z.boolean().optional().describe("로딩 상태 여부 (기본값: false)"),
248
+ loadingStage: z.enum(["1", "2", "3"]).optional().describe("현재 로딩 단계 (1, 2, 3 중 하나, 기본값: 1)"),
249
+ loadingStagesMessages: z.string().optional().describe("커스텀 로딩 메시지 배열 변수명 (예: loadingMessages)"),
250
+ insightText: z.string().optional().describe("AI insight 텍스트 내용"),
251
+ title: z.string().optional().describe("타일 제목 (기본값: 'Opsnow Insight AI')"),
252
+ subtitle: z.string().optional().describe("타일 부제목 (기본값: 'AI-powered analysis and recommendations')"),
253
+ onDownload: z.string().optional().describe("커스텀 다운로드 콜백 함수명 (예: handleDownload)"),
254
+ sx: z.string().optional().describe("MUI sx 스타일 객체(JSX/문자열)"),
255
+ });
207
256
  // Forms 컴포넌트 함수 - 배열 반환
208
257
  export function createFormsComponent() {
209
258
  return [
@@ -749,7 +798,7 @@ export function createFormsComponent() {
749
798
  {
750
799
  name: "createToggleButton",
751
800
  description: `ToggleButtonGroup 컴포넌트 - 다양한 스타일, 아이콘, 단일/다중 선택 지원
752
-
801
+
753
802
  - 버튼별 아이콘, 라벨, 아이콘 위치, 아이콘만 표시 등 다양한 옵션 지원
754
803
  - exclusive: true로 단일 선택, false 또는 미설정 시 다중 선택
755
804
  - theme, disabled 등 다양한 스타일 속성 지원
@@ -807,6 +856,104 @@ export function createFormsComponent() {
807
856
  ]
808
857
  };
809
858
  }
859
+ },
860
+ {
861
+ name: "createTreeView",
862
+ description: `TreeView 컴포넌트 - 계층 구조 데이터를 트리 형태로 표시
863
+
864
+ - data: 트리 데이터 배열 변수
865
+ - totalLabel, totalIconName: 최상단 Total 표시 옵션
866
+ - renderTotalRightContent, renderRightContent: 각 노드의 우측에 커스텀 콘텐츠 렌더링
867
+ - onTotalClick, onNodeClick: 클릭 이벤트 핸들러
868
+ - selectedNodeId: 선택된 노드 ID
869
+
870
+ **import:**
871
+ \`\`\`javascript
872
+ import { useCommonComponents } from '@opsnow-common/opsnow-finops-common-ui-loader';
873
+ const { OpsnowCommonTreeView } = useCommonComponents();
874
+ \`\`\``,
875
+ parameters: TreeViewSchema,
876
+ handler: async (args) => {
877
+ const props = [];
878
+ if (args.data)
879
+ props.push(`data={${args.data}}`);
880
+ if (args.totalLabel)
881
+ props.push(`totalLabel=\"${args.totalLabel}\"`);
882
+ if (args.totalIconName)
883
+ props.push(`totalIconName=\"${args.totalIconName}\"`);
884
+ if (args.showTotal !== undefined)
885
+ props.push(`showTotal={${args.showTotal}}`);
886
+ if (args.searchable !== undefined)
887
+ props.push(`searchable={${args.searchable}}`);
888
+ if (args.defaultExpandAll !== undefined)
889
+ props.push(`defaultExpandAll={${args.defaultExpandAll}}`);
890
+ if (args.checkboxSelection !== undefined)
891
+ props.push(`checkboxSelection={${args.checkboxSelection}}`);
892
+ if (args.multiSelect !== undefined)
893
+ props.push(`multiSelect={${args.multiSelect}}`);
894
+ if (args.onCheckedItemsChange)
895
+ props.push(`onCheckedItemsChange={${args.onCheckedItemsChange}}`);
896
+ if (args.defaultCheckedItems)
897
+ props.push(`defaultCheckedItems={${args.defaultCheckedItems}}`);
898
+ if (args.onTotalClick)
899
+ props.push(`onTotalClick={${args.onTotalClick}}`);
900
+ if (args.renderTotalRightContent)
901
+ props.push(`renderTotalRightContent={${args.renderTotalRightContent}}`);
902
+ if (args.renderRightContent)
903
+ props.push(`renderRightContent={${args.renderRightContent}}`);
904
+ if (args.onNodeClick)
905
+ props.push(`onNodeClick={${args.onNodeClick}}`);
906
+ if (args.selectedNodeId)
907
+ props.push(`selectedNodeId={${args.selectedNodeId}}`);
908
+ const code = `<OpsnowCommonTreeView\n ${props.join('\n ')}\n/>`;
909
+ return {
910
+ content: [
911
+ {
912
+ type: "text",
913
+ text: `\`\`\`jsx\n${code}\n\`\`\``
914
+ }
915
+ ]
916
+ };
917
+ }
918
+ },
919
+ {
920
+ name: "createInsightCard",
921
+ description: `InsightCard 컴포넌트 - AI 인사이트, 로딩 상태 등 지원
922
+
923
+ **import:**
924
+ \`\`\`javascript
925
+ import { useCommonComponents } from '@opsnow-common/opsnow-finops-common-ui-loader';
926
+ const { OpsnowCommonInsightCard } = useCommonComponents();
927
+ \`\`\``,
928
+ parameters: InsightCardSchema,
929
+ handler: async (args) => {
930
+ const props = [];
931
+ if (args.loading !== undefined)
932
+ props.push(`loading={${args.loading}}`);
933
+ if (args.loadingStage !== undefined)
934
+ props.push(`loadingStage={${args.loadingStage}}`);
935
+ if (args.loadingStagesMessages)
936
+ props.push(`loadingStagesMessages={${args.loadingStagesMessages}}`);
937
+ if (args.insightText)
938
+ props.push(`insightText="${args.insightText}"`);
939
+ if (args.title)
940
+ props.push(`title="${args.title}"`);
941
+ if (args.subtitle)
942
+ props.push(`subtitle="${args.subtitle}"`);
943
+ if (args.onDownload)
944
+ props.push(`onDownload={${args.onDownload}}`);
945
+ if (args.sx)
946
+ props.push(`sx={${args.sx}}`);
947
+ const code = `<OpsnowCommonInsightCard\n ${props.join('\n ')}\n/>`;
948
+ return {
949
+ content: [
950
+ {
951
+ type: "text",
952
+ text: `\`\`\`jsx\n${code}\n\`\`\``
953
+ }
954
+ ]
955
+ };
956
+ }
810
957
  }
811
958
  ];
812
959
  }
@@ -18,12 +18,29 @@ const GridLayoutSchema = z.object({
18
18
  spacing: z.number().optional().describe("Grid 간격 (MUI spacing 단위)"),
19
19
  tileProps: z.record(z.any()).optional().describe("OpsnowCommonTile에 전달할 추가 props"),
20
20
  });
21
+ const MegaFilterSchema = z.object({
22
+ filters: z.string().describe("필터 배열 변수명"),
23
+ vendors: z.string().optional().describe("벤더 옵션 배열 변수명"),
24
+ selectedVendor: z.string().optional().describe("선택된 벤더 상태 변수명"),
25
+ onVendorChange: z.string().optional().describe("벤더 변경 이벤트 핸들러"),
26
+ onFilterChange: z.string().optional().describe("필터 변경 이벤트 핸들러"),
27
+ selectedDateRange: z.string().optional().describe("선택된 날짜 범위 상태 변수명"),
28
+ onDateRangeChange: z.string().optional().describe("날짜 범위 변경 이벤트 핸들러"),
29
+ locale: z.enum(["en", "ko"]).optional().describe("로케일 설정"),
30
+ title: z.string().optional().describe("필터 제목"),
31
+ clearAllText: z.string().optional().describe("전체 초기화 버튼 텍스트"),
32
+ clearText: z.string().optional().describe("개별 초기화 버튼 텍스트"),
33
+ applyText: z.string().optional().describe("적용 버튼 텍스트"),
34
+ cancelText: z.string().optional().describe("취소 버튼 텍스트"),
35
+ searchPlaceholder: z.string().optional().describe("검색 입력 placeholder"),
36
+ noItemsText: z.string().optional().describe("검색 결과 없음 텍스트"),
37
+ });
21
38
  export function createGridLayoutComponent() {
22
39
  return [
23
40
  {
24
41
  name: "createGridLayout",
25
42
  description: `Grid 및 Box 기반 레이아웃 생성
26
-
43
+
27
44
  **import:**
28
45
  \`\`\`jsx
29
46
  import { Box, Grid } from '@mui/material';
@@ -64,6 +81,58 @@ export function createGridLayoutComponent() {
64
81
  ]
65
82
  };
66
83
  },
84
+ },
85
+ {
86
+ name: "createMegaFilter",
87
+ description: `MegaFilter 컴포넌트 - 다양한 필터, 벤더 선택, 날짜 범위 등 지원
88
+
89
+ **import:**
90
+ \`\`\`javascript
91
+ import { OpsnowCommonMegaFilter } from '@opsnow-common/opsnow-common-layout';
92
+ \`\`\``,
93
+ parameters: MegaFilterSchema,
94
+ handler: async (args) => {
95
+ const props = [];
96
+ if (args.filters)
97
+ props.push(`filters={${args.filters}}`);
98
+ if (args.vendors)
99
+ props.push(`vendors={${args.vendors}}`);
100
+ if (args.selectedVendor)
101
+ props.push(`selectedVendor={${args.selectedVendor}}`);
102
+ if (args.onVendorChange)
103
+ props.push(`onVendorChange={${args.onVendorChange}}`);
104
+ if (args.onFilterChange)
105
+ props.push(`onFilterChange={${args.onFilterChange}}`);
106
+ if (args.selectedDateRange)
107
+ props.push(`selectedDateRange={${args.selectedDateRange}}`);
108
+ if (args.onDateRangeChange)
109
+ props.push(`onDateRangeChange={${args.onDateRangeChange}}`);
110
+ if (args.locale)
111
+ props.push(`locale="${args.locale}"`);
112
+ if (args.title)
113
+ props.push(`title="${args.title}"`);
114
+ if (args.clearAllText)
115
+ props.push(`clearAllText="${args.clearAllText}"`);
116
+ if (args.clearText)
117
+ props.push(`clearText="${args.clearText}"`);
118
+ if (args.applyText)
119
+ props.push(`applyText="${args.applyText}"`);
120
+ if (args.cancelText)
121
+ props.push(`cancelText="${args.cancelText}"`);
122
+ if (args.searchPlaceholder)
123
+ props.push(`searchPlaceholder="${args.searchPlaceholder}"`);
124
+ if (args.noItemsText)
125
+ props.push(`noItemsText="${args.noItemsText}"`);
126
+ const code = `<OpsnowCommonMegaFilter\n ${props.join('\n ')}\n/>`;
127
+ return {
128
+ content: [
129
+ {
130
+ type: "text",
131
+ text: `\`\`\`jsx\n${code}\n\`\`\``
132
+ }
133
+ ]
134
+ };
135
+ },
67
136
  }
68
137
  ];
69
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsnow-mcp/opsnow-mcp-common-ui-server",
3
- "version": "1.0.12",
3
+ "version": "1.0.16",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,6 +9,9 @@
9
9
  "module": "src/index.ts",
10
10
  "scripts": {
11
11
  "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
12
+ "build:skill": "npx tsx scripts/build-skill.ts",
13
+ "build:skill:clean": "rm -rf skills/plugins/opsnow-common-ui-skill/skills && npm run build:skill",
14
+ "build:all": "npm run build && npm run build:skill",
12
15
  "start": "node --loader ts-node/esm src/index.ts",
13
16
  "dev": "node --loader ts-node/esm src/index.ts",
14
17
  "clean": "rm -rf build",