@zea.cl/cranium-sdk 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,638 @@
1
+ 'use strict';
2
+
3
+ var React3 = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var React3__default = /*#__PURE__*/_interopDefault(React3);
9
+
10
+ // src/components/CraniumShell.tsx
11
+ function getAuthHeaders(options) {
12
+ const headers = {};
13
+ if (options.apiKey) headers["x-api-key"] = options.apiKey;
14
+ try {
15
+ const auth = JSON.parse(localStorage.getItem("thalamus_auth") || "null");
16
+ if (auth?.accessToken) headers["Authorization"] = `Bearer ${auth.accessToken}`;
17
+ if (auth?.user?.organization_id) headers["x-zea-org-id"] = auth.user.organization_id;
18
+ } catch {
19
+ }
20
+ return headers;
21
+ }
22
+ function useCranium(options) {
23
+ const [pieces, setPieces] = React3.useState([]);
24
+ const [active, setActiveState] = React3.useState("");
25
+ const [loading, setLoading] = React3.useState(true);
26
+ const [error, setError] = React3.useState(null);
27
+ const [refreshKey, setRefreshKey] = React3.useState(0);
28
+ const api = `${options.baseUrl || ""}/api/v1`;
29
+ const refresh = React3.useCallback(async () => {
30
+ setLoading(true);
31
+ setError(null);
32
+ try {
33
+ const res = await fetch(`${api}/site`, { headers: getAuthHeaders(options) });
34
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
35
+ const d = await res.json();
36
+ const enabled = (d?.pieces || []).filter((p) => p.enabled);
37
+ setPieces(enabled);
38
+ if (enabled.length > 0 && !active) setActiveState(enabled[0].slug);
39
+ } catch (e) {
40
+ setError(e.message);
41
+ }
42
+ setLoading(false);
43
+ }, [api, options.apiKey]);
44
+ React3.useEffect(() => {
45
+ refresh();
46
+ }, []);
47
+ const setActive = (slug) => {
48
+ setActiveState(slug);
49
+ try {
50
+ localStorage.setItem("cranium:active", slug);
51
+ } catch {
52
+ }
53
+ };
54
+ const create = async (piece) => {
55
+ try {
56
+ const res = await fetch(`${api}/pieces`, {
57
+ method: "POST",
58
+ headers: { ...getAuthHeaders(options), "Content-Type": "application/json" },
59
+ body: JSON.stringify(piece)
60
+ });
61
+ if (res.ok) {
62
+ refresh();
63
+ return true;
64
+ }
65
+ } catch {
66
+ }
67
+ return false;
68
+ };
69
+ const remove = async (id) => {
70
+ try {
71
+ const res = await fetch(`${api}/pieces/${id}`, {
72
+ method: "DELETE",
73
+ headers: getAuthHeaders(options)
74
+ });
75
+ if (res.ok) {
76
+ refresh();
77
+ return true;
78
+ }
79
+ } catch {
80
+ }
81
+ return false;
82
+ };
83
+ React3.useCallback((slug) => {
84
+ if (slug === active) setRefreshKey((k) => k + 1);
85
+ }, [active]);
86
+ return { pieces, active, setActive, loading, error, refresh, create, remove };
87
+ }
88
+ function useCraniumRouter() {
89
+ const [path, setPath] = React3.useState(() => {
90
+ if (typeof window !== "undefined") return window.location.pathname;
91
+ return "/";
92
+ });
93
+ const parse = React3.useCallback((p) => {
94
+ const parts = p.split("/").filter(Boolean);
95
+ if (parts[0] === "pieces" && parts[1]) {
96
+ return { piece: parts[1], section: null, sub: null };
97
+ }
98
+ if (parts[0] === "section" && parts[1]) {
99
+ return { piece: null, section: parts[1], sub: parts[2] || null };
100
+ }
101
+ return { piece: null, section: null, sub: null };
102
+ }, []);
103
+ const parsed = parse(path);
104
+ const navigate = React3.useCallback((to) => {
105
+ if (typeof window === "undefined") return;
106
+ window.history.pushState({}, "", to);
107
+ setPath(to);
108
+ }, []);
109
+ const back = React3.useCallback(() => {
110
+ if (typeof window === "undefined") return;
111
+ window.history.back();
112
+ }, []);
113
+ React3.useEffect(() => {
114
+ const onPop = () => setPath(window.location.pathname);
115
+ window.addEventListener("popstate", onPop);
116
+ return () => window.removeEventListener("popstate", onPop);
117
+ }, []);
118
+ React3.useEffect(() => {
119
+ if (typeof window !== "undefined") {
120
+ const params = new URLSearchParams(window.location.search);
121
+ if (params.has("code") || params.has("state")) {
122
+ return;
123
+ }
124
+ }
125
+ }, []);
126
+ return {
127
+ path,
128
+ activePiece: parsed.piece,
129
+ activeSection: parsed.section,
130
+ activeSub: parsed.sub,
131
+ navigate,
132
+ back
133
+ };
134
+ }
135
+ function CraniumTable({ columns, rows, onRowClick, emptyMessage = "No data", className }) {
136
+ const [sortKey, setSortKey] = React3.useState(null);
137
+ const [sortDir, setSortDir] = React3.useState("asc");
138
+ const sorted = React3.useMemo(() => {
139
+ if (!sortKey) return rows;
140
+ return [...rows].sort((a, b) => {
141
+ const av = a[sortKey], bv = b[sortKey];
142
+ if (av == null) return 1;
143
+ if (bv == null) return -1;
144
+ const cmp = String(av).localeCompare(String(bv), void 0, { numeric: true });
145
+ return sortDir === "asc" ? cmp : -cmp;
146
+ });
147
+ }, [rows, sortKey, sortDir]);
148
+ const toggleSort = (key) => {
149
+ if (sortKey === key) {
150
+ setSortDir((d) => d === "asc" ? "desc" : "asc");
151
+ } else {
152
+ setSortKey(key);
153
+ setSortDir("asc");
154
+ }
155
+ };
156
+ if (rows.length === 0) {
157
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 32, textAlign: "center", color: "#8b949e", fontSize: 14 }, children: emptyMessage });
158
+ }
159
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { overflow: "auto", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: [
160
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { style: { position: "sticky", top: 0, zIndex: 5 }, children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsxs(
161
+ "th",
162
+ {
163
+ onClick: () => col.sortable !== false && toggleSort(col.key),
164
+ style: {
165
+ padding: "10px 16px",
166
+ textAlign: "left",
167
+ fontWeight: 600,
168
+ fontSize: 11,
169
+ color: "#8b949e",
170
+ textTransform: "uppercase",
171
+ letterSpacing: "0.04em",
172
+ borderBottom: "1px solid #21262d",
173
+ background: "#0d1117",
174
+ cursor: col.sortable !== false ? "pointer" : "default",
175
+ userSelect: "none",
176
+ whiteSpace: "nowrap"
177
+ },
178
+ children: [
179
+ col.label,
180
+ sortKey === col.key && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: 4, fontSize: 10 }, children: sortDir === "asc" ? "\u25B2" : "\u25BC" })
181
+ ]
182
+ },
183
+ col.key
184
+ )) }) }),
185
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: sorted.map((row, i) => /* @__PURE__ */ jsxRuntime.jsx(
186
+ "tr",
187
+ {
188
+ onClick: () => onRowClick?.(row, i),
189
+ style: {
190
+ cursor: onRowClick ? "pointer" : "default",
191
+ borderBottom: "1px solid #21262d",
192
+ transition: "background 0.1s"
193
+ },
194
+ onMouseEnter: (e) => {
195
+ if (onRowClick) e.currentTarget.style.background = "#161b22";
196
+ },
197
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
198
+ children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
199
+ "td",
200
+ {
201
+ style: {
202
+ padding: "10px 16px",
203
+ color: "#e6edf3",
204
+ whiteSpace: "nowrap",
205
+ overflow: "hidden",
206
+ textOverflow: "ellipsis",
207
+ maxWidth: 300
208
+ },
209
+ children: col.render ? col.render(row[col.key], row) : formatCell(row[col.key])
210
+ },
211
+ col.key
212
+ ))
213
+ },
214
+ row.id || i
215
+ )) })
216
+ ] }) });
217
+ }
218
+ function formatCell(value) {
219
+ if (value === null || value === void 0) return "\u2014";
220
+ if (typeof value === "boolean") return value ? "\u2713" : "\u2717";
221
+ if (value instanceof Date) return value.toLocaleDateString();
222
+ return String(value);
223
+ }
224
+ function CraniumDashboard({ widgets, className }) {
225
+ if (widgets.length === 0) {
226
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 32, textAlign: "center", color: "#8b949e", fontSize: 14 }, children: "No widgets configured" });
227
+ }
228
+ return /* @__PURE__ */ jsxRuntime.jsx(
229
+ "div",
230
+ {
231
+ className,
232
+ style: {
233
+ display: "grid",
234
+ gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
235
+ gap: 16,
236
+ padding: 20,
237
+ overflow: "auto",
238
+ height: "100%",
239
+ alignContent: "start",
240
+ fontFamily: "system-ui, sans-serif"
241
+ },
242
+ children: widgets.map((w, i) => /* @__PURE__ */ jsxRuntime.jsxs(
243
+ "div",
244
+ {
245
+ style: {
246
+ gridColumn: `span ${w.span || 1}`,
247
+ background: "#161b22",
248
+ border: "1px solid #21262d",
249
+ borderRadius: 8,
250
+ padding: 20
251
+ },
252
+ children: [
253
+ w.type === "card" && /* @__PURE__ */ jsxRuntime.jsx(CardWidget, { w }),
254
+ w.type === "chart" && /* @__PURE__ */ jsxRuntime.jsx(ChartWidget, { w }),
255
+ w.type === "list" && /* @__PURE__ */ jsxRuntime.jsx(ListWidget, { w })
256
+ ]
257
+ },
258
+ i
259
+ ))
260
+ }
261
+ );
262
+ }
263
+ function CardWidget({ w }) {
264
+ const trendColor = w.trend === "up" ? "#3fb950" : w.trend === "down" ? "#f85149" : "#8b949e";
265
+ const trendIcon = w.trend === "up" ? "\u2191" : w.trend === "down" ? "\u2193" : "\u2192";
266
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
267
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#8b949e", textTransform: "uppercase", letterSpacing: "0.04em", marginBottom: 8 }, children: w.title }),
268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 8 }, children: [
269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 28, fontWeight: 700, color: "#e6edf3" }, children: w.value }),
270
+ w.trend && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 13, color: trendColor, fontWeight: 600 }, children: [
271
+ trendIcon,
272
+ " ",
273
+ w.subtitle
274
+ ] })
275
+ ] })
276
+ ] });
277
+ }
278
+ function ChartWidget({ w }) {
279
+ const max = Math.max(...(w.data || []).map((d) => d.value), 1);
280
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
281
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#8b949e", textTransform: "uppercase", letterSpacing: "0.04em", marginBottom: 12 }, children: w.title }),
282
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "flex-end", gap: 6, height: 100 }, children: (w.data || []).map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }, children: [
283
+ /* @__PURE__ */ jsxRuntime.jsx(
284
+ "div",
285
+ {
286
+ style: {
287
+ width: "100%",
288
+ height: `${d.value / max * 100}%`,
289
+ background: "#58a6ff",
290
+ borderRadius: "3px 3px 0 0",
291
+ minHeight: 2,
292
+ transition: "height 0.3s"
293
+ }
294
+ }
295
+ ),
296
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, color: "#484f58", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "100%" }, children: d.label })
297
+ ] }, i)) })
298
+ ] });
299
+ }
300
+ function ListWidget({ w }) {
301
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
302
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#8b949e", textTransform: "uppercase", letterSpacing: "0.04em", marginBottom: 12 }, children: w.title }),
303
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: (w.data || []).map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 13 }, children: [
304
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#e6edf3" }, children: d.label }),
305
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#8b949e", fontWeight: 600 }, children: d.value })
306
+ ] }, i)) })
307
+ ] });
308
+ }
309
+ function CraniumForm({ schema, onSubmit, submitLabel = "Save", className }) {
310
+ const [values, setValues] = React3.useState({});
311
+ const [loading, setLoading] = React3.useState(false);
312
+ const [error, setError] = React3.useState(null);
313
+ const handleSubmit = async (e) => {
314
+ e.preventDefault();
315
+ setLoading(true);
316
+ setError(null);
317
+ try {
318
+ await onSubmit(values);
319
+ } catch (err) {
320
+ setError(err.message);
321
+ } finally {
322
+ setLoading(false);
323
+ }
324
+ };
325
+ const s = (p = {}) => ({
326
+ background: "#0d1117",
327
+ border: "1px solid #21262d",
328
+ borderRadius: 6,
329
+ color: "#e6edf3",
330
+ padding: "8px 12px",
331
+ fontSize: 13,
332
+ fontFamily: "system-ui, sans-serif",
333
+ width: "100%",
334
+ outline: "none",
335
+ ...p
336
+ });
337
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { maxWidth: 520, margin: "40px auto", padding: "0 24px" }, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
338
+ schema.map((field) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
339
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "block", fontSize: 12, fontWeight: 600, color: "#8b949e", marginBottom: 4 }, children: [
340
+ field.label,
341
+ " ",
342
+ field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#f85149" }, children: "*" })
343
+ ] }),
344
+ field.type === "select" ? /* @__PURE__ */ jsxRuntime.jsxs(
345
+ "select",
346
+ {
347
+ value: values[field.key] || "",
348
+ onChange: (e) => setValues((v) => ({ ...v, [field.key]: e.target.value })),
349
+ required: field.required,
350
+ style: s(),
351
+ children: [
352
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select..." }),
353
+ (field.options || []).map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value))
354
+ ]
355
+ }
356
+ ) : field.type === "checkbox" ? /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }, children: [
357
+ /* @__PURE__ */ jsxRuntime.jsx(
358
+ "input",
359
+ {
360
+ type: "checkbox",
361
+ checked: !!values[field.key],
362
+ onChange: (e) => setValues((v) => ({ ...v, [field.key]: e.target.checked }))
363
+ }
364
+ ),
365
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13, color: "#e6edf3" }, children: field.label })
366
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
367
+ "input",
368
+ {
369
+ type: field.type || "text",
370
+ value: values[field.key] || "",
371
+ onChange: (e) => setValues((v) => ({ ...v, [field.key]: e.target.value })),
372
+ required: field.required,
373
+ placeholder: field.placeholder,
374
+ style: s()
375
+ }
376
+ )
377
+ ] }, field.key)),
378
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#f85149", fontSize: 12 }, children: error }),
379
+ /* @__PURE__ */ jsxRuntime.jsx(
380
+ "button",
381
+ {
382
+ type: "submit",
383
+ disabled: loading,
384
+ style: {
385
+ background: "#238636",
386
+ color: "#fff",
387
+ border: "none",
388
+ borderRadius: 6,
389
+ padding: "10px 24px",
390
+ cursor: "pointer",
391
+ fontSize: 14,
392
+ fontWeight: 600,
393
+ fontFamily: "inherit",
394
+ opacity: loading ? 0.6 : 1
395
+ },
396
+ children: loading ? "Saving..." : submitLabel
397
+ }
398
+ )
399
+ ] }) });
400
+ }
401
+ function CraniumShell({ config, theme, brandName, rightPiece, sidebarSections, sidebarWidth: iSW = 240, rightWidth: iRW = 420, showRight: iR = true, className, headerContent, subHeaderContent, footerContent, renderIcon }) {
402
+ const { pieces, active, setActive, loading, error, refresh } = useCranium(config);
403
+ const router = useCraniumRouter();
404
+ const [sw, setSw] = React3.useState(() => {
405
+ try {
406
+ return Number(localStorage.getItem("cr:sw") || iSW);
407
+ } catch {
408
+ return iSW;
409
+ }
410
+ });
411
+ const [rw, setRw] = React3.useState(() => {
412
+ try {
413
+ return Number(localStorage.getItem("cr:rw") || iRW);
414
+ } catch {
415
+ return iRW;
416
+ }
417
+ });
418
+ const [rv, setRv] = React3.useState(iR);
419
+ const activeFromRouter = router.activePiece || active;
420
+ const ap = pieces.find((p) => p.slug === activeFromRouter);
421
+ const handleSelect = (slug) => {
422
+ setActive(slug);
423
+ router.navigate(`/pieces/${slug}`);
424
+ };
425
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `cr-shell ${className || ""}`, style: { gridTemplateColumns: `${sw}px 4px 1fr ${rv ? `4px ${rw}px` : "0px 0px"}` }, children: [
426
+ /* @__PURE__ */ jsxRuntime.jsx(Sidebar, { pieces, active: activeFromRouter, onSelect: handleSelect, loading, error, onRefresh: refresh, sections: sidebarSections, brandName, headerContent, subHeaderContent, footerContent, renderIcon }),
427
+ /* @__PURE__ */ jsxRuntime.jsx(Handle, { p: "cr:sw", w: sw, setW: setSw, min: 160, max: 400 }),
428
+ /* @__PURE__ */ jsxRuntime.jsx(Content, { piece: ap, rv, toggle: () => setRv((v) => !v), config }),
429
+ rv ? /* @__PURE__ */ jsxRuntime.jsx(Handle, { p: "cr:rw", w: rw, setW: setRw, min: 300, max: 700, dir: -1 }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
430
+ rv ? /* @__PURE__ */ jsxRuntime.jsx("aside", { className: "cr-right-panel", children: rightPiece || /* @__PURE__ */ jsxRuntime.jsx(DefaultRight, {}) }) : /* @__PURE__ */ jsxRuntime.jsx("div", {})
431
+ ] });
432
+ }
433
+ function Sidebar({ pieces, active, onSelect, loading, error, onRefresh, sections, brandName, headerContent, subHeaderContent, footerContent, renderIcon }) {
434
+ const groups = pieces.reduce((acc, p) => {
435
+ const c = p.category || "General";
436
+ if (!acc[c]) acc[c] = [];
437
+ acc[c].push(p);
438
+ return acc;
439
+ }, {});
440
+ const secs = sections || [];
441
+ return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "cr-sidebar", children: [
442
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cr-sidebar-header", children: [
443
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 14 }, children: (() => {
444
+ const parts = (brandName || "Cranium").split(" ");
445
+ if (parts.length > 1) {
446
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
447
+ parts[0].toUpperCase() === "ZEA" ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: "https://zea.cl/text-zea.svg", alt: "ZEA", style: { height: 13, transform: "translateY(1px)" } }) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700, color: "var(--cr-text)" }, children: parts[0] }),
448
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--cr-text-muted)" }, children: "|" }),
449
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600, color: "var(--cr-text-muted)", fontSize: 13, letterSpacing: "0.04em" }, children: parts.slice(1).join(" ").trim().toUpperCase() })
450
+ ] });
451
+ }
452
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600, color: "var(--cr-text)" }, children: brandName || "Cranium" });
453
+ })() }),
454
+ headerContent && /* @__PURE__ */ jsxRuntime.jsx("div", { children: headerContent })
455
+ ] }),
456
+ subHeaderContent && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "12px 16px 0", flexShrink: 0 }, children: subHeaderContent }),
457
+ /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "cr-sidebar-nav", children: [
458
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "16px", color: "var(--cr-text-muted)", fontSize: 12 }, children: "Loading..." }),
459
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "12px 16px" }, children: [
460
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#ff7b72", fontSize: 11, marginBottom: 6 }, children: error }),
461
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onRefresh, className: "cr-btn", children: "Retry" })
462
+ ] }),
463
+ !loading && !error && Object.entries(groups).map(([cat, items]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
464
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cr-sidebar-section-title", children: cat }),
465
+ items.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => onSelect(p.slug), className: `cr-sidebar-item ${active === p.slug ? "cr-sidebar-item--active" : ""}`, children: [
466
+ p.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 14, width: 20, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, opacity: active === p.slug ? 1 : 0.7 }, children: renderIcon ? renderIcon(p.icon) : p.icon }),
467
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: p.name })
468
+ ] }, p.slug))
469
+ ] }, cat)),
470
+ secs.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
471
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cr-sidebar-section-title", children: s.label }),
472
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "0 0 8px 0" }, children: s.content })
473
+ ] }, s.id))
474
+ ] }),
475
+ footerContent ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cr-sidebar-footer", children: footerContent }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cr-sidebar-footer", style: { display: "flex", alignItems: "center", gap: 10 }, children: [
476
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 28, height: 28, borderRadius: "50%", background: "var(--cr-primary)", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 700, flexShrink: 0 }, children: "?" }),
477
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, fontWeight: 600, color: "var(--cr-text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }, children: "User" }),
478
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => {
479
+ localStorage.removeItem("thalamus_auth");
480
+ window.location.reload();
481
+ }, className: "cr-btn", children: "Logout" })
482
+ ] })
483
+ ] });
484
+ }
485
+ function Content({ piece, rv, toggle, config }) {
486
+ const [data, setData] = React3.useState(null);
487
+ const [fetchError, setFetchError] = React3.useState(null);
488
+ const [fetchLoading, setFetchLoading] = React3.useState(false);
489
+ const [formSchema, setFormSchema] = React3.useState(null);
490
+ const [formConfig, setFormConfig] = React3.useState(null);
491
+ const [refreshKey, setRefreshKey] = React3.useState(0);
492
+ React3__default.default.useEffect(() => {
493
+ if (!piece || piece.renderMode === "iframe" || !piece.renderMode) return;
494
+ setFormSchema(null);
495
+ setFormConfig(null);
496
+ setFetchLoading(true);
497
+ setFetchError(null);
498
+ const headers = getApiHeaders(config.apiKey);
499
+ const auth = getToken();
500
+ let url = piece.url;
501
+ if (url.includes("auth.zea.localhost/api/users") && auth?.user?.organization_id) {
502
+ url += (url.includes("?") ? "&" : "?") + "organization_id=" + auth.user.organization_id;
503
+ }
504
+ fetch(url, { headers }).then((r) => {
505
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
506
+ return r.json();
507
+ }).then((d) => setData(d)).catch((e) => setFetchError(e.message)).finally(() => setFetchLoading(false));
508
+ }, [piece?.slug, piece?.renderMode, piece?.url, refreshKey]);
509
+ React3.useState(0)[1];
510
+ return /* @__PURE__ */ jsxRuntime.jsxs("main", { className: "cr-main", children: [
511
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "cr-header", children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: piece && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--cr-text)", fontWeight: 500 }, children: piece.name }) }),
513
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
514
+ piece?.addAction && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => {
515
+ fetch(piece.addAction.url).then((r) => r.json()).then((d) => {
516
+ setFormSchema(d.schema);
517
+ setFormConfig(d);
518
+ }).catch((e) => setFetchError(e.message));
519
+ }, className: "cr-btn cr-btn--primary", children: [
520
+ "+ ",
521
+ piece.addAction.label || "Add"
522
+ ] }),
523
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: toggle, className: "cr-btn", children: rv ? "Hide Panel" : "Panel" })
524
+ ] })
525
+ ] }),
526
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cr-content-area", children: [
527
+ !piece && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%", color: "var(--cr-text-muted)", fontSize: 14 }, children: "Select a piece from the sidebar" }),
528
+ piece && fetchLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%", color: "var(--cr-text-muted)", fontSize: 14 }, children: "Loading..." }),
529
+ piece && fetchError && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "100%", gap: 12 }, children: [
530
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#f85149", fontSize: 13 }, children: fetchError }),
531
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setFetchError(null), className: "cr-btn", children: "Retry" })
532
+ ] }),
533
+ piece && !fetchLoading && !fetchError && piece.renderMode === "table" && data && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: formSchema ? /* @__PURE__ */ jsxRuntime.jsx(
534
+ CraniumForm,
535
+ {
536
+ schema: formSchema,
537
+ onSubmit: async (values) => {
538
+ const submitUrl = formConfig?.submitUrl;
539
+ if (submitUrl) {
540
+ const res = await fetch(submitUrl, {
541
+ method: formConfig?.submitMethod || "POST",
542
+ headers: { "Content-Type": "application/json", ...getApiHeaders() },
543
+ body: JSON.stringify(values)
544
+ });
545
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
546
+ await res.json();
547
+ }
548
+ setFormSchema(null);
549
+ setFormConfig(null);
550
+ setRefreshKey((k) => k + 1);
551
+ }
552
+ }
553
+ ) : /* @__PURE__ */ jsxRuntime.jsx(CraniumTable, { columns: piece.columns || buildColumns(data?.data || data?.items?.[0] ? data.items : data), rows: Array.isArray(data) ? data : data?.items || data?.data || data?.rows || [] }) }),
554
+ piece && !fetchLoading && !fetchError && piece.renderMode === "dashboard" && data && /* @__PURE__ */ jsxRuntime.jsx(CraniumDashboard, { widgets: data.widgets || (Array.isArray(data) ? data : buildDashboardWidgets(data)) }),
555
+ piece && !fetchLoading && !fetchError && piece.renderMode === "form" && data?.schema && /* @__PURE__ */ jsxRuntime.jsx(
556
+ CraniumForm,
557
+ {
558
+ schema: data.schema,
559
+ onSubmit: async (values) => {
560
+ const submitUrl = data?.submitUrl || piece.url;
561
+ if (submitUrl) {
562
+ const res = await fetch(submitUrl, {
563
+ method: data?.submitMethod || "POST",
564
+ headers: { "Content-Type": "application/json", ...getApiHeaders() },
565
+ body: JSON.stringify(values)
566
+ });
567
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
568
+ }
569
+ }
570
+ }
571
+ ),
572
+ piece && !fetchLoading && !fetchError && (!piece.renderMode || piece.renderMode === "iframe") && /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: piece.url, style: { width: "100%", height: "100%", border: "none" }, title: piece.name }, piece.slug)
573
+ ] })
574
+ ] });
575
+ }
576
+ function DefaultRight() {
577
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%", color: "var(--cr-text-muted)", fontSize: 13 }, children: "Right panel \u2014 integrate a piece here" });
578
+ }
579
+ function buildColumns(data) {
580
+ if (!data || !Array.isArray(data) || data.length === 0) return [];
581
+ return Object.keys(data[0]).map((k) => ({ key: k, label: k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) }));
582
+ }
583
+ function buildDashboardWidgets(data) {
584
+ return Object.entries(data).map(([key, value]) => ({
585
+ type: "card",
586
+ title: key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
587
+ value: typeof value === "number" ? String(value) : String(value)
588
+ }));
589
+ }
590
+ function getToken() {
591
+ try {
592
+ const s = localStorage.getItem("thalamus_auth");
593
+ return s ? JSON.parse(s) : null;
594
+ } catch {
595
+ return null;
596
+ }
597
+ }
598
+ function getApiHeaders(apiKey) {
599
+ const headers = {};
600
+ if (apiKey) headers["x-api-key"] = apiKey;
601
+ const auth = getToken();
602
+ if (auth?.accessToken) headers["Authorization"] = `Bearer ${auth.accessToken}`;
603
+ if (auth?.user?.organization_id) headers["x-zea-org-id"] = auth.user.organization_id;
604
+ return headers;
605
+ }
606
+ function Handle({ p, w, setW, min, max, dir = 1 }) {
607
+ const down = (e) => {
608
+ e.preventDefault();
609
+ const sx = e.clientX, sw = w;
610
+ document.body.style.cursor = "col-resize";
611
+ document.body.style.userSelect = "none";
612
+ const mv = (ev) => {
613
+ setW(Math.min(max, Math.max(min, sw + (ev.clientX - sx) * dir)));
614
+ };
615
+ const up = () => {
616
+ document.body.style.cursor = "";
617
+ document.body.style.userSelect = "";
618
+ document.removeEventListener("mousemove", mv);
619
+ document.removeEventListener("mouseup", up);
620
+ try {
621
+ localStorage.setItem(p, String(w));
622
+ } catch {
623
+ }
624
+ };
625
+ document.addEventListener("mousemove", mv);
626
+ document.addEventListener("mouseup", up);
627
+ };
628
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { onMouseDown: down, className: "cr-handle" });
629
+ }
630
+
631
+ exports.CraniumDashboard = CraniumDashboard;
632
+ exports.CraniumForm = CraniumForm;
633
+ exports.CraniumShell = CraniumShell;
634
+ exports.CraniumTable = CraniumTable;
635
+ exports.useCranium = useCranium;
636
+ exports.useCraniumRouter = useCraniumRouter;
637
+ //# sourceMappingURL=index.js.map
638
+ //# sourceMappingURL=index.js.map