feat: RoutePanel 시점/종점 표시 및 교량/터널만 표시

- 상단 시점, 하단 종점 측점명 고정 표시
- POI는 교량/터널만 필터링 (터널: 보라, 교량: 하늘색)
- 불필요한 측점 도트/텍스트 제거

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-01 16:20:28 +09:00
parent 1f90af20cc
commit 8c475c9ee8

View File

@@ -225,6 +225,14 @@ export default function RoutePanel({ currentTime, visible, onSeek }: RoutePanelP
const maxKm = Math.max(...allKms);
const kmToY = (km: number) => (1 - (km - minKm) / (maxKm - minKm)) * 100;
// 시점/종점 측점
const sortedByKm = [...validStations].sort((a, b) => stationKm(a.title) - stationKm(b.title));
const startStation = sortedByKm[0];
const endStation = sortedByKm[sortedByKm.length - 1];
// 교량/터널만 표시
const filteredPois = pois.filter(p => p.category === '\uD130\uB110' || p.category === '\uAD50\uB7C9');
return (
<div
ref={panelRef}
@@ -233,49 +241,56 @@ export default function RoutePanel({ currentTime, visible, onSeek }: RoutePanelP
>
{/* Center vertical line */}
<div
className="absolute top-0 bottom-0"
style={{ left: 36, width: 2, background: 'rgba(255,255,255,0.3)' }}
className="absolute"
style={{ left: 36, width: 2, top: 20, bottom: 20, background: 'rgba(255,255,255,0.3)' }}
/>
{/* Stations */}
{validStations.map((st, i) => {
const km = stationKm(st.title);
if (km < 0) return null;
return (
<div
key={`st-${i}`}
className="absolute flex items-center"
style={{ top: `${kmToY(km)}%`, transform: 'translateY(-50%)', left: 0, right: 0 }}
>
<div className="text-[9px] text-white/70 text-right" style={{ width: 34 }}>
{st.title}
</div>
<div style={{ position: 'absolute', left: 33, width: 8, display: 'flex', justifyContent: 'center' }}>
<div className="w-2 h-2 rounded-full bg-white/60" />
</div>
</div>
);
})}
{/* 시점 label — top */}
<div className="absolute left-0 right-0 flex items-center" style={{ top: 4 }}>
<div className="text-[9px] text-white/50 text-right" style={{ width: 34 }}>
{startStation.title}
</div>
<div style={{ position: 'absolute', left: 30, width: 12, display: 'flex', justifyContent: 'center' }}>
<div className="w-2 h-2 rounded-full bg-white/50" />
</div>
<div className="text-[8px] text-white/40 ml-1" style={{ position: 'absolute', left: 44 }}></div>
</div>
{/* POIs */}
{pois.map((poi, i) => {
{/* 종점 label — bottom */}
<div className="absolute left-0 right-0 flex items-center" style={{ bottom: 4 }}>
<div className="text-[9px] text-white/50 text-right" style={{ width: 34 }}>
{endStation.title}
</div>
<div style={{ position: 'absolute', left: 30, width: 12, display: 'flex', justifyContent: 'center' }}>
<div className="w-2 h-2 rounded-full bg-white/50" />
</div>
<div className="text-[8px] text-white/40" style={{ position: 'absolute', left: 44 }}></div>
</div>
{/* 교량/터널 POIs */}
{filteredPois.map((poi, i) => {
const km = poiKm(poi, validStations);
if (km < 0) return null;
const y = kmToY(km);
if (y < 5 || y > 95) return null; // 시점/종점 영역과 겹치지 않게
return (
<div
key={`poi-${i}`}
className="absolute flex items-center"
style={{ top: `${kmToY(km)}%`, transform: 'translateY(-50%)', left: 0, right: 0 }}
className="absolute flex items-center pointer-events-none"
style={{ top: `${y}%`, transform: 'translateY(-50%)', left: 0, right: 0 }}
>
<div className="text-[11px] text-right" style={{ width: 34 }}>
{CATEGORY_EMOJI[poi.category] || '\uD83D\uDCCD'}
</div>
<div style={{ position: 'absolute', left: 33, width: 8, display: 'flex', justifyContent: 'center' }}>
<div className="w-1.5 h-1.5 rounded-full bg-cyan-400/70" />
<div style={{ position: 'absolute', left: 30, width: 12, display: 'flex', justifyContent: 'center' }}>
<div
className="w-2.5 h-2.5 rounded-sm"
style={{ background: poi.category === '\uD130\uB110' ? '#6366f1' : '#0ea5e9' }}
/>
</div>
<div
className="text-[8px] text-cyan-300/80 truncate"
style={{ position: 'absolute', left: 44, right: 2 }}
className="text-[8px] truncate"
style={{
position: 'absolute', left: 44, right: 2,
color: poi.category === '\uD130\uB110' ? '#a5b4fc' : '#7dd3fc',
}}
>
{poi.title}
</div>