Compare commits
2 Commits
9775b28d9d
...
f65caf45cd
| Author | SHA1 | Date |
|---|---|---|
|
|
f65caf45cd | |
|
|
dac3e927aa |
|
|
@ -70,6 +70,11 @@ export function CanvasElement({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 위젯 내부 (헤더 제외) 클릭 시 드래그 무시 - 인터랙티브 사용 가능
|
||||||
|
if ((e.target as HTMLElement).closest(".widget-interactive-area")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
onSelect(element.id);
|
onSelect(element.id);
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
setDragStart({
|
setDragStart({
|
||||||
|
|
@ -344,12 +349,12 @@ export function CanvasElement({
|
||||||
</div>
|
</div>
|
||||||
) : element.type === "widget" && element.subtype === "weather" ? (
|
) : element.type === "widget" && element.subtype === "weather" ? (
|
||||||
// 날씨 위젯 렌더링
|
// 날씨 위젯 렌더링
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full widget-interactive-area">
|
||||||
<WeatherWidget city={element.config?.city || "서울"} refreshInterval={600000} />
|
<WeatherWidget city={element.config?.city || "서울"} refreshInterval={600000} />
|
||||||
</div>
|
</div>
|
||||||
) : element.type === "widget" && element.subtype === "exchange" ? (
|
) : element.type === "widget" && element.subtype === "exchange" ? (
|
||||||
// 환율 위젯 렌더링
|
// 환율 위젯 렌더링
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full widget-interactive-area">
|
||||||
<ExchangeWidget
|
<ExchangeWidget
|
||||||
baseCurrency={element.config?.baseCurrency || "KRW"}
|
baseCurrency={element.config?.baseCurrency || "KRW"}
|
||||||
targetCurrency={element.config?.targetCurrency || "USD"}
|
targetCurrency={element.config?.targetCurrency || "USD"}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { getExchangeRate, ExchangeRateData } from '@/lib/api/openApi';
|
||||||
import { TrendingUp, TrendingDown, RefreshCw, ArrowRightLeft } from 'lucide-react';
|
import { TrendingUp, TrendingDown, RefreshCw, ArrowRightLeft } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
|
||||||
interface ExchangeWidgetProps {
|
interface ExchangeWidgetProps {
|
||||||
baseCurrency?: string;
|
baseCurrency?: string;
|
||||||
|
|
@ -29,6 +30,8 @@ export default function ExchangeWidget({
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||||
|
const [calculatorAmount, setCalculatorAmount] = useState<string>('');
|
||||||
|
const [displayAmount, setDisplayAmount] = useState<string>('');
|
||||||
|
|
||||||
// 지원 통화 목록
|
// 지원 통화 목록
|
||||||
const currencies = [
|
const currencies = [
|
||||||
|
|
@ -86,6 +89,33 @@ export default function ExchangeWidget({
|
||||||
return currencies.find((c) => c.value === currency)?.symbol || currency;
|
return currencies.find((c) => c.value === currency)?.symbol || currency;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 계산기 금액 입력 처리
|
||||||
|
const handleCalculatorInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
|
||||||
|
// 쉼표 제거 후 숫자만 추출
|
||||||
|
const cleanValue = value.replace(/,/g, '').replace(/[^\d]/g, '');
|
||||||
|
|
||||||
|
// 계산용 원본 값 저장
|
||||||
|
setCalculatorAmount(cleanValue);
|
||||||
|
|
||||||
|
// 표시용 포맷팅된 값 저장
|
||||||
|
if (cleanValue === '') {
|
||||||
|
setDisplayAmount('');
|
||||||
|
} else {
|
||||||
|
const num = parseInt(cleanValue);
|
||||||
|
setDisplayAmount(num.toLocaleString('ko-KR'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 계산 결과
|
||||||
|
const calculateResult = () => {
|
||||||
|
const amount = parseFloat(calculatorAmount || '0');
|
||||||
|
if (!exchangeRate || isNaN(amount)) return 0;
|
||||||
|
|
||||||
|
return amount * (base === 'KRW' ? exchangeRate.rate : 1 / exchangeRate.rate);
|
||||||
|
};
|
||||||
|
|
||||||
// 로딩 상태
|
// 로딩 상태
|
||||||
if (loading && !exchangeRate) {
|
if (loading && !exchangeRate) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -98,31 +128,15 @@ export default function ExchangeWidget({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 에러 상태
|
// 에러 상태 - 하지만 계산기는 표시
|
||||||
if (error || !exchangeRate) {
|
const hasError = 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 (
|
return (
|
||||||
<div className="h-full bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg border p-6">
|
<div className="h-full bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg border p-4">
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">💱 환율</h3>
|
<h3 className="text-base font-semibold text-gray-900 mb-1">💱 환율</h3>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
{lastUpdated
|
{lastUpdated
|
||||||
? `업데이트: ${lastUpdated.toLocaleTimeString('ko-KR', {
|
? `업데이트: ${lastUpdated.toLocaleTimeString('ko-KR', {
|
||||||
|
|
@ -144,9 +158,9 @@ export default function ExchangeWidget({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 통화 선택 */}
|
{/* 통화 선택 */}
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<Select value={base} onValueChange={setBase}>
|
<Select value={base} onValueChange={setBase}>
|
||||||
<SelectTrigger className="flex-1 bg-white">
|
<SelectTrigger className="flex-1 bg-white h-8 text-xs">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|
@ -162,13 +176,13 @@ export default function ExchangeWidget({
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleSwap}
|
onClick={handleSwap}
|
||||||
className="h-10 w-10 p-0 rounded-full hover:bg-white"
|
className="h-8 w-8 p-0 rounded-full hover:bg-white"
|
||||||
>
|
>
|
||||||
<ArrowRightLeft className="h-4 w-4" />
|
<ArrowRightLeft className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Select value={target} onValueChange={setTarget}>
|
<Select value={target} onValueChange={setTarget}>
|
||||||
<SelectTrigger className="flex-1 bg-white">
|
<SelectTrigger className="flex-1 bg-white h-8 text-xs">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|
@ -181,13 +195,27 @@ export default function ExchangeWidget({
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 에러 메시지 */}
|
||||||
|
{hasError && (
|
||||||
|
<div className="mb-3 p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||||
|
<p className="text-xs text-red-600 text-center">{error || '환율 정보를 불러올 수 없습니다.'}</p>
|
||||||
|
<button
|
||||||
|
onClick={fetchExchangeRate}
|
||||||
|
className="mt-2 w-full text-xs text-red-600 hover:text-red-700 underline"
|
||||||
|
>
|
||||||
|
다시 시도
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 환율 표시 */}
|
{/* 환율 표시 */}
|
||||||
<div className="bg-white rounded-lg border p-4 mb-4">
|
{!hasError && (
|
||||||
|
<div className="mb-2 bg-white rounded-lg border p-2">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-sm text-gray-600 mb-2">
|
<div className="text-xs text-gray-400 mb-0.5">
|
||||||
{exchangeRate.base === 'KRW' ? '1,000' : '1'} {getCurrencySymbol(exchangeRate.base)} =
|
{exchangeRate.base === 'KRW' ? '1,000' : '1'} {getCurrencySymbol(exchangeRate.base)} =
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-bold text-gray-900 mb-1">
|
<div className="text-lg font-bold text-gray-900">
|
||||||
{exchangeRate.base === 'KRW'
|
{exchangeRate.base === 'KRW'
|
||||||
? (exchangeRate.rate * 1000).toLocaleString('ko-KR', {
|
? (exchangeRate.rate * 1000).toLocaleString('ko-KR', {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
|
|
@ -198,37 +226,47 @@ export default function ExchangeWidget({
|
||||||
maximumFractionDigits: 4,
|
maximumFractionDigits: 4,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">{getCurrencySymbol(exchangeRate.target)}</div>
|
<div className="text-xs text-gray-400 mt-0.5">{getCurrencySymbol(exchangeRate.target)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 계산기 입력 */}
|
||||||
|
<div className="bg-white rounded-lg border p-2">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
inputMode="numeric"
|
||||||
|
value={displayAmount || ""}
|
||||||
|
onChange={handleCalculatorInput}
|
||||||
|
placeholder="금액 직접 입력"
|
||||||
|
autoComplete="off"
|
||||||
|
className="flex-1 text-center text-sm font-semibold"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-gray-400 w-12">{base}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 계산 예시 */}
|
<div className="flex items-center justify-center gap-2">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-gray-300 to-transparent" />
|
||||||
<div className="bg-white rounded-lg border p-3">
|
<span className="text-xs text-gray-400">▼</span>
|
||||||
<div className="text-xs text-gray-500 mb-1">10,000 {base}</div>
|
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-gray-300 to-transparent" />
|
||||||
<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>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg border p-3">
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-xs text-gray-500 mb-1">100,000 {base}</div>
|
<div className="flex-1 text-center text-lg font-bold text-green-600 bg-green-50 border border-green-200 rounded px-2 py-1.5">
|
||||||
<div className="text-lg font-semibold text-gray-900">
|
{calculateResult().toLocaleString('ko-KR', {
|
||||||
{(100000 * (base === 'KRW' ? exchangeRate.rate : 1 / exchangeRate.rate)).toLocaleString('ko-KR', {
|
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
})}{' '}
|
})}
|
||||||
{target}
|
</div>
|
||||||
|
<span className="text-xs text-gray-400 w-12">{target}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 데이터 출처 */}
|
{/* 데이터 출처 */}
|
||||||
<div className="mt-4 pt-3 border-t text-center">
|
<div className="mt-3 pt-2 border-t text-center">
|
||||||
<p className="text-xs text-gray-400">출처: {exchangeRate.source}</p>
|
<p className="text-xs text-gray-400">출처: {exchangeRate.source}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Check,
|
Check,
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
|
Settings,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
|
|
@ -34,12 +35,38 @@ export default function WeatherWidget({
|
||||||
refreshInterval = 600000,
|
refreshInterval = 600000,
|
||||||
}: WeatherWidgetProps) {
|
}: WeatherWidgetProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const [selectedCity, setSelectedCity] = useState(city);
|
const [selectedCity, setSelectedCity] = useState(city);
|
||||||
const [weather, setWeather] = useState<WeatherData | null>(null);
|
const [weather, setWeather] = useState<WeatherData | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||||
|
|
||||||
|
// 표시할 날씨 정보 선택
|
||||||
|
const [selectedItems, setSelectedItems] = useState<string[]>([
|
||||||
|
'temperature',
|
||||||
|
'feelsLike',
|
||||||
|
'humidity',
|
||||||
|
'windSpeed',
|
||||||
|
'pressure',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 날씨 항목 정의
|
||||||
|
const weatherItems = [
|
||||||
|
{ id: 'temperature', label: '기온', icon: Sun },
|
||||||
|
{ id: 'feelsLike', label: '체감온도', icon: Sun },
|
||||||
|
{ id: 'humidity', label: '습도', icon: Droplets },
|
||||||
|
{ id: 'windSpeed', label: '풍속', icon: Wind },
|
||||||
|
{ id: 'pressure', label: '기압', icon: Gauge },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 항목 토글
|
||||||
|
const toggleItem = (itemId: string) => {
|
||||||
|
setSelectedItems((prev) =>
|
||||||
|
prev.includes(itemId) ? prev.filter((id) => id !== itemId) : [...prev, itemId]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 도시 목록 (전국 시/군/구 단위)
|
// 도시 목록 (전국 시/군/구 단위)
|
||||||
const cities = [
|
const cities = [
|
||||||
// 서울특별시 (25개 구)
|
// 서울특별시 (25개 구)
|
||||||
|
|
@ -278,9 +305,9 @@ export default function WeatherWidget({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg border p-6">
|
<div className="h-full bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg border p-4">
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
|
@ -334,6 +361,46 @@ export default function WeatherWidget({
|
||||||
: ''}
|
: ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[200px] p-3" align="end">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-sm font-semibold text-gray-900 mb-3">표시 항목</h4>
|
||||||
|
{weatherItems.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => toggleItem(item.id)}
|
||||||
|
className={cn(
|
||||||
|
'w-full flex items-center gap-2 px-2 py-1.5 rounded text-xs transition-colors',
|
||||||
|
selectedItems.includes(item.id)
|
||||||
|
? 'bg-blue-50 text-blue-700'
|
||||||
|
: 'text-gray-600 hover:bg-gray-50'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
'h-3.5 w-3.5',
|
||||||
|
selectedItems.includes(item.id) ? 'opacity-100' : 'opacity-0'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Icon className="h-3.5 w-3.5" />
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -345,59 +412,104 @@ export default function WeatherWidget({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 반응형 그리드 레이아웃 - 자동 조정 */}
|
||||||
|
<div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))' }}>
|
||||||
{/* 날씨 아이콘 및 온도 */}
|
{/* 날씨 아이콘 및 온도 */}
|
||||||
<div className="flex items-center justify-center mb-6">
|
<div className="bg-white/50 rounded-lg p-3">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-1.5">
|
||||||
{getWeatherIcon(weather.weatherMain)}
|
<div className="flex-shrink-0">
|
||||||
<div>
|
{(() => {
|
||||||
<div className="text-5xl font-bold text-gray-900">
|
const iconClass = "h-5 w-5";
|
||||||
|
switch (weather.weatherMain.toLowerCase()) {
|
||||||
|
case 'clear':
|
||||||
|
return <Sun className={`${iconClass} text-yellow-500`} />;
|
||||||
|
case 'clouds':
|
||||||
|
return <Cloud className={`${iconClass} text-gray-400`} />;
|
||||||
|
case 'rain':
|
||||||
|
case 'drizzle':
|
||||||
|
return <CloudRain className={`${iconClass} text-blue-500`} />;
|
||||||
|
case 'snow':
|
||||||
|
return <CloudSnow className={`${iconClass} text-blue-300`} />;
|
||||||
|
default:
|
||||||
|
return <Cloud className={`${iconClass} text-gray-400`} />;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="text-sm font-bold text-gray-900 leading-tight truncate">
|
||||||
{weather.temperature}°C
|
{weather.temperature}°C
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 capitalize">
|
<p className="text-xs text-gray-400 capitalize leading-tight truncate">
|
||||||
{weather.weatherDescription}
|
{weather.weatherDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 상세 정보 */}
|
{/* 기온 - 선택 가능 */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
{selectedItems.includes('temperature') && (
|
||||||
<div className="flex items-center gap-2 bg-white/50 rounded-lg p-3">
|
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
|
||||||
<Wind className="h-5 w-5 text-blue-500" />
|
<Sun className="h-3.5 w-3.5 text-orange-500 flex-shrink-0" />
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-xs text-gray-500">체감 온도</p>
|
<p className="text-xs text-gray-400 leading-tight truncate">기온</p>
|
||||||
<p className="text-sm font-semibold text-gray-900">
|
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
|
||||||
|
{weather.temperature}°C
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 체감 온도 */}
|
||||||
|
{selectedItems.includes('feelsLike') && (
|
||||||
|
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
|
||||||
|
<Wind className="h-3.5 w-3.5 text-blue-500 flex-shrink-0" />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-xs text-gray-400 leading-tight truncate">체감온도</p>
|
||||||
|
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
|
||||||
{weather.feelsLike}°C
|
{weather.feelsLike}°C
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/50 rounded-lg p-3">
|
)}
|
||||||
<Droplets className="h-5 w-5 text-blue-500" />
|
|
||||||
<div>
|
{/* 습도 */}
|
||||||
<p className="text-xs text-gray-500">습도</p>
|
{selectedItems.includes('humidity') && (
|
||||||
<p className="text-sm font-semibold text-gray-900">
|
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
|
||||||
|
<Droplets className="h-3.5 w-3.5 text-blue-500 flex-shrink-0" />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-xs text-gray-400 leading-tight truncate">습도</p>
|
||||||
|
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
|
||||||
{weather.humidity}%
|
{weather.humidity}%
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/50 rounded-lg p-3">
|
)}
|
||||||
<Wind className="h-5 w-5 text-blue-500" />
|
|
||||||
<div>
|
{/* 풍속 */}
|
||||||
<p className="text-xs text-gray-500">풍속</p>
|
{selectedItems.includes('windSpeed') && (
|
||||||
<p className="text-sm font-semibold text-gray-900">
|
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
|
||||||
|
<Wind className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-xs text-gray-400 leading-tight truncate">풍속</p>
|
||||||
|
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
|
||||||
{weather.windSpeed} m/s
|
{weather.windSpeed} m/s
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/50 rounded-lg p-3">
|
)}
|
||||||
<Gauge className="h-5 w-5 text-blue-500" />
|
|
||||||
<div>
|
{/* 기압 */}
|
||||||
<p className="text-xs text-gray-500">기압</p>
|
{selectedItems.includes('pressure') && (
|
||||||
<p className="text-sm font-semibold text-gray-900">
|
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
|
||||||
|
<Gauge className="h-3.5 w-3.5 text-purple-500 flex-shrink-0" />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-xs text-gray-400 leading-tight truncate">기압</p>
|
||||||
|
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
|
||||||
{weather.pressure} hPa
|
{weather.pressure} hPa
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue