akefin-design-system/ui_kits/web/Chrome.jsx

109 lines
4.5 KiB
JavaScript

// Chrome — top header strip + left navigation rail
function EntityScopeSwitcher({ value, onChange }) {
const entities = window.AKEFIN_DATA.entities;
const [open, setOpen] = React.useState(false);
const current = entities.find(e => e.id === value);
return (
<div className={`scope-switcher ${open ? "open" : ""}`}>
<button className="scope-trigger" onClick={() => setOpen(!open)}>
<span className="scope-stripe" style={{ background: current.color || "#1A1814" }}></span>
<span className="scope-label">SCOPE</span>
<span className="scope-name">{current.label}</span>
<Icon name="chevDown" size={14} />
</button>
{open && (
<div className="scope-menu">
{entities.map(e => (
<button key={e.id} className={`scope-item ${e.id === value ? "active" : ""}`}
onClick={() => { onChange(e.id); setOpen(false); }}>
<span className="scope-stripe" style={{ background: e.color || "#B5AE9F" }}></span>
<span>{e.label}</span>
{e.id === value && <Icon name="check" size={12} />}
</button>
))}
</div>
)}
</div>
);
}
function Header({ entity, onEntityChange, query, setQuery, statusLine, onOpenPalette, onImport }) {
return (
<header className="ak-header">
<div className="ak-logo">
<svg viewBox="0 0 48 48" width="22" height="22" aria-hidden="true">
<g fill="currentColor">
<rect x="6" y="14" width="6" height="28"/>
<rect x="12" y="8" width="6" height="6"/>
<rect x="18" y="8" width="6" height="6"/>
<rect x="24" y="14" width="6" height="28"/>
<rect x="12" y="26" width="12" height="6"/>
</g>
<rect x="36" y="36" width="6" height="6" fill="#2F7D55"/>
</svg>
<span className="ak-wordmark">akefin</span>
</div>
<EntityScopeSwitcher value={entity} onChange={onEntityChange} />
<button className="ak-search ak-search-btn" onClick={onOpenPalette}>
<Icon name="search" size={14} color="var(--fg-muted)" />
<span className="ak-search-placeholder">Search · run command · jump to anything</span>
<span className="ak-search-kbd">K</span>
</button>
<div className="ak-header-actions">
<button className="ak-header-btn" onClick={onImport}>
<Icon name="import" size={13} />
<span>IMPORT CSV</span>
</button>
</div>
<div className="ak-status">
<span className="dot" style={{ background: "var(--conf-rules)" }}></span>
<span className="ak-status-text">{statusLine}</span>
</div>
</header>
);
}
function Sidebar({ screen, setScreen, counts }) {
const items = [
{ id: "dashboard", label: "Overview", icon: "spark", count: null },
{ id: "review", label: "Review queue", icon: "activity", count: counts.review },
{ id: "rules", label: "Rules", icon: "rule", count: counts.rules },
{ id: "ledger", label: "Ledger", icon: "ledger", count: null },
{ id: "import", label: "Import", icon: "import", count: counts.import },
];
return (
<nav className="ak-sidebar">
<div className="ak-sb-section">PIPELINE</div>
{items.map(it => (
<button key={it.id}
className={`ak-sb-item ${screen === it.id ? "active" : ""}`}
onClick={() => setScreen(it.id)}>
<Icon name={it.icon} size={16} />
<span className="ak-sb-label">{it.label}</span>
{it.count !== null && <span className="ak-sb-count">{it.count}</span>}
</button>
))}
<div className="ak-sb-section" style={{ marginTop: 16 }}>SETTINGS</div>
<button className="ak-sb-item"><Icon name="card" size={16} /><span className="ak-sb-label">Accounts</span></button>
<button className="ak-sb-item"><Icon name="clock" size={16} /><span className="ak-sb-label">History</span></button>
<div style={{ flex: 1 }}></div>
<div className="ak-sb-foot">
<div className="ak-sb-foot-row">
<span className="lbl">Ledger</span>
<span className="val mono">9tfox-2026-03.ldgr</span>
</div>
<div className="ak-sb-foot-row">
<span className="lbl">Branch</span>
<span className="val mono">main · clean</span>
</div>
<div className="ak-sb-foot-row">
<span className="lbl">Last push</span>
<span className="val mono">4m ago</span>
</div>
</div>
</nav>
);
}
Object.assign(window, { Header, Sidebar });