develop #23

병합
htlee develop 에서 main 로 11 commits 를 머지했습니다 2026-02-17 07:15:24 +09:00
7개의 변경된 파일41개의 추가작업 그리고 108개의 파일을 삭제
Showing only changes of commit 2adcbc9a93 - Show all commits

파일 보기

@ -6,9 +6,8 @@
@import "./styles/base.css"; @import "./styles/base.css";
/* Components (layout.css, topbar.css → inline Tailwind) */ /* Components (layout/topbar/toggles → inline Tailwind + @wing/ui) */
@import "./styles/components/panels.css"; @import "./styles/components/panels.css";
@import "./styles/components/toggles.css";
@import "./styles/components/speed.css"; @import "./styles/components/speed.css";
@import "./styles/components/vessel-list.css"; @import "./styles/components/vessel-list.css";
@import "./styles/components/ais-list.css"; @import "./styles/components/ais-list.css";

파일 보기

@ -1,75 +0,0 @@
/* Type grid */
.tg {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3px;
}
.tb {
background: var(--card);
border: 1px solid transparent;
border-radius: 5px;
padding: 4px;
cursor: pointer;
text-align: center;
transition: all 0.15s;
user-select: none;
}
.tb:hover {
border-color: var(--border);
}
.tb.on {
border-color: var(--accent);
background: rgba(59, 130, 246, 0.1);
}
.tb .c {
font-size: 11px;
font-weight: 800;
}
.tb .n {
font-size: 8px;
color: var(--muted);
}
/* Toggles */
.tog {
display: flex;
gap: 3px;
flex-wrap: wrap;
margin-bottom: 6px;
}
.tog.tog-map {
/* Keep "지도 표시 설정" buttons in a predictable 2-row layout (4 columns). */
gap: 4px;
}
.tog.tog-map .tog-btn {
flex: 1 1 calc(25% - 4px);
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tog-btn {
font-size: 8px;
padding: 2px 6px;
border-radius: 3px;
border: 1px solid var(--border);
background: var(--card);
color: var(--muted);
cursor: pointer;
transition: all 0.15s;
user-select: none;
}
.tog-btn.on {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}

파일 보기

@ -1,3 +1,4 @@
import { ToggleButton } from '@wing/ui';
import type { Map3DSettings } from "../../widgets/map3d/Map3D"; import type { Map3DSettings } from "../../widgets/map3d/Map3D";
type Props = { type Props = {
@ -13,11 +14,11 @@ export function Map3DSettingsToggles({ value, onToggle }: Props) {
]; ];
return ( return (
<div className="tog"> <div className="flex flex-wrap gap-0.75 mb-1.5">
{items.map((t) => ( {items.map((t) => (
<div key={t.id} className={`tog-btn ${value[t.id] ? "on" : ""}`} onClick={() => onToggle(t.id)}> <ToggleButton key={t.id} on={value[t.id]} onClick={() => onToggle(t.id)}>
{t.label} {t.label}
</div> </ToggleButton>
))} ))}
</div> </div>
); );

파일 보기

@ -146,8 +146,7 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) {
<div className="ms-label" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <div className="ms-label" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span <span
className={`tog-btn${autoGradient ? ' on' : ''}`} className={`ml-2 cursor-pointer rounded border px-1.5 py-px text-[8px] transition-all duration-150 select-none ${autoGradient ? 'border-wing-accent bg-wing-accent text-white' : 'border-wing-border bg-wing-card text-wing-muted'}`}
style={{ fontSize: 8, padding: '1px 5px', marginLeft: 8 }}
onClick={toggleAutoGradient} onClick={toggleAutoGradient}
title="최소/최대 색상 기준으로 중간 구간을 자동 보간합니다" title="최소/최대 색상 기준으로 중간 구간을 자동 보간합니다"
> >
@ -176,11 +175,11 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) {
{/* ── Depth font size ───────────────────────────── */} {/* ── Depth font size ───────────────────────────── */}
<div className="ms-section"> <div className="ms-section">
<div className="ms-label"> </div> <div className="ms-label"> </div>
<div className="tog" style={{ gap: 3 }}> <div className="flex flex-wrap gap-0.75">
{FONT_SIZES.map((fs) => ( {FONT_SIZES.map((fs) => (
<div <div
key={fs.value} key={fs.value}
className={`tog-btn${value.depthFontSize === fs.value ? ' on' : ''}`} className={`cursor-pointer rounded border px-1.5 py-0.5 text-[8px] transition-all duration-150 select-none ${value.depthFontSize === fs.value ? 'border-wing-accent bg-wing-accent text-white' : 'border-wing-border bg-wing-card text-wing-muted'}`}
onClick={() => update('depthFontSize', fs.value)} onClick={() => update('depthFontSize', fs.value)}
> >
{fs.label} {fs.label}

파일 보기

@ -1,3 +1,5 @@
import { ToggleButton } from '@wing/ui';
export type MapToggleState = { export type MapToggleState = {
pairLines: boolean; pairLines: boolean;
pairRange: boolean; pairRange: boolean;
@ -27,11 +29,16 @@ export function MapToggles({ value, onToggle }: Props) {
]; ];
return ( return (
<div className="tog tog-map"> <div className="flex flex-wrap gap-1">
{items.map((t) => ( {items.map((t) => (
<div key={t.id} className={`tog-btn ${value[t.id] ? "on" : ""}`} onClick={() => onToggle(t.id)}> <ToggleButton
key={t.id}
on={value[t.id]}
onClick={() => onToggle(t.id)}
className="flex-[1_1_calc(25%-4px)] overflow-hidden text-center text-ellipsis whitespace-nowrap"
>
{t.label} {t.label}
</div> </ToggleButton>
))} ))}
</div> </div>
); );

파일 보기

@ -9,26 +9,26 @@ type Props = {
onToggleAll: () => void; onToggleAll: () => void;
}; };
const TB = "cursor-pointer rounded-[5px] border p-1 text-center transition-all duration-150 select-none";
const TB_ON = "border-wing-accent bg-wing-accent/10";
const TB_OFF = "border-transparent bg-wing-card hover:border-wing-border";
export function TypeFilterGrid({ enabled, totalCount, countsByType, onToggle, onToggleAll }: Props) { export function TypeFilterGrid({ enabled, totalCount, countsByType, onToggle, onToggleAll }: Props) {
const allOn = VESSEL_TYPE_ORDER.every((c) => enabled[c]); const allOn = VESSEL_TYPE_ORDER.every((c) => enabled[c]);
return ( return (
<div className="tg"> <div className="grid grid-cols-3 gap-0.75">
<div className={`tb ${allOn ? "on" : ""}`} onClick={onToggleAll} style={{ gridColumn: "1/-1" }}> <div className={`col-span-full ${TB} ${allOn ? TB_ON : TB_OFF}`} onClick={onToggleAll}>
<div className="c" style={{ color: "var(--accent)" }}> <div className="text-[11px] font-extrabold text-wing-accent"></div>
<div className="text-[8px] text-wing-muted">{totalCount}</div>
</div>
<div className="n">{totalCount}</div>
</div> </div>
{VESSEL_TYPE_ORDER.map((code) => { {VESSEL_TYPE_ORDER.map((code) => {
const t = VESSEL_TYPES[code]; const t = VESSEL_TYPES[code];
const cnt = countsByType[code] ?? 0; const cnt = countsByType[code] ?? 0;
return ( return (
<div key={code} className={`tb ${enabled[code] ? "on" : ""}`} onClick={() => onToggle(code)}> <div key={code} className={`${TB} ${enabled[code] ? TB_ON : TB_OFF}`} onClick={() => onToggle(code)}>
<div className="c" style={{ color: t.color }}> <div className="text-[11px] font-extrabold" style={{ color: t.color }}>{code}</div>
{code} <div className="text-[8px] text-wing-muted">{cnt}</div>
</div>
<div className="n">{cnt}</div>
</div> </div>
); );
})} })}

파일 보기

@ -1,3 +1,4 @@
import { ToggleButton } from '@wing/ui';
import type { AisTarget } from '../../entities/aisTarget/model/types'; import type { AisTarget } from '../../entities/aisTarget/model/types';
import type { LegacyVesselIndex } from '../../entities/legacyVessel/lib'; import type { LegacyVesselIndex } from '../../entities/legacyVessel/lib';
import type { LegacyVesselDataset, LegacyVesselInfo } from '../../entities/legacyVessel/model/types'; import type { LegacyVesselDataset, LegacyVesselInfo } from '../../entities/legacyVessel/model/types';
@ -97,9 +98,9 @@ export function DashboardSidebar({
<div className="flex flex-col overflow-y-auto border-r border-wing-border bg-wing-surface max-md:hidden"> <div className="flex flex-col overflow-y-auto border-r border-wing-border bg-wing-surface max-md:hidden">
<div className="sb"> <div className="sb">
<div className="sb-t"> </div> <div className="sb-t"> </div>
<div className="tog"> <div className="flex flex-wrap gap-0.75 mb-1.5">
<div <ToggleButton
className={`tog-btn ${showTargets ? 'on' : ''}`} on={showTargets}
onClick={() => { onClick={() => {
setShowTargets((v) => { setShowTargets((v) => {
const next = !v; const next = !v;
@ -110,10 +111,10 @@ export function DashboardSidebar({
title="레거시(CN permit) 대상 선박 표시" title="레거시(CN permit) 대상 선박 표시"
> >
</div> </ToggleButton>
<div className={`tog-btn ${showOthers ? 'on' : ''}`} onClick={() => setShowOthers((v) => !v)} title="대상 외 AIS 선박 표시"> <ToggleButton on={showOthers} onClick={() => setShowOthers((v) => !v)} title="대상 외 AIS 선박 표시">
AIS AIS
</div> </ToggleButton>
</div> </div>
<TypeFilterGrid <TypeFilterGrid
enabled={typeEnabled} enabled={typeEnabled}
@ -138,14 +139,15 @@ export function DashboardSidebar({
<div className="sb-t" style={{ display: 'flex', alignItems: 'center' }}> <div className="sb-t" style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ flex: 1 }} /> <div style={{ flex: 1 }} />
<div <ToggleButton
className={`tog-btn ${projection === 'globe' ? 'on' : ''}${isProjectionToggleDisabled ? ' disabled' : ''}`} on={projection === 'globe'}
onClick={isProjectionToggleDisabled ? undefined : () => setProjection((p) => (p === 'globe' ? 'mercator' : 'globe'))} onClick={isProjectionToggleDisabled ? undefined : () => setProjection((p) => (p === 'globe' ? 'mercator' : 'globe'))}
title={isProjectionToggleDisabled ? '3D 모드 준비 중...' : '3D 지구본 투영: 드래그로 회전, 휠로 확대/축소'} title={isProjectionToggleDisabled ? '3D 모드 준비 중...' : '3D 지구본 투영: 드래그로 회전, 휠로 확대/축소'}
style={{ fontSize: 9, padding: '2px 8px', opacity: isProjectionToggleDisabled ? 0.4 : 1, cursor: isProjectionToggleDisabled ? 'not-allowed' : 'pointer' }} className="px-2 py-0.5 text-[9px]"
style={{ opacity: isProjectionToggleDisabled ? 0.4 : 1, cursor: isProjectionToggleDisabled ? 'not-allowed' : 'pointer' }}
> >
3D 3D
</div> </ToggleButton>
</div> </div>
<MapToggles value={overlays} onToggle={(k) => setOverlays((prev) => ({ ...prev, [k]: !prev[k] }))} /> <MapToggles value={overlays} onToggle={(k) => setOverlays((prev) => ({ ...prev, [k]: !prev[k] }))} />
</div> </div>