@opsnow-mcp/opsnow-mcp-common-ui-server 1.0.34 → 1.0.36

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.
@@ -1609,26 +1609,92 @@ const handleDownload = useCallback(() => {
1609
1609
  ];
1610
1610
  export const CurrencySwitcherExamples = [
1611
1611
  {
1612
- title: 'basic-currency-switcher',
1613
- code: `const { OpsnowCommonCurrencySwitcher } = useCommonComponents()
1612
+ title: 'header-currency-switcher',
1613
+ code: `// 공통 헤더(header)에서 사용법 기본 배선 (opsnow-finops-common-header >= 1.0.20)
1614
+ // 헤더는 통화 상태/환율 데이터를 갖지 않는다 — 앱이 관리해 props로 주입한다
1615
+ import OpsnowFinopsCommonHeader from '@opsnow-common/opsnow-finops-common-header'
1616
+
1617
+ const [currency, setCurrency] = useState('USD')
1618
+ const [historyQuery, setHistoryQuery] = useState({ currency: 'USD', period: 24 })
1619
+ const historySeries = useMemo(
1620
+ () => buildSeries(historyQuery.currency, historyQuery.period), // 실사용: BE 조회
1621
+ [historyQuery]
1622
+ )
1623
+
1624
+ // 통화 전환 적용 대상: 개요(OverView) · 비용 분석(Analytics) · 청구 내역(Billing Invoice) · 비용 배분(Cost Allocation)
1625
+ // 그 외 메뉴에서는 currencySwitcherEnabled={false} → 버튼 disabled + 호버 시 안내 툴팁
1626
+ const CURRENCY_ENABLED_ROUTES = [
1627
+ '/overview', // 개요
1628
+ '/cost/analytics/usage-charges', // 비용 분석
1629
+ '/cost/billing-invoice', // 청구 내역
1630
+ '/settings/cost-allocation', // 비용 배분 (하위 경로 포함)
1631
+ ]
1632
+ const isCurrencyEnabledPath = CURRENCY_ENABLED_ROUTES.some(
1633
+ (route) => location.pathname === route || location.pathname.startsWith(route + '/')
1634
+ )
1635
+
1636
+ <OpsnowFinopsCommonHeader
1637
+ menuList={headerMenuList}
1638
+ /* …로고 · 도움말 · 언어 · 테마 토글 등 다른 헤더 props는 기존 코드 유지… */
1639
+
1640
+ showCurrencySwitcher // 헤더에 스위처 노출
1641
+ currencySwitcherEnabled={isCurrencyEnabledPath} // 적용 대상 메뉴 여부
1642
+ currencySwitcherProps={{ // dropdown 컴포넌트로 그대로 전달
1643
+ value: currency,
1644
+ onChange: code => setCurrency(code),
1645
+ rateInfo, // BE 환율 API 응답
1646
+ historySeries,
1647
+ onHistoryQueryChange: (code, months) => {
1648
+ setHistoryQuery({ currency: code, period: months })
1649
+ },
1650
+ }}
1651
+ />`,
1652
+ description: "공통 헤더(header)에서 사용법 — 기본 배선 예제입니다. 선택 통화·히스토리 조회 조건은 앱 상태로 관리하고, 적용 대상 메뉴 여부는 currencySwitcherEnabled로 넘깁니다. 통화 변경은 '개요(OverView)'·'비용 분석(Analytics)'·'청구 내역(Billing Invoice)'·'비용 배분(Cost Allocation)' 4개 메뉴에서만 활성화하고, 그 외 메뉴에서는 currencySwitcherEnabled={false}로 버튼을 disabled 처리해 호버 시 안내 툴팁이 뜨게 합니다. 헤더는 AI 버튼~알림 벨 사이 자리만 제공하고 상태·환율 데이터를 갖지 않으므로, 앱이 currencySwitcherProps로 주입한 값이 내부 OpsnowCommonCurrencySwitcher에 그대로 전달됩니다(단독 사용 시 props 규칙 동일 적용). 주의: 이 MCP는 헤더 props 중 통화 전환 관련 4개(showCurrencySwitcher/currencySwitcherEnabled/currencySwitcherProps/currencySwitcherDisabledTooltip)만 안내하며, menuList·로고·도움말·언어·테마 토글 등 헤더의 다른 props는 제공하지 않습니다 — 프로젝트의 기존 헤더 코드를 유지한 채 통화 관련 props만 추가하세요. [검색 키워드: 헤더 통화 전환, 공통 헤더, header currency, 헤더에서 사용법, GNB 통화, showCurrencySwitcher]"
1653
+ },
1654
+ {
1655
+ title: 'header-multi-csp-currency-switcher',
1656
+ code: `// 공통 헤더(header)에서 사용법 — 멀티 CSP(수집 통화 혼재) 조합
1657
+ // 'multi-csp-native-currency' 예제와 같은 props를 currencySwitcherProps 안에 그대로 넣으면 된다
1658
+ <OpsnowFinopsCommonHeader
1659
+ showCurrencySwitcher
1660
+ currencySwitcherProps={{
1661
+ value: currency,
1662
+ onChange: setCurrency,
1663
+ rateInfo: multiCspRateInfo, // details.USD 에 CSP 별 환산 환율
1664
+ historySeries,
1665
+ onHistoryQueryChange: refetchHistory,
1666
+
1667
+ nativeCurrency: 'KRW', // 무환산(수집) 통화
1668
+ historyMode: 'popup', // 히스토리 항상 중앙 팝업
1669
+ simulationTarget: { currency: 'USD', payer: 'Azure' },
1670
+ }}
1671
+ // 적용 대상 외 메뉴에서 뜨는 안내 문구를 앱 문구로 덮어쓰기
1672
+ currencySwitcherDisabledTooltip={t('header.currencyNotSupported')}
1673
+ />`,
1674
+ description: "공통 헤더(header)에서 사용법 — 멀티 CSP(수집 통화 혼재) 조합 예제입니다. 'multi-csp-native-currency' 예제와 같은 props(nativeCurrency/historyMode/simulationTarget 포함)를 currencySwitcherProps 안에 그대로 넣으면 되고, 비활성 안내 문구는 currencySwitcherDisabledTooltip으로 앱 번역으로 덮어쓸 수 있습니다. [검색 키워드: 헤더 멀티 CSP, 헤더 통화 전환, 헤더에서 사용법, 수집 통화, currencySwitcherDisabledTooltip]"
1675
+ },
1676
+ {
1677
+ title: 'standalone-props-reference',
1678
+ code: `// ⚠️ 단독 배치는 props 참고용 — 실서비스 배치는 header-currency-switcher 예제 사용
1679
+ const { OpsnowCommonCurrencySwitcher } = useCommonComponents()
1614
1680
 
1615
1681
  const [currency, setCurrency] = useState('USD')
1616
1682
  const [appliedRate, setAppliedRate] = useState(1)
1683
+ const [historySeries, setHistorySeries] = useState([])
1617
1684
 
1618
1685
  // BE 환율 API 응답을 그대로 주입 (컴포넌트는 API를 호출하지 않음)
1619
1686
  const rateInfo = {
1620
1687
  baseDate: '2026-07-08', // 적용 환율 기준일
1621
1688
  source: '한국수출입은행', // 출처 (선택)
1622
- rates: { USD: 1, KRW: 1450, JPY: 156.58, VND: 25508, AED: 3.67 }, // 기준 통화(USD) 1단위당 환율
1689
+ rates: { USD: 1, KRW: 1450, JPY: 156.58, VND: 25508, AED: 3.67 }, // 기준 통화(USD) 1단위당 대표 환율
1690
+ details: { // (선택) 통화별 Payer/Invoice 상세 환율
1691
+ KRW: [ // 2건 이상이면 '다중 환율' — 배지 · 현재 적용 환율 목록 · 히스토리 팝업
1692
+ { payer: 'Payer 1234', invoice: 'Invoice 0000', rate: 1450 },
1693
+ { payer: 'Payer 5678', invoice: 'Invoice 9999', rate: 1462 },
1694
+ ],
1695
+ },
1623
1696
  }
1624
1697
 
1625
- // 환율 상세 보기 차트용 월별(또는 일별) 추이 — 과거 → 현재 순, 마지막 값 = 현재 환율
1626
- const history = [
1627
- { label: '25.07', rate: 1495.3 },
1628
- { label: '26.06', rate: 1462.1 },
1629
- { label: '26.07', rate: 1450 },
1630
- ]
1631
-
1632
1698
  <OpsnowCommonCurrencySwitcher
1633
1699
  value={currency}
1634
1700
  onChange={(code, rate) => {
@@ -1636,13 +1702,22 @@ const history = [
1636
1702
  setAppliedRate(rate) // 금액 표시 변환은 소비 측에서 rate로 처리
1637
1703
  }}
1638
1704
  rateInfo={rateInfo}
1639
- history={history}
1705
+ historySeries={historySeries}
1706
+ onHistoryQueryChange={(code, months) => {
1707
+ // 히스토리 열림/팝업 통화·기간 셀렉트 변경 시 통지 → BE 재조회 후 주입
1708
+ // historySeries: [{ name, label?, current?, points: [{ label, rate }, ...] }, ...]
1709
+ fetchHistory(code, months).then(setHistorySeries)
1710
+ }}
1711
+ onCustomRateChange={(rate, context) => {
1712
+ // what-if 시뮬레이션 값 (저장되지 않음, onChange 미호출)
1713
+ }}
1640
1714
  />`,
1641
- description: '통화 전환 드롭다운 + 환율 팝오버 기본 예제입니다. 환율 데이터는 컴포넌트가 조회하지 않으므로 BE 환율 API 응답을 rateInfo/history props로 주입합니다. [검색 키워드: 통화 전환, 환율, 환율 필터, 통화 필터, 통화 변환, currency, exchange rate]'
1715
+ description: '통화 전환 드롭다운 + 환율 팝오버 + 환율 히스토리 props 참고용 예제입니다 (⚠️ 실서비스 배치는 header-currency-switcher 예제처럼 공통 헤더로). 환율 데이터는 컴포넌트가 조회하지 않으므로 BE 환율 API 응답을 rateInfo/historySeries props로 주입하고, onHistoryQueryChange(currency, months)로 조회 조건을 통지받아 재조회합니다. rateInfo.details가 2건 이상인 통화는 다중 환율로 취급되어 히스토리가 화면 중앙 팝업(차트/테이블)으로 열리고, 단일 환율·기준 통화는 팝오버 안 인라인 차트로 열립니다. [검색 키워드: 통화 전환, 환율, 환율 필터, 통화 필터, 통화 변환, 환율 히스토리, 다중 환율, currency, exchange rate]'
1642
1716
  },
1643
1717
  {
1644
1718
  title: 'custom-options-no-detail',
1645
- code: `<OpsnowCommonCurrencySwitcher
1719
+ code: `// ⚠️ 단독 배치는 props 참고용 — 실서비스 배치는 header-currency-switcher 예제 사용
1720
+ <OpsnowCommonCurrencySwitcher
1646
1721
  value={currency}
1647
1722
  onChange={handleCurrencyChange}
1648
1723
  rateInfo={rateInfo}
@@ -1654,9 +1729,40 @@ const history = [
1654
1729
  baseCurrency="USD"
1655
1730
  size="small"
1656
1731
  showDetail={false}
1732
+ showHistory={false}
1733
+ />`,
1734
+ description: '팝오버 통화 목록을 커스텀하고 환율 설정(시뮬레이션)·히스토리 없이 small 크기 트리거로 사용하는 예제입니다.'
1735
+ },
1736
+ {
1737
+ title: 'multi-csp-native-currency',
1738
+ code: `// ⚠️ 단독 배치는 props 참고용 — 실서비스 배치는 header-multi-csp-currency-switcher 예제 사용
1739
+ // 수집 통화가 CSP별로 다른 회사 (AWS=USD, Azure/GCP=KRW 수집)
1740
+ // USD로 '보는' 것 자체가 KRW 환율로 나누는 환산이므로 details를 기준 통화 키(USD)에 담고
1741
+ // 행마다 currency 필드로 행 단위 통화를 표기, 무환산 문구는 nativeCurrency='KRW' 행에 붙음
1742
+ const multiCspRateInfo = {
1743
+ baseDate: '2026-07-08',
1744
+ source: '한국수출입은행',
1745
+ rates: { USD: 1, KRW: 1508.8, JPY: 156.58 },
1746
+ details: {
1747
+ USD: [
1748
+ { payer: 'Azure', invoice: 'Invoice 1111', rate: 1508.8, currency: 'KRW' },
1749
+ { payer: 'GCP', invoice: 'Invoice 2222', rate: 1512.3, currency: 'KRW' },
1750
+ ],
1751
+ },
1752
+ }
1753
+
1754
+ <OpsnowCommonCurrencySwitcher
1755
+ value={currency}
1756
+ onChange={handleCurrencyChange}
1757
+ rateInfo={multiCspRateInfo}
1758
+ historySeries={historySeries}
1759
+ onHistoryQueryChange={handleHistoryQueryChange}
1760
+ nativeCurrency="KRW" // 무환산(수집) 통화 — 앵커(baseCurrency=USD)와 분리
1761
+ historyMode="popup" // 히스토리 항상 중앙 팝업 (USD 뷰 포함)
1762
+ simulationTarget={{ currency: 'USD', payer: 'Azure' }} // 시뮬레이션 대상을 실제 환산되는 CSP로 고정
1657
1763
  />`,
1658
- description: '팝오버 통화 목록을 커스텀하고 환율 상세 보기 드릴다운 없이 small 크기 트리거로 사용하는 예제입니다.'
1659
- }
1764
+ description: "수집 통화 분리 — 멀티 CSP 혼재 예제입니다 (opsnow-common-dropdown >= 1.0.32). 기준 통화(USD)는 환율 앵커로 유지하되 '변환 없음(Default)' 문구는 nativeCurrency='KRW' 행에 붙습니다. USD 행에는 'N개 환율' 배지가 붙고, USD 선택 시 '현재 적용 환율'에 CSP별 환산 환율이 행 단위 통화(KRW)로 나열됩니다. historyMode='popup'이면 선택 통화와 무관하게 히스토리가 항상 중앙 팝업으로 열리고, simulationTarget으로 환율 설정(시뮬레이션) 대상이 실제 환산되는 Payer에 고정됩니다. [검색 키워드: 멀티 CSP, 수집 통화, 무환산, nativeCurrency, historyMode, simulationTarget]"
1765
+ },
1660
1766
  ];
1661
1767
  export const ToggleButtonExamples = [
1662
1768
  {
@@ -290,19 +290,27 @@ export const InsightContentSchema = z.object({
290
290
  emptyMessage: z.string().optional().describe("insightText가 없을 때 표시할 빈 메시지"),
291
291
  sx: z.string().optional().describe("MUI sx 스타일 객체(JSX/문자열)"),
292
292
  });
293
- // CurrencySwitcher 컴포넌트 관련 스키마 정의 (통화 전환 드롭다운 + 환율 팝오버, opsnow-common-forms >= 1.0.19)
293
+ // CurrencySwitcher 컴포넌트 관련 스키마 정의 (통화 전환 드롭다운 + 환율 팝오버 + 환율 히스토리, opsnow-common-dropdown >= 1.0.29 · nativeCurrency/historyMode/simulationTarget은 >= 1.0.32)
294
+ // 공통 헤더(OpsnowFinopsCommonHeader) 배선은 헤더의 props(showCurrencySwitcher 등)라 이 스키마에 없음 — createCurrencySwitcher description·헤더 예제 참고
294
295
  export const CurrencySwitcherSchema = z.object({
295
296
  value: z.string().describe("현재 표시 통화 코드 상태 변수명 (controlled, 예: currency)"),
296
- onChange: z.string().describe("통화 선택/직접 입력 시 호출되는 핸들러 함수명 — (currency, rate) => void 형태, rate는 기준 통화 1단위당 적용 환율 (예: handleCurrencyChange)"),
297
- rateInfo: z.string().optional().describe("기준일 환율 정보 객체 변수명 — { baseDate, source?, rates } 형태로 BE 환율 API 응답을 주입 (없으면 목록에 환율이 '-'로 표시, 예: rateInfo)"),
298
- history: z.string().optional().describe("환율 상세 보기 추이 차트 데이터 배열 변수명 — Array<{ label, rate }>, 과거 현재 순이며 마지막 = 현재 환율 (예: history)"),
297
+ onChange: z.string().describe("통화 선택 시 호출되는 핸들러 함수명 — (currency, rate) => void 형태, rate는 기준 통화 1단위당 대표 환율 (예: handleCurrencyChange)"),
298
+ rateInfo: z.string().optional().describe("기준일 환율 정보 객체 변수명 — { baseDate, source?, rates, details? } 형태로 BE 환율 API 응답을 주입. details는 통화별 Payer/Invoice 상세 환율(Array<{ payer, invoice?, rate, currency? }>)로 2건 이상이면 다중 환율 통화로 취급 (없으면 목록에 환율이 '-'로 표시). 수집 통화가 CSP별로 다른 멀티 CSP 케이스는 details를 기준 통화 키(details.USD)에 담고 행마다 currency 필드로 행 단위 통화를 표기 — nativeCurrency와 함께 사용 (예: rateInfo)"),
299
+ historySeries: z.string().optional().describe("환율 히스토리 시리즈 배열 변수명 (controlled) — Array<{ name, label?, current?, points: Array<{ label, rate }> }>, onHistoryQueryChange로 통지받은 조건 기준으로 BE 재조회 주입. 인라인 차트는 첫 번째 시리즈(대표 환율)만 사용 (예: historySeries)"),
300
+ onHistoryQueryChange: z.string().optional().describe("히스토리 조회 조건 통지 핸들러 함수명 — (currency, months) => void 형태, 히스토리 열림/팝업 통화·기간 셀렉트 변경 시 호출 → 소비 측이 BE 재조회 후 historySeries 갱신 (예: handleHistoryQueryChange)"),
301
+ historyPeriodOptions: z.string().optional().describe("히스토리 팝업 기간 옵션 배열 변수명 — number[] (기본값: [6, 12, 24])"),
302
+ defaultHistoryPeriod: z.number().optional().describe("히스토리 기본 조회 기간(개월, 기본값: 24)"),
299
303
  currencyOptions: z.string().optional().describe("팝오버 통화 목록 배열 변수명 — Array<{ code, symbol, digits? }> (기본: USD/KRW/JPY/VND/AED)"),
300
- baseCurrency: z.string().optional().describe("변환 기준 통화 코드 (기본값: 'USD')"),
301
- defaultTargetCurrency: z.string().optional().describe("기준 통화 선택 중일 상세 보기가 다룰 초기 비교 통화 코드"),
304
+ baseCurrency: z.string().optional().describe("변환 기준 통화 코드 — 환율이 '1 {base} = X'로 저장되는 앵커. 무환산 표기와는 별개 개념(nativeCurrency 참고) (기본값: 'USD')"),
305
+ nativeCurrency: z.string().optional().describe("무환산(수집) 통화 코드 '변환 없음(Default)' 문구가 붙는 행. 수집 통화가 기준 통화와 다른 멀티 CSP 조합(예: Azure/GCP KRW 수집)에서 baseCurrency(환율 앵커)와 분리 지정 (기본값: baseCurrency, dropdown >= 1.0.32)"),
306
+ defaultTargetCurrency: z.string().optional().describe("기준 통화 선택 중일 때 환율 설정이 다룰 초기 비교 통화 코드"),
307
+ simulationTarget: z.string().optional().describe("환율 설정(시뮬레이션) 대상 지정 객체 변수명 — { currency, payer?, invoice? } 형태, 기준 통화 뷰에서 실제 환산되는 CSP(Payer)로 고정하며 payer/invoice 일치 행이 사전 선택됨 (예: simulationTarget, dropdown >= 1.0.32)"),
302
308
  customRate: z.string().optional().describe("직접 입력 시뮬레이션 환율 상태 변수명 (number | null, controlled 모드 — 미지정 시 내부 관리)"),
303
- onCustomRateChange: z.string().optional().describe("직접 입력 환율 변경 핸들러 함수명 — (rate) => void 형태"),
304
- showDetail: z.boolean().optional().describe("환율 상세 보기 드릴다운 노출 여부 (기본값: true)"),
305
- renderChart: z.string().optional().describe("추이 차트 교체 슬롯 함수명 (history, currency) => ReactNode 형태 (기본: 내장 SVG 스파크라인)"),
309
+ onCustomRateChange: z.string().optional().describe("직접 입력 환율 변경 핸들러 함수명 — (rate, context) => void 형태, 저장되지 않는 what-if 값 통지 (onChange는 호출되지 않음)"),
310
+ showDetail: z.boolean().optional().describe("환율 설정(직접 입력 시뮬레이션) 섹션 노출 여부 (기본값: true)"),
311
+ showHistory: z.boolean().optional().describe("월별 환율 히스토리 링크·인라인 차트·팝업 노출 여부 (기본값: true)"),
312
+ historyMode: z.enum(["auto", "popup"]).optional().describe("히스토리 열림 방식 — 'popup'이면 선택 통화·환율 수와 무관하게 항상 화면 중앙 팝업으로 열고 통화 셀렉트에 기준 통화 포함, 'auto'는 다중 환율=팝업/단일 환율·기준 통화=인라인 분기 (기본값: 'auto', dropdown >= 1.0.32)"),
313
+ renderChart: z.string().optional().describe("히스토리 팝업 차트 교체 슬롯 함수명 — (series, currency) => ReactNode 형태, 체크된(보이는) 시리즈만 전달 (기본: 내장 SVG 멀티라인 차트)"),
306
314
  labels: z.string().optional().describe("문구 개별 커스텀 객체 변수명 — Partial<CurrencySwitcherLabels>, i18n(ko/en/ja) 번역 위에 항목별로 덮어씀 ({placeholder} 템플릿 치환 지원)"),
307
315
  size: z.enum(["small", "medium"]).optional().describe("트리거 크기"),
308
316
  disabled: z.boolean().optional().describe("비활성화 여부"),
@@ -1113,60 +1121,115 @@ export function createFormsComponent() {
1113
1121
  },
1114
1122
  {
1115
1123
  name: "createCurrencySwitcher",
1116
- description: `CurrencySwitcher 컴포넌트 - 통화 전환 드롭다운 + 환율 팝오버, 환율/통화 필터로 사용 (opsnow-common-forms >= 1.0.19)
1124
+ description: `CurrencySwitcher 컴포넌트 - 통화 전환 드롭다운 + 환율 팝오버 + 환율 히스토리 — ⚠️ 실서비스 배치는 반드시 공통 헤더(OpsnowFinopsCommonHeader)의 currencySwitcherProps로만 (opsnow-common-dropdown >= 1.0.29 · nativeCurrency/historyMode/simulationTarget은 >= 1.0.32)
1125
+
1126
+ ⚠️ **배치 규칙: 실서비스에서는 반드시 OpsnowFinopsCommonHeader의 currencySwitcherProps로만 배치합니다. 단독 \`<OpsnowCommonCurrencySwitcher>\` 사용은 props 참고용 데모에 한합니다.**
1117
1127
 
1118
1128
  트리거에 현재 선택된 통화가 표시되고, 클릭하면 팝오버에서 적용 환율 기준일 안내와
1119
- 통화 목록(클릭 시 즉시 적용)을 제공합니다. "환율 상세 보기" 드릴다운에서 직접 입력
1120
- 시뮬레이션, 현재/최고/최저/변동 스탯, 환율 추이 차트를 확인할 있습니다.
1129
+ 통화 목록(클릭 시 즉시 적용)을 제공합니다. Payer/Invoice별 환율이 여러 개인 통화는
1130
+ 'N개 환율' 배지와 '현재 적용 환율' 목록이 표시되고, 환율 설정에서 Payer를 골라
1131
+ what-if 시뮬레이션을 입력할 수 있습니다.
1121
1132
 
1122
1133
  **데이터 주입 규칙 (중요):**
1123
- - 환율 데이터는 컴포넌트가 조회하지 않습니다 — BE 환율 API 응답을 rateInfo / history props로 주입하세요.
1124
- - rateInfo: { baseDate: 'YYYY-MM-DD', source?: '출처', rates: { USD: 1, KRW: 1450, ... } } — 기준 통화 1단위당 환율
1125
- - history: [{ label: '25.07', rate: 1495.3 }, ...] 과거 → 현재 순, 마지막 값 = 현재 환율
1134
+ - 환율 데이터는 컴포넌트가 조회하지 않습니다 — BE 환율 API 응답을 rateInfo / historySeries props로 주입하고, 조회 조건이 바뀌면 onHistoryQueryChange(currency, months)로 통지받아 재조회하세요.
1135
+ - rateInfo: { baseDate: 'YYYY-MM-DD', source?: '출처', rates: { USD: 1, KRW: 1450, ... }, details?: { KRW: [{ payer, invoice?, rate }, ...] } } — 기준 통화 1단위당 환율, details의 통화별 배열이 2건 이상이면 다중 환율 통화로 취급
1136
+ - 멀티 CSP(수집 통화 혼재, 예: AWS=USD·Azure/GCP=KRW 수집): details를 기준 통화 키에 담고 행마다 currency 필드로 행 단위 통화 표기 — details: { USD: [{ payer: 'Azure', rate: 1508.8, currency: 'KRW' }, ...] }, nativeCurrency='KRW'와 함께 사용
1137
+ - historySeries: [{ name, label?, current?, points: [{ label, rate }, ...] }, ...] — 모든 시리즈는 같은 월 구간으로 정렬, 대표 환율이 첫 번째 시리즈가 되도록 정렬 (인라인 차트는 첫 번째 시리즈만 사용)
1126
1138
  - BE API 호출 시 axios 직접 import 금지 — getAxios() 공통 axios 사용
1127
1139
 
1140
+ **히스토리 동작 분기 규칙:**
1141
+
1142
+ | 선택 통화 상태 | 히스토리 동작 | 조회 통화 | 표시 시리즈 |
1143
+ |------|------|------|------|
1144
+ | historyMode='popup' 지정 (선택 통화·환율 수 무관) | 항상 화면 중앙 팝업 — 통화 셀렉트에 기준 통화 포함 | 선택 통화 (기준 통화 포함) | 주입 시리즈 전체 (멀티 CSP 다중 시리즈) |
1145
+ | 'auto' · 다중 환율 통화 선택 (details 2건 이상 — 기준 통화 포함) | 화면 중앙 팝업 (차트/테이블 전환, Payer/Invoice 검색·체크박스 — 팝오버는 열린 채 유지, 닫으면 복귀) | 선택 통화 | Payer/Invoice별 전체 시리즈 |
1146
+ | 'auto' · 단일 환율 통화 선택 (예: JPY, VND) | 팝오버 안에 인라인 차트 펼침/접힘 (팝업 없음) | 선택 통화 | 첫 번째 시리즈(대표 환율)만 |
1147
+ | 'auto' · 기준 통화 선택 중 + details 없음 (예: USD) | 팝오버 안에 인라인 차트 펼침/접힘 (팝업 없음) | 선택 통화(기준 통화) | 첫 번째 시리즈(대표 환율)만 |
1148
+
1128
1149
  **동작 규칙:**
1129
- - 선택/직접 입력 시 onChange(code, rate)가 호출됩니다 — 금액 표시 변환은 소비 프로젝트에서 rate로 처리
1130
- - 직접 입력 환율은 시뮬레이션 용도이며 저장되지 않습니다
1150
+ - 통화 선택 시 onChange(currency, rate)가 호출됩니다 — 금액 표시 변환은 소비 프로젝트에서 rate로 처리
1151
+ - 직접 입력 환율은 저장되지 않는 what-if 시뮬레이션 값으로 onCustomRateChange(rate, context)로만 통지되며, onChange(실제 적용)는 호출되지 않습니다
1131
1152
  - 문구는 i18n 현재 언어(ko/en/ja)를 자동으로 따르고, labels prop으로 항목별 커스텀 가능
1132
1153
 
1154
+ **공통 헤더에서 쓰기 (OpsnowFinopsCommonHeader, opsnow-finops-common-header >= 1.0.20):**
1155
+ - 실서비스 배치는 **무조건 공통 헤더를 통해서** 합니다 — 페이지 본문에 단독 배치하지 마세요 (단독 예제는 props 사용법 참고용)
1156
+ - 헤더는 AI 버튼~알림 벨 사이 자리만 제공하고 상태·환율 데이터를 갖지 않습니다 — 앱이 관리하는 값을 currencySwitcherProps로 주입하면 내부 OpsnowCommonCurrencySwitcher에 그대로 전달됩니다 (위 props 규칙 동일 적용)
1157
+ - **활성/비활성 정책**: 통화 변경은 '개요(OverView)' · '비용 분석(Analytics)' · '청구 내역(Billing Invoice)' · '비용 배분(Cost Allocation)' 4개 메뉴에서만 허용됩니다 — 그 외 메뉴 진입 시 currencySwitcherEnabled={false}로 넘겨 버튼을 disabled 상태로 노출하고, 호버 시 안내 툴팁이 표시됩니다 (문구는 내장 ko/en/ja 기본, currencySwitcherDisabledTooltip으로 덮어쓰기 가능). 적용 라우트: '/overview' · '/cost/analytics/usage-charges' · '/cost/billing-invoice' · '/settings/cost-allocation'(하위 경로 포함) — 현재 라우트가 여기에 속하는지를 앱에서 판단해 주입하세요
1158
+
1159
+ 헤더 전용 Props:
1160
+
1161
+ | 이름 | 타입 | 기본값 | 설명 |
1162
+ |------|------|--------|------|
1163
+ | showCurrencySwitcher | boolean | false | 헤더에 통화 전환 노출 여부. props를 주입해 둔 채로 메뉴에 따라 아예 숨겨야 할 때도 false로 준다 (비활성+툴팁은 currencySwitcherEnabled 사용) |
1164
+ | currencySwitcherProps | OpsnowCommonCurrencySwitcherProps | — | 위 Props 표의 값이 그대로 전달된다. 주입하지 않으면 showCurrencySwitcher가 true여도 렌더되지 않음 |
1165
+ | currencySwitcherEnabled | boolean | true | 현재 메뉴가 통화 전환 적용 대상인지. false면 트리거를 비활성화하고 호버 시 안내 툴팁 표시 |
1166
+ | currencySwitcherDisabledTooltip | string | 내장 ko/en/ja 문구 | 비활성 안내 툴팁 문구 덮어쓰기 (미지정 시 헤더 locale을 따르는 기본 문구) |
1167
+
1168
+ - ⚠️ 위 4개는 CurrencySwitcher가 아니라 **헤더(OpsnowFinopsCommonHeader)의 props**입니다 — 이 도구의 파라미터/스키마에 없으며 OpsnowCommonCurrencySwitcher에 직접 붙이면 안 됩니다. 배선 코드는 header-currency-switcher / header-multi-csp-currency-switcher 예제(getUIExamples) 참고
1169
+ - ⚠️ 이 MCP는 헤더 props 중 통화 전환 관련 위 4개만 안내합니다 — menuList·로고·도움말·언어·테마 토글 등 헤더의 다른 props는 제공하지 않으므로, 프로젝트의 기존 헤더 코드는 그대로 두고 통화 관련 props만 추가하세요
1170
+
1133
1171
  **import:**
1134
1172
  \`\`\`javascript
1173
+ // 단독 사용
1135
1174
  import { useCommonComponents } from '@opsnow-common/opsnow-finops-common-ui-loader';
1136
1175
  const { OpsnowCommonCurrencySwitcher } = useCommonComponents();
1176
+
1177
+ // 공통 헤더에서 사용 (헤더 배선 시에만)
1178
+ import OpsnowFinopsCommonHeader from '@opsnow-common/opsnow-finops-common-header';
1137
1179
  \`\`\``,
1138
1180
  parameters: CurrencySwitcherSchema,
1139
1181
  handler: async (args) => {
1140
- const props = [];
1182
+ const entries = [];
1141
1183
  if (args.value)
1142
- props.push(`value={${args.value}}`);
1184
+ entries.push(["value", args.value]);
1143
1185
  if (args.onChange)
1144
- props.push(`onChange={${args.onChange}}`);
1186
+ entries.push(["onChange", args.onChange]);
1145
1187
  if (args.rateInfo)
1146
- props.push(`rateInfo={${args.rateInfo}}`);
1147
- if (args.history)
1148
- props.push(`history={${args.history}}`);
1188
+ entries.push(["rateInfo", args.rateInfo]);
1189
+ if (args.historySeries)
1190
+ entries.push(["historySeries", args.historySeries]);
1191
+ if (args.onHistoryQueryChange)
1192
+ entries.push(["onHistoryQueryChange", args.onHistoryQueryChange]);
1193
+ if (args.historyPeriodOptions)
1194
+ entries.push(["historyPeriodOptions", args.historyPeriodOptions]);
1195
+ if (args.defaultHistoryPeriod !== undefined)
1196
+ entries.push(["defaultHistoryPeriod", String(args.defaultHistoryPeriod)]);
1149
1197
  if (args.currencyOptions)
1150
- props.push(`currencyOptions={${args.currencyOptions}}`);
1198
+ entries.push(["currencyOptions", args.currencyOptions]);
1151
1199
  if (args.baseCurrency)
1152
- props.push(`baseCurrency="${args.baseCurrency}"`);
1200
+ entries.push(["baseCurrency", `'${args.baseCurrency}'`]);
1201
+ if (args.nativeCurrency)
1202
+ entries.push(["nativeCurrency", `'${args.nativeCurrency}'`]);
1153
1203
  if (args.defaultTargetCurrency)
1154
- props.push(`defaultTargetCurrency="${args.defaultTargetCurrency}"`);
1204
+ entries.push(["defaultTargetCurrency", `'${args.defaultTargetCurrency}'`]);
1155
1205
  if (args.customRate)
1156
- props.push(`customRate={${args.customRate}}`);
1206
+ entries.push(["customRate", args.customRate]);
1157
1207
  if (args.onCustomRateChange)
1158
- props.push(`onCustomRateChange={${args.onCustomRateChange}}`);
1208
+ entries.push(["onCustomRateChange", args.onCustomRateChange]);
1159
1209
  if (args.showDetail !== undefined)
1160
- props.push(`showDetail={${args.showDetail}}`);
1210
+ entries.push(["showDetail", String(args.showDetail)]);
1211
+ if (args.showHistory !== undefined)
1212
+ entries.push(["showHistory", String(args.showHistory)]);
1213
+ if (args.historyMode)
1214
+ entries.push(["historyMode", `'${args.historyMode}'`]);
1215
+ if (args.simulationTarget)
1216
+ entries.push(["simulationTarget", args.simulationTarget]);
1161
1217
  if (args.renderChart)
1162
- props.push(`renderChart={${args.renderChart}}`);
1218
+ entries.push(["renderChart", args.renderChart]);
1163
1219
  if (args.labels)
1164
- props.push(`labels={${args.labels}}`);
1220
+ entries.push(["labels", args.labels]);
1165
1221
  if (args.size)
1166
- props.push(`size="${args.size}"`);
1222
+ entries.push(["size", `'${args.size}'`]);
1167
1223
  if (args.disabled)
1168
- props.push(`disabled`);
1169
- const code = `<OpsnowCommonCurrencySwitcher\n ${props.join('\n ')}\n/>`;
1224
+ entries.push(["disabled", "true"]);
1225
+ // 'USD' 같은 문자열 리터럴은 JSX에서 attr="USD" 형태로 렌더
1226
+ const toJsxAttr = ([k, v]) => {
1227
+ if (v === "true")
1228
+ return k;
1229
+ const literal = v.match(/^'([^']*)'$/);
1230
+ return literal ? `${k}="${literal[1]}"` : `${k}={${v}}`;
1231
+ };
1232
+ const code = `<OpsnowCommonCurrencySwitcher\n ${entries.map(toJsxAttr).join('\n ')}\n/>`;
1170
1233
  return {
1171
1234
  content: [
1172
1235
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsnow-mcp/opsnow-mcp-common-ui-server",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "bin": {