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