- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
/**
|
|
* Exchange Rate Store
|
|
* 한국수출입은행 API에서 가져온 환율 정보를 저장하고 관리
|
|
*/
|
|
import { create } from 'zustand';
|
|
import { exchangeRateApi, ExchangeRateSimple } from './api';
|
|
|
|
// 기본 환율 (API 실패 시 사용) - 2024년 12월 기준
|
|
const DEFAULT_RATES: ExchangeRateSimple = {
|
|
USD: { rate: 1483, symbol: '$', name: '미국 달러' },
|
|
MNT: { rate: 0.43, symbol: '₮', name: '몽골 투그릭' },
|
|
RUB: { rate: 14.5, symbol: '₽', name: '러시아 루블' },
|
|
CNY: { rate: 203, symbol: '¥', name: '중국 위안' },
|
|
EUR: { rate: 1750, symbol: '€', name: '유로' },
|
|
JPY: { rate: 9.5, symbol: '¥', name: '일본 엔' },
|
|
};
|
|
|
|
interface ExchangeRateState {
|
|
rates: ExchangeRateSimple;
|
|
isLoading: boolean;
|
|
lastUpdated: Date | null;
|
|
error: string | null;
|
|
fetchRates: () => Promise<void>;
|
|
getKrwToUsd: () => number; // KRW를 USD로 변환하는 비율 (1 KRW = X USD)
|
|
getUsdToMnt: () => number; // USD를 MNT로 변환하는 비율 (1 USD = X MNT)
|
|
getUsdToRub: () => number; // USD를 RUB로 변환하는 비율 (1 USD = X RUB)
|
|
convertKrwTo: (krwAmount: number, currency: string) => number;
|
|
}
|
|
|
|
export const useExchangeRateStore = create<ExchangeRateState>((set, get) => ({
|
|
rates: DEFAULT_RATES,
|
|
isLoading: false,
|
|
lastUpdated: null,
|
|
error: null,
|
|
|
|
fetchRates: async () => {
|
|
// 이미 로딩 중이면 스킵
|
|
if (get().isLoading) return;
|
|
|
|
// 30분 이내에 업데이트했으면 스킵
|
|
const lastUpdated = get().lastUpdated;
|
|
if (lastUpdated && Date.now() - lastUpdated.getTime() < 30 * 60 * 1000) {
|
|
return;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const rates = await exchangeRateApi.getSimpleRates();
|
|
set({
|
|
rates: { ...DEFAULT_RATES, ...rates },
|
|
isLoading: false,
|
|
lastUpdated: new Date(),
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to fetch exchange rates:', error);
|
|
set({
|
|
isLoading: false,
|
|
error: 'Failed to fetch exchange rates',
|
|
});
|
|
// 실패해도 기본값 사용
|
|
}
|
|
},
|
|
|
|
getKrwToUsd: () => {
|
|
const { rates } = get();
|
|
const usdRate = rates.USD?.rate || DEFAULT_RATES.USD.rate;
|
|
return 1 / usdRate; // 1 KRW = 1/1450 USD
|
|
},
|
|
|
|
getUsdToMnt: () => {
|
|
const { rates } = get();
|
|
const usdRate = rates.USD?.rate || DEFAULT_RATES.USD.rate;
|
|
const mntRate = rates.MNT?.rate || DEFAULT_RATES.MNT.rate;
|
|
// MNT rate is already in KRW per MNT
|
|
// So 1 USD = (USD_KRW / MNT_KRW) MNT
|
|
return usdRate / mntRate;
|
|
},
|
|
|
|
getUsdToRub: () => {
|
|
const { rates } = get();
|
|
const usdRate = rates.USD?.rate || DEFAULT_RATES.USD.rate;
|
|
const rubRate = rates.RUB?.rate || DEFAULT_RATES.RUB.rate;
|
|
return usdRate / rubRate;
|
|
},
|
|
|
|
convertKrwTo: (krwAmount: number, currency: string) => {
|
|
const { rates } = get();
|
|
const rate = rates[currency]?.rate || DEFAULT_RATES[currency]?.rate;
|
|
if (!rate) return 0;
|
|
return krwAmount / rate;
|
|
},
|
|
}));
|
|
|
|
// 환율 초기화 함수 (앱 시작 시 호출)
|
|
export async function initExchangeRates() {
|
|
const store = useExchangeRateStore.getState();
|
|
await store.fetchRates();
|
|
}
|
|
|
|
// 환율 변환 헬퍼 함수들
|
|
export function formatWithExchangeRate(
|
|
krwAmount: number,
|
|
currency: 'USD' | 'MNT' | 'RUB' | 'CNY' | 'EUR'
|
|
): string {
|
|
const store = useExchangeRateStore.getState();
|
|
const converted = store.convertKrwTo(krwAmount, currency);
|
|
const symbol = store.rates[currency]?.symbol || DEFAULT_RATES[currency]?.symbol || '';
|
|
|
|
return `${symbol}${converted.toLocaleString('en-US', {
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: currency === 'USD' ? 0 : 0,
|
|
})}`;
|
|
}
|