// BinGest dashboard mock — used in hero (compact) and demo tour (full). // Includes data-hotspot anchors so a parent can render highlight overlays. const DM_NAV = [ { id: "dashboard", label: "Dashboard", Icon: IconDashboard, active: true }, { id: "clientes", label: "Clientes", Icon: IconUsers }, { id: "folha", label: "Folha de ponto", Icon: IconClock }, { id: "obras", label: "Obras", Icon: IconBuilding, sub: "Condomínio das Acácias" }, { id: "servicos", label: "Serviços", Icon: IconClipboard }, { id: "equipe", label: "Equipe", Icon: IconTeam }, { id: "financeiro", label: "Financeiro", Icon: IconWallet }, { id: "relatorios", label: "Relatórios", Icon: IconReport }, { id: "config", label: "Configurações", Icon: IconGear }]; const DM_TABS = ["Consolidado", "Somente Obras", "Somente Assistência Técnica", "Por Cliente"]; function brl(n) { return "R$ " + n.toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } // Animated currency that ticks up to the target on mount or when value changes. function TickNumber({ value, prefix = "R$ ", suffix = "", duration = 1200, decimals = 2 }) { const [n, setN] = React.useState(0); const startedRef = React.useRef(false); React.useEffect(() => { let raf, t0; const from = 0,to = value; const step = (t) => { if (!t0) t0 = t; const p = Math.min(1, (t - t0) / duration); const eased = 1 - Math.pow(1 - p, 3); setN(from + (to - from) * eased); if (p < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [value]); const formatted = decimals ? n.toLocaleString("pt-BR", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : Math.round(n).toLocaleString("pt-BR"); return {prefix}{formatted}{suffix}; } // Bar chart — last 6 months: Receita Obras (blue), Custo Direto (amber), Receita AT (purple), Resultado Op (green) function MiniBarChart({ height = 180 }) { const months = ["dez. 25", "jan. 26", "fev. 26", "mar. 26", "abr. 26", "mai. 26"]; const obras = [3200, 0, 5800, 0, 7800, 19500]; const at = [0, 2400, 0, 1800, 0, 0]; const custo = [-1100, -800, -2200, -700, -2800, -8210]; const resOp = [2100, 1600, 3600, 1100, 5000, 11290]; const all = [...obras, ...at, ...resOp]; const max = Math.max(...all); const w = 560,h = height; const gx = 36,gy = 18; const innerW = w - gx - 8,innerH = h - gy - 24; const groupW = innerW / months.length; const barW = 8; const colors = { obras: "#2A5BFF", at: "#7C5BD8", custo: "#F59E0B", res: "#16A34A" }; const scale = (v) => v / max * innerH; const yTicks = [0, 5000, 10000, 15000, 20000]; return ( ); } function DashboardMock({ mode = "full", highlight = null, animateValues = true, scale = 1 }) { const rootRef = React.useRef(null); const [box, setBox] = React.useState(null); // Compute highlight bbox relative to mock root React.useEffect(() => { if (!highlight || !rootRef.current) {setBox(null);return;} const el = rootRef.current.querySelector(`[data-hotspot="${highlight}"]`); if (!el) {setBox(null);return;} const rect = el.getBoundingClientRect(); const root = rootRef.current.getBoundingClientRect(); setBox({ top: rect.top - root.top - 6, left: rect.left - root.left - 6, width: rect.width + 12, height: rect.height + 12 }); }, [highlight, mode]); // Recompute on resize React.useEffect(() => { if (!highlight || !rootRef.current) return; const ro = new ResizeObserver(() => { const el = rootRef.current.querySelector(`[data-hotspot="${highlight}"]`); if (!el) return; const rect = el.getBoundingClientRect(); const root = rootRef.current.getBoundingClientRect(); setBox({ top: rect.top - root.top - 6, left: rect.left - root.left - 6, width: rect.width + 12, height: rect.height + 12 }); }); ro.observe(rootRef.current); return () => ro.disconnect(); }, [highlight]); return (
BinGest Construções · quarta-feira, 13 de maio