// UI primitives — shared across modules // Icons inline (Lucide-style stroke), Stat, Card, Table, Badge, Button, Tabs const { useState, useEffect, useRef, useMemo } = React; // ============ ICONS ============ const Ic = ({ name, size = 16, ...rest }) => { const s = size; const common = { width: s, height: s, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", ...rest }; const paths = { home: <>, live: <>, store: <>, site: <>, box: <>, cart: <>, users: <>, chart: <>, megaphone: <>, settings: <>, search: <>, bell: <>, plus: <>, arrowRight: <>, chevronDown: <>, chevronRight: <>, x: <>, check: <>, download: <>, filter: <>, play: <>, pause: <>, edit: <>, trash: <>, package: <>, tag: <>, truck: <>, whatsapp: <>, instagram: <>, pix: <>, qr: <>, barcode: <>, gift: <>, sparkles: <>, dollar: <>, file: <>, folder: <>, map: <>, sliders: <>, layers: <>, flame: <>, refresh: <>, eye: <>, send: <>, user: <>, phone: <>, grid: <>, list: <>, print: <>, cake: <>, bag: <>, spinner: <>, moneyIn: <>, devices: <>, }; return {paths[name] || null}; }; // ============ Currency helpers ============ const brl = (v) => "R$ " + Number(v).toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const brlNoCents = (v) => "R$ " + Number(v).toLocaleString("pt-BR", { maximumFractionDigits: 0 }); const numFmt = (v) => Number(v).toLocaleString("pt-BR"); const pct = (v) => (v >= 0 ? "+" : "") + v.toFixed(1) + "%"; // ============ Stat card ============ const Stat = ({ label, value, valueSuffix, delta, glow, hint, footer }) => (
{label}
{value}{valueSuffix && {valueSuffix}}
{delta !== undefined && delta !== null && (
= 0 ? "up" : "down")}>{pct(delta)} vs. período ant.
)} {hint &&
{hint}
} {footer}
); // ============ Badge ============ const Badge = ({ children, kind, dot }) => ( {dot && } {children} ); // ============ Toggle ============ const Toggle = ({ on, onChange }) => (
onChange && onChange(!on)} role="switch" aria-checked={on}/> ); // ============ Tabs ============ const Tabs = ({ tabs, active, onChange }) => (
{tabs.map(t => ( ))}
); // ============ Status helpers ============ const statusKind = (s) => { const m = (s || "").toLowerCase(); if (m.includes("pago") || m.includes("aprovad") || m.includes("entregue") || m.includes("conclu") || m.includes("recupera")) return "green"; if (m.includes("cancel") || m.includes("falha") || m.includes("vencido")) return "red"; if (m.includes("aguard") || m.includes("pendente") || m.includes("rascunho")) return "muted"; if (m.includes("envia") || m.includes("separa") || m.includes("vence")) return "cyan"; if (m.includes("agend") || m.includes("sacola")) return "purple"; if (m.includes("em dia")) return "green"; return ""; }; const StatusBadge = ({ status }) => {status}; // ============ Avatar ============ const ClientAvatar = ({ name, size = "" }) => { const initials = name.split(" ").map(p => p[0]).slice(0, 2).join("").toUpperCase(); return
{initials}
; }; // ============ Level ============ const Level = ({ n }) => { const kind = n >= 8 ? "l-high" : n >= 5 ? "l-mid" : "l-low"; return N{n}; }; // ============ Spark / line / bars ============ const LineChart = ({ data, height = 180, accent = "var(--accent)" }) => { // data: [{d, v}] const max = Math.max(...data.map(d => d.v)); const min = 0; const w = 100; const h = 100; const pts = data.map((d, i) => { const x = (i / (data.length - 1)) * w; const y = h - ((d.v - min) / (max - min || 1)) * (h - 8) - 4; return [x, y]; }); const path = pts.map((p, i) => (i === 0 ? "M" : "L") + p[0].toFixed(2) + " " + p[1].toFixed(2)).join(" "); const area = path + ` L ${w} ${h} L 0 ${h} Z`; return (
{pts.map((p, i) => ( ))}
{data.filter((_, i) => i % Math.max(1, Math.floor(data.length / 6)) === 0).map((d, i) => {d.d})}
); }; const BarH = ({ rows, max }) => { const m = max || Math.max(...rows.map(r => r.value)); return (
{rows.map((r, i) => (
{r.label} {r.display || r.value}
))}
); }; const Donut = ({ segments, center, label }) => { // segments: [{value, color, label}] const total = segments.reduce((s, x) => s + x.value, 0); let acc = 0; const r = 36, c = 2 * Math.PI * r; return (
{segments.map((s, i) => { const len = (s.value / total) * c; const offset = (acc / total) * c; acc += s.value; return ; })}
{center}
{label}
); }; // ============ Empty state ============ const Empty = ({ icon = "box", title, hint, action }) => (

{title}

{hint &&

{hint}

} {action &&
{action}
}
); Object.assign(window, { Ic, Stat, Badge, Toggle, Tabs, StatusBadge, ClientAvatar, Level, LineChart, BarH, Donut, Empty, brl, brlNoCents, numFmt, pct, statusKind, });