/* global React */
/* Cosidata Portal — shared primitives + sample data.
Loaded first; exposes components on window for client.jsx / admin.jsx. */
const { useState, useEffect, useRef } = React;
const MARK = "assets/cosidata-mark.svg";
function useLucide(){ useEffect(() => { if (window.lucide) window.lucide.createIcons(); }); }
const Icon = ({ name, style, cls }) => ;
function Wordmark({ size = 22, dark = false }){
return (
cosidata
);
}
function Avatar({ name, bg = "linear-gradient(135deg,var(--primary),var(--accent))", size = 38 }){
const initials = name.split(" ").map(w => w[0]).slice(0,2).join("").toUpperCase();
return
{initials}
;
}
function Badge({ kind = "primary", children }){
return {children};
}
// ============================ SIDEBAR ============================
function Sidebar({ nav, view, onNav, user, accentLabel, open, onClose, footer }){
useLucide();
return (
);
}
// ============================ TOPBAR ============================
function Topbar({ title, sub, onMenu, actions, crumb }){
useLucide();
return (
{crumb &&
{crumb}
}
{title}
{sub &&
{sub}
}
{actions}
);
}
// ============================ KPI ============================
function KpiTile({ label, value, delta, dir = "up", icon }){
return (
{value}
{delta &&
{dir === "up" ? "▲" : "▼"} {delta}}
);
}
// ============================ BAR CHART (forked) ============================
function BarChart({ title, sub, data, unit = "" }){
const max = Math.max(...data.map(d => d[1])) * 1.12;
return (
{data.map(([m, v], i) => (
))}
);
}
// ============================ KANBAN ============================
const KAN_COLS = [
{ id: "todo", label: "A fazer", color: "var(--muted)" },
{ id: "doing", label: "Em andamento", color: "var(--amber)" },
{ id: "done", label: "Concluído", color: "var(--green)" },
];
function Kanban({ tasks, setTasks, readOnly }){
useLucide();
const [dragId, setDragId] = useState(null);
const [overCol, setOverCol] = useState(null);
const [adding, setAdding] = useState(false);
const [draft, setDraft] = useState("");
const move = (id, col) => setTasks(ts => ts.map(t => t.id === id ? { ...t, col } : t));
const addTask = () => {
const txt = draft.trim();
if (!txt) { setAdding(false); return; }
setTasks(ts => [...ts, { id: "t" + Date.now(), title: txt, col: "todo", tag: null }]);
setDraft(""); setAdding(false);
};
return (
{KAN_COLS.map(col => {
const items = tasks.filter(t => t.col === col.id);
return (
{ if (readOnly) return; e.preventDefault(); setOverCol(col.id); }}
onDragLeave={() => setOverCol(o => o === col.id ? null : o)}
onDrop={e => { e.preventDefault(); if (dragId) move(dragId, col.id); setDragId(null); setOverCol(null); }}>
{col.label}
{items.length}
{items.map(t => (
setDragId(t.id)} onDragEnd={() => { setDragId(null); setOverCol(null); }}>
{t.title}
{t.tag &&
{t.tag.label}
}
))}
{col.id === "todo" && !readOnly && (
adding ? (
) : (
)
)}
);
})}
);
}
// ============================ COMMENTS ============================
function Comments({ thread, setThread, me }){
useLucide();
const [draft, setDraft] = useState("");
const endRef = useRef(null);
const send = () => {
const txt = draft.trim();
if (!txt) return;
setThread(t => [...t, { id: "c" + Date.now(), who: me.who, name: me.name, avatarBg: me.avatarBg, time: "agora", text: txt }]);
setDraft("");
};
return (
{thread.length === 0 &&
Nenhuma mensagem ainda. Comece a conversa abaixo.
}
{thread.map(c => {
const mine = c.who === me.who;
return (
{c.name}· {c.time}
{c.text}
);
})}
);
}
Object.assign(window, { MARK, useLucide, Icon, Wordmark, Avatar, Badge, Sidebar, Topbar, KpiTile, BarChart, Kanban, KAN_COLS, Comments });