ERP-node/frontend/components/dashboard/widgets/ExchangeWidget.tsx

238 lines
8.1 KiB
TypeScript
Raw Normal View History

'use client';
/**
*
* -
* - (BOK) API
*/
import React, { useEffect, useState } from 'react';
import { getExchangeRate, ExchangeRateData } from '@/lib/api/openApi';
import { TrendingUp, TrendingDown, RefreshCw, ArrowRightLeft } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
interface ExchangeWidgetProps {
baseCurrency?: string;
targetCurrency?: string;
refreshInterval?: number; // 새로고침 간격 (ms), 기본값: 600000 (10분)
}
export default function ExchangeWidget({
baseCurrency = 'KRW',
targetCurrency = 'USD',
refreshInterval = 600000,
}: ExchangeWidgetProps) {
const [base, setBase] = useState(baseCurrency);
const [target, setTarget] = useState(targetCurrency);
const [exchangeRate, setExchangeRate] = useState<ExchangeRateData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
// 지원 통화 목록
const currencies = [
{ value: 'KRW', label: '🇰🇷 KRW (원)', symbol: '₩' },
{ value: 'USD', label: '🇺🇸 USD (달러)', symbol: '$' },
{ value: 'EUR', label: '🇪🇺 EUR (유로)', symbol: '€' },
{ value: 'JPY', label: '🇯🇵 JPY (엔)', symbol: '¥' },
{ value: 'CNY', label: '🇨🇳 CNY (위안)', symbol: '¥' },
{ value: 'GBP', label: '🇬🇧 GBP (파운드)', symbol: '£' },
];
// 환율 조회
const fetchExchangeRate = async () => {
try {
setError(null);
setLoading(true);
const data = await getExchangeRate(base, target);
setExchangeRate(data);
setLastUpdated(new Date());
} catch (err: any) {
console.error('환율 조회 실패:', err);
let errorMessage = '환율 정보를 가져오는 중 오류가 발생했습니다.';
if (err.response?.status === 503) {
errorMessage = 'API 키가 설정되지 않았습니다. 관리자에게 문의하세요.';
} else if (err.response?.status === 401) {
errorMessage = 'API 키가 유효하지 않습니다.';
} else if (err.response?.data?.message) {
errorMessage = err.response.data.message;
}
setError(errorMessage);
} finally {
setLoading(false);
}
};
// 초기 로딩 및 자동 새로고침
useEffect(() => {
fetchExchangeRate();
const interval = setInterval(fetchExchangeRate, refreshInterval);
return () => clearInterval(interval);
}, [base, target, refreshInterval]);
// 통화 스왑
const handleSwap = () => {
setBase(target);
setTarget(base);
};
// 통화 기호 가져오기
const getCurrencySymbol = (currency: string) => {
return currencies.find((c) => c.value === currency)?.symbol || currency;
};
// 로딩 상태
if (loading && !exchangeRate) {
return (
<div className="flex h-full items-center justify-center bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg border p-6">
<div className="flex flex-col items-center gap-2">
<RefreshCw className="h-8 w-8 animate-spin text-green-500" />
<p className="text-sm text-gray-600"> ...</p>
</div>
</div>
);
}
// 에러 상태
if (error || !exchangeRate) {
return (
<div className="flex h-full flex-col items-center justify-center bg-gradient-to-br from-red-50 to-orange-50 rounded-lg border p-6">
<TrendingDown className="h-12 w-12 text-gray-400 mb-2" />
<p className="text-sm text-gray-600 text-center mb-3">{error || '환율 정보를 불러올 수 없습니다.'}</p>
<Button
variant="outline"
size="sm"
onClick={fetchExchangeRate}
className="gap-1"
>
<RefreshCw className="h-3 w-3" />
</Button>
</div>
);
}
return (
<div className="h-full bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg border p-6">
{/* 헤더 */}
<div className="flex items-center justify-between mb-4">
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 mb-1">💱 </h3>
<p className="text-xs text-gray-500">
{lastUpdated
? `업데이트: ${lastUpdated.toLocaleTimeString('ko-KR', {
hour: '2-digit',
minute: '2-digit',
})}`
: ''}
</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={fetchExchangeRate}
disabled={loading}
className="h-8 w-8 p-0"
>
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
</Button>
</div>
{/* 통화 선택 */}
<div className="flex items-center gap-2 mb-6">
<Select value={base} onValueChange={setBase}>
<SelectTrigger className="flex-1 bg-white">
<SelectValue />
</SelectTrigger>
<SelectContent>
{currencies.map((currency) => (
<SelectItem key={currency.value} value={currency.value}>
{currency.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
variant="ghost"
size="sm"
onClick={handleSwap}
className="h-10 w-10 p-0 rounded-full hover:bg-white"
>
<ArrowRightLeft className="h-4 w-4" />
</Button>
<Select value={target} onValueChange={setTarget}>
<SelectTrigger className="flex-1 bg-white">
<SelectValue />
</SelectTrigger>
<SelectContent>
{currencies.map((currency) => (
<SelectItem key={currency.value} value={currency.value}>
{currency.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 환율 표시 */}
<div className="bg-white rounded-lg border p-4 mb-4">
<div className="text-center">
<div className="text-sm text-gray-600 mb-2">
{exchangeRate.base === 'KRW' ? '1,000' : '1'} {getCurrencySymbol(exchangeRate.base)} =
</div>
<div className="text-3xl font-bold text-gray-900 mb-1">
{exchangeRate.base === 'KRW'
? (exchangeRate.rate * 1000).toLocaleString('ko-KR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
: exchangeRate.rate.toLocaleString('ko-KR', {
minimumFractionDigits: 2,
maximumFractionDigits: 4,
})}
</div>
<div className="text-sm text-gray-600">{getCurrencySymbol(exchangeRate.target)}</div>
</div>
</div>
{/* 계산 예시 */}
<div className="grid grid-cols-2 gap-3">
<div className="bg-white rounded-lg border p-3">
<div className="text-xs text-gray-500 mb-1">10,000 {base}</div>
<div className="text-lg font-semibold text-gray-900">
{(10000 * (base === 'KRW' ? exchangeRate.rate : 1 / exchangeRate.rate)).toLocaleString('ko-KR', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})}{' '}
{target}
</div>
</div>
<div className="bg-white rounded-lg border p-3">
<div className="text-xs text-gray-500 mb-1">100,000 {base}</div>
<div className="text-lg font-semibold text-gray-900">
{(100000 * (base === 'KRW' ? exchangeRate.rate : 1 / exchangeRate.rate)).toLocaleString('ko-KR', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})}{' '}
{target}
</div>
</div>
</div>
{/* 데이터 출처 */}
<div className="mt-4 pt-3 border-t text-center">
<p className="text-xs text-gray-400">: {exchangeRate.source}</p>
</div>
</div>
);
}