feat: Tailwind CSS v4 + @wing/ui 디자인 시스템 #21
@ -5,10 +5,8 @@
|
|||||||
@source "../../../../packages/ui/src/**/*.{ts,tsx}";
|
@source "../../../../packages/ui/src/**/*.{ts,tsx}";
|
||||||
|
|
||||||
@import "./styles/base.css";
|
@import "./styles/base.css";
|
||||||
@import "./styles/layout.css";
|
|
||||||
|
|
||||||
/* Components */
|
/* Components (layout.css, topbar.css → inline Tailwind) */
|
||||||
@import "./styles/components/topbar.css";
|
|
||||||
@import "./styles/components/panels.css";
|
@import "./styles/components/panels.css";
|
||||||
@import "./styles/components/toggles.css";
|
@import "./styles/components/toggles.css";
|
||||||
@import "./styles/components/speed.css";
|
@import "./styles/components/speed.css";
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
/* Google Fonts loaded via index.html <link> for optimal performance */
|
/* Google Fonts: loaded via index.html <link> */
|
||||||
|
/* CSS Reset: handled by Tailwind preflight */
|
||||||
* {
|
/* CSS Variables: defined in @wing/ui/theme/tokens.css */
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CSS variables are now defined in @wing/ui/theme/tokens.css */
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
.topbar {
|
|
||||||
grid-column: 1/-1;
|
|
||||||
background: var(--panel);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 14px;
|
|
||||||
gap: 10px;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar .logo {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 800;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar .logo span {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar .stats {
|
|
||||||
display: flex;
|
|
||||||
gap: 14px;
|
|
||||||
margin-left: auto;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar .stat {
|
|
||||||
font-size: 10px;
|
|
||||||
color: var(--muted);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar .stat b {
|
|
||||||
color: var(--text);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar .time {
|
|
||||||
font-size: 10px;
|
|
||||||
color: var(--accent);
|
|
||||||
font-weight: 600;
|
|
||||||
margin-left: 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-user {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-left: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-user__name {
|
|
||||||
font-size: 10px;
|
|
||||||
color: var(--text);
|
|
||||||
font-weight: 500;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-user__logout {
|
|
||||||
font-size: 9px;
|
|
||||||
color: var(--muted);
|
|
||||||
background: none;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 2px 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-user__logout:hover {
|
|
||||||
color: var(--text);
|
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
.app {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 310px 1fr;
|
|
||||||
grid-template-rows: 44px 1fr;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
background: var(--panel);
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
overflow-y: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-area {
|
|
||||||
position: relative;
|
|
||||||
background: #010610;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 920px) {
|
|
||||||
.app {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: 44px 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -245,7 +245,7 @@ export function DashboardPage() {
|
|||||||
|
|
||||||
// ── Render ──
|
// ── Render ──
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="grid h-screen grid-cols-[310px_1fr] grid-rows-[44px_1fr] max-md:grid-cols-[1fr]">
|
||||||
<Topbar
|
<Topbar
|
||||||
total={legacyVesselsAll.length}
|
total={legacyVesselsAll.length}
|
||||||
fishing={fishingCount}
|
fishing={fishingCount}
|
||||||
@ -285,7 +285,7 @@ export function DashboardPage() {
|
|||||||
legacyIndex={legacyIndex}
|
legacyIndex={legacyIndex}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="map-area">
|
<div className="relative bg-[#010610]">
|
||||||
{showMapLoader ? (
|
{showMapLoader ? (
|
||||||
<div className="map-loader-overlay" role="status" aria-live="polite">
|
<div className="map-loader-overlay" role="status" aria-live="polite">
|
||||||
<div className="map-loader-overlay__panel">
|
<div className="map-loader-overlay__panel">
|
||||||
|
|||||||
@ -94,7 +94,7 @@ export function DashboardSidebar({
|
|||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar">
|
<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="tog">
|
||||||
|
|||||||
@ -17,43 +17,52 @@ export function Topbar({ total, fishing, transit, pairLinks, alarms, pollingStat
|
|||||||
const statusColor =
|
const statusColor =
|
||||||
pollingStatus === "ready" ? "#22C55E" : pollingStatus === "loading" ? "#F59E0B" : pollingStatus === "error" ? "#EF4444" : "var(--muted)";
|
pollingStatus === "ready" ? "#22C55E" : pollingStatus === "loading" ? "#F59E0B" : pollingStatus === "error" ? "#EF4444" : "var(--muted)";
|
||||||
return (
|
return (
|
||||||
<div className="topbar">
|
<div className="col-span-full flex items-center gap-2.5 border-b border-wing-border bg-wing-surface px-3.5 z-[1000]">
|
||||||
<div className="logo" onClick={onLogoClick} style={{ cursor: onLogoClick ? "pointer" : undefined }} title={adminMode ? "ADMIN" : undefined}>
|
<div
|
||||||
🛰 <span>WING</span> 조업감시·선단연관 {adminMode ? <span style={{ fontSize: 10, color: "#F59E0B" }}>(ADMIN)</span> : null}
|
className="flex items-center gap-1.5 whitespace-nowrap text-sm font-extrabold"
|
||||||
|
onClick={onLogoClick}
|
||||||
|
style={{ cursor: onLogoClick ? "pointer" : undefined }}
|
||||||
|
title={adminMode ? "ADMIN" : undefined}
|
||||||
|
>
|
||||||
|
🛰 <span className="text-wing-accent">WING</span> 조업감시·선단연관{" "}
|
||||||
|
{adminMode ? <span className="text-[10px] text-wing-warning">(ADMIN)</span> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="stats">
|
<div className="ml-auto flex flex-wrap justify-end gap-3.5">
|
||||||
<div className="stat">
|
<div className="flex items-center gap-1 text-[10px] text-wing-muted">
|
||||||
DATA <b style={{ color: "#22C55E" }}>API</b>
|
DATA <b style={{ color: "#22C55E" }}>API</b>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="flex items-center gap-1 text-[10px] text-wing-muted">
|
||||||
POLL{" "}
|
POLL{" "}
|
||||||
<b style={{ color: statusColor }}>
|
<b style={{ color: statusColor }}>
|
||||||
{pollingStatus.toUpperCase()}
|
{pollingStatus.toUpperCase()}
|
||||||
{lastFetchMinutes ? `(${lastFetchMinutes}m)` : ""}
|
{lastFetchMinutes ? `(${lastFetchMinutes}m)` : ""}
|
||||||
</b>
|
</b>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="flex items-center gap-1 text-[10px] text-wing-muted">
|
||||||
전체 <b>{total}</b>척
|
전체 <b className="text-xs text-wing-text">{total}</b>척
|
||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="flex items-center gap-1 text-[10px] text-wing-muted">
|
||||||
조업 <b style={{ color: "#22C55E" }}>{fishing}</b>
|
조업 <b style={{ color: "#22C55E" }}>{fishing}</b>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="flex items-center gap-1 text-[10px] text-wing-muted">
|
||||||
항해 <b style={{ color: "#3B82F6" }}>{transit}</b>
|
항해 <b style={{ color: "#3B82F6" }}>{transit}</b>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="flex items-center gap-1 text-[10px] text-wing-muted">
|
||||||
쌍연결 <b style={{ color: "#F59E0B" }}>{pairLinks}</b>
|
쌍연결 <b style={{ color: "#F59E0B" }}>{pairLinks}</b>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="flex items-center gap-1 text-[10px] text-wing-muted">
|
||||||
경고 <b style={{ color: "#EF4444" }}>{alarms}</b>
|
경고 <b style={{ color: "#EF4444" }}>{alarms}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="time">{clock}</div>
|
<div className="ml-2.5 whitespace-nowrap text-[10px] font-semibold text-wing-accent">{clock}</div>
|
||||||
{userName && (
|
{userName && (
|
||||||
<div className="topbar-user">
|
<div className="ml-2.5 flex shrink-0 items-center gap-2">
|
||||||
<span className="topbar-user__name">{userName}</span>
|
<span className="whitespace-nowrap text-[10px] font-medium text-wing-text">{userName}</span>
|
||||||
{onLogout && (
|
{onLogout && (
|
||||||
<button className="topbar-user__logout" onClick={onLogout}>
|
<button
|
||||||
|
className="cursor-pointer whitespace-nowrap rounded border border-wing-border bg-transparent px-1.5 py-0.5 text-[9px] text-wing-muted transition-all duration-150 hover:border-wing-accent hover:text-wing-text"
|
||||||
|
onClick={onLogout}
|
||||||
|
>
|
||||||
로그아웃
|
로그아웃
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user