wing-ops/frontend/src/tabs/weather/components/WeatherRightPanel.tsx
Nan Kyung Lee f5bcbde40e style(weather): 기상정보 패널 글자 크기 전체 1단계 키움
- 라벨 7px→10px, 본문 9px→10px, 섹션제목 9px→10px
- 핵심 지표 숫자 18px→22px
- 헤더 11px→12px

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 08:42:36 +09:00

256 lines
13 KiB
TypeScript
Executable File
Raw Blame 히스토리

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

interface WeatherData {
stationName: string
location: { lat: number; lon: number }
currentTime: string
wind: {
speed: number
direction: number
speed_1k: number
speed_3k: number
}
wave: {
height: number
period: number
}
temperature: {
current: number
feelsLike: number
}
pressure: number
visibility: number
forecast: WeatherForecast[]
}
interface WeatherForecast {
time: string
hour: string
icon: string
temperature: number
windSpeed: number
}
interface WeatherRightPanelProps {
weatherData: WeatherData | null
}
/** 풍속 등급 색상 */
function windColor(speed: number): string {
if (speed >= 14) return '#ef4444'
if (speed >= 10) return '#f97316'
if (speed >= 6) return '#eab308'
return '#22c55e'
}
/** 파고 등급 색상 */
function waveColor(height: number): string {
if (height >= 3) return '#ef4444'
if (height >= 2) return '#f97316'
if (height >= 1) return '#eab308'
return '#22c55e'
}
/** 풍향 텍스트 */
function windDirText(deg: number): string {
const dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
return dirs[Math.round(deg / 22.5) % 16]
}
export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
if (!weatherData) {
return (
<div className="flex flex-col bg-bg-1 border-l border-border overflow-hidden w-[320px] shrink-0">
<div className="p-6 text-center">
<p className="text-text-3 text-xs font-korean"> </p>
</div>
</div>
)
}
const sunriseTime = '07:12'
const sunsetTime = '17:58'
const moonrise = '19:35'
const moonset = '01:50'
const windDir = windDirText(weatherData.wind.direction)
const wSpd = Number(weatherData.wind.speed)
const wHgt = Number(weatherData.wave.height)
const wTemp = Number(weatherData.temperature.current)
return (
<div className="flex flex-col bg-bg-1 border-l border-border overflow-hidden w-[320px] shrink-0">
{/* 헤더 */}
<div className="px-4 py-3 border-b border-border bg-bg-2">
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-bold text-primary-cyan font-korean">📍 {weatherData.stationName}</span>
<span className="px-1.5 py-px text-[10px] rounded bg-primary-cyan/15 text-primary-cyan font-bold"></span>
</div>
<p className="text-[10px] text-text-3 font-mono">
{weatherData.location.lat.toFixed(2)}°N, {weatherData.location.lon.toFixed(2)}°E · {weatherData.currentTime}
</p>
</div>
{/* 스크롤 콘텐츠 */}
<div className="flex-1 overflow-auto" style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{/* ── 핵심 지표 3칸 카드 ── */}
<div className="grid grid-cols-3 gap-1 px-3 py-2.5">
<div className="text-center py-2.5 bg-bg-0 border border-border rounded-md">
<div className="text-[22px] font-bold font-mono" style={{ color: windColor(wSpd) }}>{wSpd.toFixed(1)}</div>
<div className="text-[10px] text-text-3 font-korean"> (m/s)</div>
</div>
<div className="text-center py-2.5 bg-bg-0 border border-border rounded-md">
<div className="text-[22px] font-bold font-mono" style={{ color: waveColor(wHgt) }}>{wHgt.toFixed(1)}</div>
<div className="text-[10px] text-text-3 font-korean"> (m)</div>
</div>
<div className="text-center py-2.5 bg-bg-0 border border-border rounded-md">
<div className="text-[22px] font-bold font-mono text-primary-cyan">{wTemp.toFixed(1)}</div>
<div className="text-[10px] text-text-3 font-korean"> (°C)</div>
</div>
</div>
{/* ── 바람 상세 ── */}
<div className="px-3 py-2 border-b border-border">
<div className="text-[10px] font-bold text-text-3 font-korean mb-2">🌬 </div>
<div className="flex items-center gap-3 mb-2">
{/* 풍향 컴파스 */}
<div className="relative w-[50px] h-[50px] shrink-0">
<svg viewBox="0 0 50 50" className="w-full h-full">
<circle cx="25" cy="25" r="22" fill="none" stroke="var(--bd)" strokeWidth="1" />
<circle cx="25" cy="25" r="16" fill="none" stroke="var(--bd)" strokeWidth="0.5" strokeDasharray="2 2" />
{['N', 'E', 'S', 'W'].map((d, i) => {
const angle = i * 90
const rad = (angle - 90) * Math.PI / 180
const x = 25 + 20 * Math.cos(rad)
const y = 25 + 20 * Math.sin(rad)
return <text key={d} x={x} y={y} textAnchor="middle" dominantBaseline="central" fill="var(--t3)" fontSize="6" fontWeight="bold">{d}</text>
})}
{/* 풍향 화살표 */}
<line
x1="25" y1="25"
x2={25 + 14 * Math.sin(weatherData.wind.direction * Math.PI / 180)}
y2={25 - 14 * Math.cos(weatherData.wind.direction * Math.PI / 180)}
stroke={windColor(wSpd)} strokeWidth="2" strokeLinecap="round"
/>
<circle cx="25" cy="25" r="3" fill={windColor(wSpd)} />
</svg>
</div>
<div className="flex-1 grid grid-cols-2 gap-x-3 gap-y-1 text-[10px]">
<div className="flex justify-between"><span className="text-text-3"></span><span className="text-text-1 font-mono font-bold">{windDir} {weatherData.wind.direction}°</span></div>
<div className="flex justify-between"><span className="text-text-3"></span><span className="text-text-1 font-mono">{weatherData.pressure} hPa</span></div>
<div className="flex justify-between"><span className="text-text-3">1k </span><span className="font-mono" style={{ color: windColor(weatherData.wind.speed_1k) }}>{Number(weatherData.wind.speed_1k).toFixed(1)}</span></div>
<div className="flex justify-between"><span className="text-text-3">3k </span><span className="font-mono" style={{ color: windColor(weatherData.wind.speed_3k) }}>{Number(weatherData.wind.speed_3k).toFixed(1)}</span></div>
<div className="col-span-2 flex justify-between"><span className="text-text-3"></span><span className="text-text-1 font-mono">{weatherData.visibility} km</span></div>
</div>
</div>
{/* 풍속 게이지 바 */}
<div className="flex items-center gap-2">
<div className="flex-1 h-[5px] bg-bg-3 rounded-full overflow-hidden">
<div className="h-full rounded-full transition-all" style={{ width: `${Math.min(wSpd / 20 * 100, 100)}%`, background: windColor(wSpd) }} />
</div>
<span className="text-[10px] font-mono text-text-3 shrink-0">{wSpd.toFixed(1)}/20</span>
</div>
</div>
{/* ── 파도 상세 ── */}
<div className="px-3 py-2 border-b border-border">
<div className="text-[10px] font-bold text-text-3 font-korean mb-2">🌊 </div>
<div className="grid grid-cols-4 gap-1 text-[10px]">
<div className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="font-bold font-mono" style={{ color: waveColor(wHgt) }}>{wHgt.toFixed(1)}m</div>
<div className="text-[10px] text-text-3"></div>
</div>
<div className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="font-bold font-mono text-status-red">{(wHgt * 1.6).toFixed(1)}m</div>
<div className="text-[10px] text-text-3"></div>
</div>
<div className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="font-bold font-mono text-primary-cyan">{weatherData.wave.period}s</div>
<div className="text-[10px] text-text-3"></div>
</div>
<div className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="font-bold font-mono text-text-1">NW</div>
<div className="text-[10px] text-text-3"></div>
</div>
</div>
{/* 파고 게이지 바 */}
<div className="flex items-center gap-2 mt-1.5">
<div className="flex-1 h-[5px] bg-bg-3 rounded-full overflow-hidden">
<div className="h-full rounded-full transition-all" style={{ width: `${Math.min(wHgt / 5 * 100, 100)}%`, background: waveColor(wHgt) }} />
</div>
<span className="text-[10px] font-mono text-text-3 shrink-0">{wHgt.toFixed(1)}/5m</span>
</div>
</div>
{/* ── 수온/공기 ── */}
<div className="px-3 py-2 border-b border-border">
<div className="text-[10px] font-bold text-text-3 font-korean mb-2">🌡 · </div>
<div className="grid grid-cols-3 gap-1 text-[10px]">
<div className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="font-bold font-mono text-primary-cyan">{wTemp.toFixed(1)}°</div>
<div className="text-[10px] text-text-3"></div>
</div>
<div className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="font-bold font-mono text-text-1">2.1°</div>
<div className="text-[10px] text-text-3"></div>
</div>
<div className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="font-bold font-mono text-text-1">31.2</div>
<div className="text-[10px] text-text-3">(PSU)</div>
</div>
</div>
</div>
{/* ── 시간별 예보 ── */}
<div className="px-3 py-2 border-b border-border">
<div className="text-[10px] font-bold text-text-3 font-korean mb-2"> </div>
<div className="grid grid-cols-5 gap-1">
{weatherData.forecast.map((f, i) => (
<div key={i} className="flex flex-col items-center py-1.5 px-1 bg-bg-0 border border-border rounded">
<span className="text-[10px] text-text-3 mb-0.5">{f.hour}</span>
<span className="text-base mb-0.5">{f.icon}</span>
<span className="text-[10px] font-bold text-text-1">{f.temperature}°</span>
<span className="text-[10px] text-text-3 font-mono">{f.windSpeed}</span>
</div>
))}
</div>
</div>
{/* ── 천문/조석 ── */}
<div className="px-3 py-2 border-b border-border">
<div className="text-[10px] font-bold text-text-3 font-korean mb-2"> · </div>
<div className="grid grid-cols-4 gap-1 text-[10px]">
{[
{ icon: '🌅', label: '일출', value: sunriseTime },
{ icon: '🌄', label: '일몰', value: sunsetTime },
{ icon: '🌙', label: '월출', value: moonrise },
{ icon: '🌜', label: '월몰', value: moonset },
].map((item, i) => (
<div key={i} className="text-center py-1.5 bg-bg-0 border border-border rounded">
<div className="text-sm mb-0.5">{item.icon}</div>
<div className="text-[10px] text-text-3">{item.label}</div>
<div className="font-bold font-mono text-text-1">{item.value}</div>
</div>
))}
</div>
<div className="flex items-center gap-2 mt-1.5 px-2 py-1 bg-bg-0 border border-border rounded text-[10px]">
<span className="text-sm">🌓</span>
<span className="text-text-3"> 14</span>
<span className="ml-auto text-text-1 font-mono"> 6.7m</span>
</div>
</div>
{/* ── 날씨 특보 ── */}
<div className="px-3 py-2">
<div className="text-[10px] font-bold text-text-3 font-korean mb-2">🚨 </div>
<div className="px-2.5 py-2 rounded border" style={{ background: 'rgba(239,68,68,.06)', borderColor: 'rgba(239,68,68,.2)' }}>
<div className="flex items-center gap-2 text-[10px]">
<span className="px-1.5 py-px rounded text-[10px] font-bold" style={{ background: 'rgba(239,68,68,.15)', color: 'var(--red)' }}></span>
<span className="text-text-1 font-korean"> 08:00~</span>
</div>
</div>
</div>
</div>
</div>
)
}