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