/* O Tal Picolé — shared components */
const { useState, useEffect, useMemo, useRef, useCallback } = React;
/* ============ LOGO ============ */
function Logo({ size = 30, mark = false }) {
// Popsicle mark in brand red — simple, geometric, recognizable
return (
);
}
/* ============ ICON SET (line, 18px) ============ */
const Icons = {};
const _icon = (paths, vb = "0 0 20 20") => function Icon({ size = 18, color = "currentColor", strokeWidth = 1.6, className = "ic" }) {
return (
);
};
Icons.Dashboard = _icon(<>>);
Icons.Factory = _icon(<>>);
Icons.Store = _icon(<>>);
Icons.Sun = _icon(<>>);
Icons.Users = _icon(<>>);
Icons.Box = _icon(<>>);
Icons.Wallet = _icon(<>>);
Icons.Chart = _icon(<>>);
Icons.Settings = _icon(<>>);
Icons.Search = _icon(<>>);
Icons.Bell = _icon(<>>);
Icons.Plus = _icon(<>>);
Icons.Minus = _icon(<>>);
Icons.Check = _icon(<>>);
Icons.X = _icon(<>>);
Icons.ArrowUp = _icon(<>>);
Icons.ArrowDown = _icon(<>>);
Icons.ArrowRight = _icon(<>>);
Icons.ChevDown = _icon(<>>);
Icons.Filter = _icon(<>>);
Icons.Calendar = _icon(<>>);
Icons.Receipt = _icon(<>>);
Icons.Beach = _icon(<>>);
Icons.Truck = _icon(<>>);
Icons.Edit = _icon(<>>);
Icons.Trash = _icon(<>>);
Icons.More = _icon(<>>);
Icons.Eye = _icon(<>>);
Icons.Print = _icon(<>>);
Icons.Download = _icon(<>>);
Icons.Send = _icon(<>>);
Icons.AlertTriangle = _icon(<>>);
Icons.Snowflake = _icon(<>>);
Icons.History = _icon(<>>);
Icons.PieChart = _icon(<>>);
Icons.MapPin = _icon(<>>);
Icons.Coffee = _icon(<>>);
Icons.Star = _icon(<>>);
Icons.User = _icon(<>>);
Icons.QrCode = _icon(<>>);
/* ============ Helper components ============ */
function Card({ title, subtitle, action, children, flush, className = "", style }) {
return (
{(title || action) && (
{title &&
{title}
}
{subtitle &&
{subtitle}
}
{action &&
{action}
}
)}
{children}
);
}
function KPI({ label, value, trend, trendDir, sub, icon, medal, tone, sparkline }) {
const cls = "kpi" + (tone ? " kpi--" + tone : "");
return (
{icon}
{label}
{medal &&
{medal}
}
{value}
{trend && (
{trendDir === "up" && }
{trendDir === "down" && }
{trend}
)}
{sub && {sub}}
{sparkline}
);
}
function Chip({ children, tone, dot }) {
const cls = "chip" + (tone ? " chip--" + tone : "");
return {dot && }{children};
}
function FlavorTag({ productId }) {
const p = PRODUCTS.find(x => x.id === productId);
if (!p) return null;
return (
{p.flavor}
);
}
function Button({ children, variant = "secondary", size, icon, onClick, type = "button", disabled, style }) {
const cls = "btn btn--" + variant + (size ? " btn--" + size : "");
return (
);
}
function Field({ label, required, hint, error, children, style }) {
return (
{label && }
{children}
{hint && !error && {hint}}
{error && {error}}
);
}
function Segmented({ options, value, onChange }) {
return (
{options.map(opt => (
))}
);
}
function Tabs({ items, value, onChange }) {
return (
{items.map(it => (
))}
);
}
function Modal({ title, subtitle, size, onClose, children, footer }) {
useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose && onClose(); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
return (
{ if (e.target === e.currentTarget) onClose && onClose(); }}>
{title}
{subtitle &&
{subtitle}
}
{children}
{footer &&
{footer}
}
);
}
function Toast({ message, onDone, kind = "success" }) {
useEffect(() => {
if (!message) return;
const t = setTimeout(() => onDone && onDone(), 2400);
return () => clearTimeout(t);
}, [message, onDone]);
if (!message) return null;
return (
{kind === "success" ? : }
{message}
);
}
function Sparkline({ data, color = "#D4310B", height = 38 }) {
if (!data || !data.length) return null;
const w = 120, h = height;
const min = Math.min(...data), max = Math.max(...data);
const range = max - min || 1;
const step = w / (data.length - 1 || 1);
const pts = data.map((v, i) => `${i*step},${h - ((v - min) / range) * (h - 6) - 3}`).join(" ");
const area = `M0,${h} L${pts.split(' ').join(' L')} L${w},${h} Z`;
return (
);
}
function BarChart({ data, max, color = "var(--brand-600)", alt = "var(--ink-200)", showLabels = true, height = 180 }) {
const m = max || Math.max(...data.map(d => d.value));
return (
{data.map((d, i) => {
const pct = (d.value / m) * 100;
return (
{showLabels &&
{d.label}
}
);
})}
);
}
function ProgressBar({ value, max, tone }) {
const pct = Math.min(100, Math.max(0, (value / max) * 100));
const cls = "bar" + (tone ? " bar--" + tone : "");
return
;
}
/* Numeric stepper */
function Stepper({ value, onChange, min = 0, max = 9999, step = 1 }) {
return (
{
const v = parseInt(e.target.value || "0", 10);
if (!isNaN(v)) onChange(Math.max(min, Math.min(max, v)));
}} style={{ width:48, textAlign:"center", border:"none", borderLeft:"1px solid var(--line)", borderRight:"1px solid var(--line)", padding:"6px 4px", outline:"none", background:"white", fontWeight:600 }} />
);
}
function Steps({ items, current }) {
return (
{items.map((it, i) => (
{i < current ? : i + 1}
{it}
{i < items.length - 1 && }
))}
);
}
/* Donut chart */
function Donut({ segments, size = 140, thickness = 18, total }) {
const r = (size - thickness) / 2;
const c = 2 * Math.PI * r;
const sum = total || segments.reduce((s, x) => s + x.value, 0);
let offset = 0;
return (
);
}
/* Section header inside a page */
function SectionHead({ title, subtitle, action }) {
return (
{title}
{subtitle &&
{subtitle}
}
{action}
);
}
Object.assign(window, {
Logo, Icons, Card, KPI, Chip, FlavorTag, Button, Field, Segmented, Tabs,
Modal, Toast, Sparkline, BarChart, ProgressBar, Stepper, Steps, Donut, SectionHead,
});