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