/** * @license * SPDX-License-Identifier: Apache-2.0 */ /** * Console UI kit — the shared visual language ported from the operations console * (nearle_console). Orders / Deliveries / Delivery-Reports all render against this * so they match the source design exactly: brand purple #662582, the tint/soft/ * ring/edge alpha scale, pill filters + tabs, KPI cards with a gradient top-bar, * status chips, metric pills, stamp cells, gradient headers and total bars. * * Per the design's own model, accent colours are data-driven (per status / per * card), so colour-bearing bits use inline styles (the natural translation of the * source's MUI `sx`) while layout/spacing use Tailwind. */ import React from 'react'; // ── Design tokens ──────────────────────────────────────────────────────────────── export const BRAND = '#662582'; export const BRAND_LIGHT = '#9255AB'; export const TEXT = '#0f172a'; export const TEXT_2 = '#64748b'; export const TEXT_3 = '#94a3b8'; export const BORDER = '#e2e8f0'; export const DIVIDER = '#f1f5f9'; export const SURFACE_ALT = '#f8fafc'; export const SHADOW_MD = '0 8px 24px rgba(15, 23, 42, 0.08)'; export const SHADOW_SOFT = '0 14px 40px rgba(15, 23, 42, 0.10)'; export const SHADOW_POP = '0 18px 50px rgba(15, 23, 42, 0.18)'; /** Alpha helpers — append #RRGGBBAA suffixes (08≈3%, 18≈9%, 26≈15%, 55≈33%). */ export const tint = (c: string) => `${c}08`; export const soft = (c: string) => `${c}18`; export const ring = (c: string) => `${c}26`; export const edge = (c: string) => `${c}55`; // ── Status colour maps ─────────────────────────────────────────────────────────── /** Order lifecycle (orders board). */ export const ORDER_STATUS: Record = { created: '#0ea5e9', pending: '#f59e0b', processing: '#0ea5e9', modified: '#06b6d4', confirmed: '#10b981', accepted: '#6366f1', ready: '#6366f1', delivered: '#10b981', cancelled: '#ef4444', }; /** Delivery lifecycle (STATUS_META). */ export const DELIVERY_STATUS: Record = { pending: '#f59e0b', accepted: '#6366f1', arrived: '#06b6d4', picked: '#8b5cf6', active: '#14b8a6', skipped: '#f97316', delivered: '#10b981', cancelled: '#ef4444', }; export const statusColor = (map: Record, s: string) => map[s.toLowerCase()] || TEXT_2; // ── Gradient header ────────────────────────────────────────────────────────────── export function GradientHeader({ title, subtitle, status, right, }: { title: string; subtitle?: string; status?: React.ReactNode; right?: React.ReactNode; }) { return (

{title}

{subtitle &&

{subtitle}

} {status &&
{status}
}
{right &&
{right}
}
); } function BrandMark() { return ( ); } /** Live / loading / error status line used under the header title. */ export function LiveStatus({ state, label }: { state: 'live' | 'loading' | 'error'; label: string }) { const color = state === 'error' ? '#ef4444' : state === 'loading' ? '#94a3b8' : '#10b981'; return ( {label} ); } // ── KPI cards ──────────────────────────────────────────────────────────────────── export interface KpiItem { label: string; value: string; color: string; icon: React.ReactNode; badge?: string; } export function KpiStrip({ items, loading }: { items: KpiItem[]; loading?: boolean }) { return (
{items.map((it) => (
(e.currentTarget.style.boxShadow = SHADOW_MD)} onMouseLeave={(e) => (e.currentTarget.style.boxShadow = 'none')} >

{it.label}

{loading ? '—' : it.value}

{it.badge && ( {it.badge} )}
{it.icon}
))}
); } // ── Pill (filter chip / tab) ───────────────────────────────────────────────────── interface PillProps { active: boolean; color: string; onClick?: () => void; title?: string; children: React.ReactNode; count?: number | string; } export function Pill({ active, color, onClick, title, children, count }: PillProps) { return ( ); } // ── Status chip (table cell) ───────────────────────────────────────────────────── export function StatusChip({ label, color }: { label: string; color: string }) { return ( {label} ); } // ── Metric pill (km / amount / count cells) ────────────────────────────────────── export function MetricPill({ color, children, minWidth }: { color: string; children: React.ReactNode; minWidth?: number }) { return ( {children} ); } // ── Stamp cell (date over time) ────────────────────────────────────────────────── export function StampCell({ date, time }: { date?: string; time?: string }) { if (!date && !time) return ; return (
{date &&
{date}
} {time &&
{time}
}
); } // ── Search pill ────────────────────────────────────────────────────────────────── export function SearchPill({ value, onChange, placeholder, color = BRAND }: { value: string; onChange: (v: string) => void; placeholder?: string; color?: string }) { return (
onChange(e.target.value)} placeholder={placeholder} className="block w-full rounded-full outline-none font-medium transition-all box-border" style={{ height: 38, paddingLeft: 32, paddingRight: value ? 30 : 14, fontSize: 12.5, background: tint(color), border: `1.5px solid ${edge(color)}`, color: TEXT }} onFocus={(e) => { e.currentTarget.style.borderColor = color; e.currentTarget.style.boxShadow = `0 0 0 3px ${ring(color)}`; }} onBlur={(e) => { e.currentTarget.style.borderColor = edge(color); e.currentTarget.style.boxShadow = 'none'; }} /> {value && ( )}
); } // ── Card shells & table head cell ──────────────────────────────────────────────── export function Card({ children, className = '', flush }: { children: React.ReactNode; className?: string; flush?: 'top' }) { return (
{children}
); } /** Filter/tab bar paper that visually joins the table below it (flat bottom). */ export function FilterBar({ children, className = '' }: { children: React.ReactNode; className?: string }) { return (
{children}
); } export const TH_STYLE: React.CSSProperties = { background: SURFACE_ALT, color: TEXT_2, fontSize: 10.5, fontWeight: 800, letterSpacing: 0.6, textTransform: 'uppercase', whiteSpace: 'nowrap', borderBottom: `1px solid ${BORDER}`, };