elysia-rack 0.2.2 → 0.3.0

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.
Files changed (38) hide show
  1. package/README.md +22 -15
  2. package/dist/Dashboard-4wv82gwx.js +212 -0
  3. package/dist/Dashboard-jkvfzyk2.js +210 -0
  4. package/dist/Dashboard-zhg2jtq5.js +211 -0
  5. package/dist/{Panel-fbp3wrsw.js → Panel-1qkzh8t7.js} +355 -311
  6. package/dist/Panel-7zpqz8gy.js +782 -0
  7. package/dist/Panel-9mmh3tbh.js +790 -0
  8. package/dist/Panel-ba0qzknj.js +789 -0
  9. package/dist/Panel-e6jyf4rc.js +789 -0
  10. package/dist/index-0dwhzk3j.js +145 -0
  11. package/dist/{index-6v0ns1xn.js → index-b8sq8rbd.js} +1 -1
  12. package/dist/index-rg87f3v1.js +174 -0
  13. package/dist/panel-app.js +27 -11
  14. package/dist/panel.css +2 -1
  15. package/dist/rack/types/metadata.d.ts +2 -1
  16. package/dist/react/donate.d.ts +1 -1
  17. package/dist/react/index.js +3 -3
  18. package/dist/react/page/Panel.d.ts +1 -0
  19. package/dist/react/page/components/Icon.d.ts +10 -0
  20. package/dist/react/page/components/Masthead.d.ts +5 -4
  21. package/package.json +8 -3
  22. package/dist/Dashboard-0da8dvbv.js +0 -225
  23. package/dist/Dashboard-2j561p7c.js +0 -225
  24. package/dist/Dashboard-5mt9hnbw.js +0 -252
  25. package/dist/Dashboard-7gmx8c9y.js +0 -248
  26. package/dist/Dashboard-s8nfyf7s.js +0 -39
  27. package/dist/Dashboard-xkh2g9f7.js +0 -248
  28. package/dist/Dashboard-xpstr7fr.js +0 -172
  29. package/dist/Panel-3yrk3krj.js +0 -914
  30. package/dist/Panel-b7jhgvnm.js +0 -916
  31. package/dist/Panel-n0kstkyw.js +0 -735
  32. package/dist/Panel-pkvqf5v8.js +0 -912
  33. package/dist/Panel-wjxjtbmr.js +0 -753
  34. package/dist/Panel-za4av616.js +0 -753
  35. package/dist/index-h3zbq796.js +0 -39
  36. package/dist/index-tgk4jt4d.js +0 -63
  37. package/dist/index-xsfqad54.js +0 -187
  38. package/dist/index-zc7rgvmm.js +0 -13
@@ -0,0 +1,782 @@
1
+ import {
2
+ Masthead,
3
+ Sidebar
4
+ } from "./index-0dwhzk3j.js";
5
+ import {
6
+ getRackTree
7
+ } from "./index-4wxvmd8a.js";
8
+ import {
9
+ resolveDonateProp
10
+ } from "./index-b8sq8rbd.js";
11
+ import"./index-37x76zdn.js";
12
+
13
+ // src/react/page/components/Breadcrumb.tsx
14
+ import { jsxDEV } from "react/jsx-dev-runtime";
15
+ function Breadcrumb({ trail }) {
16
+ return /* @__PURE__ */ jsxDEV("nav", {
17
+ "aria-label": "Breadcrumb",
18
+ className: "pb-3 text-xs",
19
+ children: /* @__PURE__ */ jsxDEV("ol", {
20
+ className: "flex flex-wrap items-center gap-1.5",
21
+ children: trail.map((item, i) => {
22
+ const last = i === trail.length - 1;
23
+ return /* @__PURE__ */ jsxDEV("li", {
24
+ className: "flex items-center gap-1.5",
25
+ children: [
26
+ i > 0 ? /* @__PURE__ */ jsxDEV("span", {
27
+ "aria-hidden": "true",
28
+ className: "text-text-muted",
29
+ children: "/"
30
+ }, undefined, false, undefined, this) : null,
31
+ item.href && !last ? /* @__PURE__ */ jsxDEV("a", {
32
+ href: item.href,
33
+ className: "text-breadcrumb-foreground hover:text-foreground",
34
+ children: item.label
35
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
36
+ "aria-current": last ? "page" : undefined,
37
+ className: last ? "text-foreground font-medium" : "text-breadcrumb-foreground",
38
+ children: item.label
39
+ }, undefined, false, undefined, this)
40
+ ]
41
+ }, `${item.label}-${i}`, true, undefined, this);
42
+ })
43
+ }, undefined, false, undefined, this)
44
+ }, undefined, false, undefined, this);
45
+ }
46
+
47
+ // src/react/page/components/href.ts
48
+ function href(params, over = {}) {
49
+ const query = new URLSearchParams;
50
+ for (const [key, value] of Object.entries({ ...params, ...over })) {
51
+ if (value === undefined || value === null || value === "")
52
+ continue;
53
+ query.set(key, Array.isArray(value) ? value.join(",") : String(value));
54
+ }
55
+ const str = query.toString();
56
+ return str ? `?${str}` : "?";
57
+ }
58
+ function pageWindow(page, total) {
59
+ const keep = new Set([1, total, page - 1, page, page + 1]);
60
+ const sorted = [...keep].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
61
+ const out = [];
62
+ let prev = 0;
63
+ for (const p of sorted) {
64
+ if (p - prev > 1)
65
+ out.push("…");
66
+ out.push(p);
67
+ prev = p;
68
+ }
69
+ return out;
70
+ }
71
+
72
+ // src/react/page/components/DataTable.tsx
73
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
74
+ function statusTag(value) {
75
+ if (typeof value !== "string")
76
+ return null;
77
+ const text = value.toLowerCase();
78
+ if (text === "active")
79
+ return "bg-success-muted text-success-muted-foreground";
80
+ if (text === "archived")
81
+ return "bg-warning-muted text-warning-muted-foreground";
82
+ return "bg-badge-background text-badge-foreground";
83
+ }
84
+ function cell(value) {
85
+ if (value === null || value === undefined)
86
+ return "";
87
+ if (value instanceof Date)
88
+ return value.toLocaleString("en-US");
89
+ if (typeof value === "object")
90
+ return JSON.stringify(value);
91
+ return String(value);
92
+ }
93
+ function isNumericColumn(rows, col) {
94
+ if (rows.length === 0)
95
+ return false;
96
+ return rows.every((r) => typeof r[col] === "number" || r[col] == null);
97
+ }
98
+ function DataTable({
99
+ columns,
100
+ rows,
101
+ sortable,
102
+ params,
103
+ selectable,
104
+ editable,
105
+ primaryKey
106
+ }) {
107
+ const activeSort = typeof params.sort === "string" ? params.sort : "";
108
+ const activeOrder = params.order === "desc" ? "desc" : "asc";
109
+ const pk = primaryKey ?? "id";
110
+ const extra = (selectable ? 1 : 0) + (editable ? 1 : 0);
111
+ const numeric = new Set(columns.filter((c) => isNumericColumn(rows, c)));
112
+ const cellClass = (col) => `border-table-border border-t px-3 py-2.5 ${numeric.has(col) ? "text-right tabular-nums" : ""}`;
113
+ if (rows.length === 0)
114
+ return /* @__PURE__ */ jsxDEV2("div", {
115
+ className: "border-border bg-table overflow-x-auto rounded-lg border",
116
+ children: /* @__PURE__ */ jsxDEV2("table", {
117
+ className: "w-full border-collapse text-left text-sm",
118
+ "data-selectable": selectable ? true : undefined,
119
+ "data-editable": editable ? true : undefined,
120
+ "data-pk": primaryKey ?? "id",
121
+ children: /* @__PURE__ */ jsxDEV2("tbody", {
122
+ "data-panel-rows": true,
123
+ children: /* @__PURE__ */ jsxDEV2("tr", {
124
+ children: /* @__PURE__ */ jsxDEV2("td", {
125
+ colSpan: columns.length + extra || 1,
126
+ className: "text-text-muted px-4 py-10 text-center",
127
+ children: "No records found."
128
+ }, undefined, false, undefined, this)
129
+ }, undefined, false, undefined, this)
130
+ }, undefined, false, undefined, this)
131
+ }, undefined, false, undefined, this)
132
+ }, undefined, false, undefined, this);
133
+ return /* @__PURE__ */ jsxDEV2("div", {
134
+ className: "border-border bg-table overflow-x-auto rounded-lg border",
135
+ children: /* @__PURE__ */ jsxDEV2("table", {
136
+ className: "w-full border-collapse text-left text-sm",
137
+ "data-selectable": selectable ? true : undefined,
138
+ "data-editable": editable ? true : undefined,
139
+ "data-pk": primaryKey ?? "id",
140
+ children: [
141
+ /* @__PURE__ */ jsxDEV2("thead", {
142
+ children: /* @__PURE__ */ jsxDEV2("tr", {
143
+ children: [
144
+ selectable ? /* @__PURE__ */ jsxDEV2("th", {
145
+ className: "border-border w-10 border-b px-3 py-2",
146
+ children: /* @__PURE__ */ jsxDEV2("input", {
147
+ type: "checkbox",
148
+ "data-select-all": true,
149
+ "aria-label": "Select all",
150
+ className: "accent-primary size-4"
151
+ }, undefined, false, undefined, this)
152
+ }, undefined, false, undefined, this) : null,
153
+ columns.map((col) => /* @__PURE__ */ jsxDEV2("th", {
154
+ "data-col": col,
155
+ className: `border-border text-text-muted border-b px-3 py-2 text-xs font-medium ${numeric.has(col) ? "text-right" : ""}`,
156
+ children: sortable?.includes(col) ? /* @__PURE__ */ jsxDEV2("a", {
157
+ className: "hover:text-foreground text-link",
158
+ "data-qlink": true,
159
+ "data-sort-link": col,
160
+ href: href(params, {
161
+ sort: col,
162
+ order: activeSort === col && activeOrder === "asc" ? "desc" : "asc"
163
+ }),
164
+ children: [
165
+ col,
166
+ /* @__PURE__ */ jsxDEV2("span", {
167
+ "data-sort-ind": true,
168
+ children: activeSort === col ? activeOrder === "asc" ? " ▲" : " ▼" : ""
169
+ }, undefined, false, undefined, this)
170
+ ]
171
+ }, undefined, true, undefined, this) : col
172
+ }, col, false, undefined, this)),
173
+ editable ? /* @__PURE__ */ jsxDEV2("th", {
174
+ className: "border-border text-text-muted border-b px-3 py-2 text-right text-xs font-medium",
175
+ children: "Actions"
176
+ }, undefined, false, undefined, this) : null
177
+ ]
178
+ }, undefined, true, undefined, this)
179
+ }, undefined, false, undefined, this),
180
+ /* @__PURE__ */ jsxDEV2("tbody", {
181
+ "data-panel-rows": true,
182
+ children: rows.map((row, i) => /* @__PURE__ */ jsxDEV2("tr", {
183
+ className: "hover:bg-table-row-hover",
184
+ children: [
185
+ selectable ? /* @__PURE__ */ jsxDEV2("td", {
186
+ className: "border-table-border border-t px-3 py-2.5",
187
+ children: /* @__PURE__ */ jsxDEV2("input", {
188
+ type: "checkbox",
189
+ "data-select-row": true,
190
+ value: String(row[pk] ?? ""),
191
+ "aria-label": `Select row ${String(row[pk] ?? i)}`,
192
+ className: "accent-primary size-4"
193
+ }, undefined, false, undefined, this)
194
+ }, undefined, false, undefined, this) : null,
195
+ columns.map((col) => /* @__PURE__ */ jsxDEV2("td", {
196
+ className: cellClass(col),
197
+ children: col === "status" && statusTag(row[col]) ? /* @__PURE__ */ jsxDEV2("span", {
198
+ className: `rounded-sm px-1.5 py-0.5 text-xs font-medium ${statusTag(row[col]) ?? ""}`,
199
+ children: cell(row[col])
200
+ }, undefined, false, undefined, this) : cell(row[col])
201
+ }, col, false, undefined, this)),
202
+ editable ? /* @__PURE__ */ jsxDEV2("td", {
203
+ className: "border-table-border whitespace-nowrap border-t px-3 py-2.5 text-right",
204
+ children: [
205
+ /* @__PURE__ */ jsxDEV2("button", {
206
+ type: "button",
207
+ "data-edit-id": String(row[pk] ?? ""),
208
+ className: "text-link hover:text-link-hover px-1 text-sm font-medium hover:underline",
209
+ children: "Edit"
210
+ }, undefined, false, undefined, this),
211
+ /* @__PURE__ */ jsxDEV2("button", {
212
+ type: "button",
213
+ "data-delete-id": String(row[pk] ?? ""),
214
+ className: "text-destructive ml-3 px-1 text-sm font-medium hover:underline",
215
+ children: "Delete"
216
+ }, undefined, false, undefined, this)
217
+ ]
218
+ }, undefined, true, undefined, this) : null
219
+ ]
220
+ }, i, true, undefined, this))
221
+ }, undefined, false, undefined, this)
222
+ ]
223
+ }, undefined, true, undefined, this)
224
+ }, undefined, false, undefined, this);
225
+ }
226
+
227
+ // src/react/page/components/Pagination.tsx
228
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
229
+ var pageClass = "border-border rounded-md border px-2.5 py-1 text-xs hover:bg-navigation-hover";
230
+ var currentClass = "bg-primary text-primary-foreground rounded-md px-2.5 py-1 text-xs font-medium";
231
+ var disabledClass = "border-border text-text-disabled rounded-md border px-2.5 py-1 text-xs";
232
+ function Pagination({ page, totalPages, total, limit, params }) {
233
+ const from = total === 0 ? 0 : (page - 1) * limit + 1;
234
+ const to = Math.min(page * limit, total);
235
+ return /* @__PURE__ */ jsxDEV3("nav", {
236
+ className: "flex flex-wrap items-center justify-between gap-3 py-3 text-sm",
237
+ children: [
238
+ /* @__PURE__ */ jsxDEV3("p", {
239
+ className: "text-text-muted text-xs",
240
+ "data-panel-count": true,
241
+ children: total === 0 ? "0 records" : `${from}–${to} of ${total} records`
242
+ }, undefined, false, undefined, this),
243
+ /* @__PURE__ */ jsxDEV3("div", {
244
+ className: "flex items-center gap-1",
245
+ "data-panel-pages": true,
246
+ children: [
247
+ page > 1 ? /* @__PURE__ */ jsxDEV3("a", {
248
+ className: pageClass,
249
+ "data-qlink": true,
250
+ href: href(params, { page: page - 1 }),
251
+ children: "Previous"
252
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("span", {
253
+ className: disabledClass,
254
+ children: "Previous"
255
+ }, undefined, false, undefined, this),
256
+ pageWindow(page, totalPages).map((p, i) => p === "…" ? /* @__PURE__ */ jsxDEV3("span", {
257
+ className: "text-text-muted px-1",
258
+ children: "…"
259
+ }, `e${i}`, false, undefined, this) : p === page ? /* @__PURE__ */ jsxDEV3("span", {
260
+ className: currentClass,
261
+ children: p
262
+ }, p, false, undefined, this) : /* @__PURE__ */ jsxDEV3("a", {
263
+ className: pageClass,
264
+ "data-qlink": true,
265
+ href: href(params, { page: p }),
266
+ children: p
267
+ }, p, false, undefined, this)),
268
+ page < totalPages ? /* @__PURE__ */ jsxDEV3("a", {
269
+ className: pageClass,
270
+ "data-qlink": true,
271
+ href: href(params, { page: page + 1 }),
272
+ children: "Next"
273
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("span", {
274
+ className: disabledClass,
275
+ children: "Next"
276
+ }, undefined, false, undefined, this)
277
+ ]
278
+ }, undefined, true, undefined, this)
279
+ ]
280
+ }, undefined, true, undefined, this);
281
+ }
282
+
283
+ // src/react/page/components/Toolbar.tsx
284
+ import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
285
+ var inputClass = "border-input bg-input-background text-foreground placeholder:text-form-placeholder focus:border-ring h-8 rounded-md border px-2.5 text-sm";
286
+ var labelClass = "flex flex-col gap-1";
287
+ function Toolbar({ searchable, filterable, sortable, params }) {
288
+ if (!searchable?.length && !filterable?.length && !sortable?.length)
289
+ return null;
290
+ return /* @__PURE__ */ jsxDEV4("form", {
291
+ method: "get",
292
+ action: "",
293
+ className: "flex flex-wrap items-end gap-3 pb-4",
294
+ children: [
295
+ searchable && searchable.length > 0 ? /* @__PURE__ */ jsxDEV4("label", {
296
+ className: labelClass,
297
+ children: [
298
+ /* @__PURE__ */ jsxDEV4("span", {
299
+ className: "text-text-muted text-xs font-medium",
300
+ children: "Search"
301
+ }, undefined, false, undefined, this),
302
+ /* @__PURE__ */ jsxDEV4("input", {
303
+ name: "search",
304
+ "data-search-input": true,
305
+ defaultValue: String(params.search ?? ""),
306
+ placeholder: searchable.join(", "),
307
+ autoComplete: "off",
308
+ className: `${inputClass} w-56 max-w-full`
309
+ }, undefined, false, undefined, this)
310
+ ]
311
+ }, undefined, true, undefined, this) : null,
312
+ (filterable ?? []).map((field) => /* @__PURE__ */ jsxDEV4("label", {
313
+ className: labelClass,
314
+ children: [
315
+ /* @__PURE__ */ jsxDEV4("span", {
316
+ className: "text-text-muted text-xs font-medium",
317
+ children: field
318
+ }, undefined, false, undefined, this),
319
+ /* @__PURE__ */ jsxDEV4("input", {
320
+ name: field,
321
+ defaultValue: String(params[field] ?? ""),
322
+ className: `${inputClass} w-40 max-w-full`
323
+ }, undefined, false, undefined, this)
324
+ ]
325
+ }, field, true, undefined, this)),
326
+ sortable && sortable.length > 0 ? /* @__PURE__ */ jsxDEV4(Fragment, {
327
+ children: [
328
+ /* @__PURE__ */ jsxDEV4("label", {
329
+ className: labelClass,
330
+ children: [
331
+ /* @__PURE__ */ jsxDEV4("span", {
332
+ className: "text-text-muted text-xs font-medium",
333
+ children: "Sort"
334
+ }, undefined, false, undefined, this),
335
+ /* @__PURE__ */ jsxDEV4("select", {
336
+ name: "sort",
337
+ defaultValue: String(params.sort ?? ""),
338
+ className: inputClass,
339
+ children: [
340
+ /* @__PURE__ */ jsxDEV4("option", {
341
+ value: "",
342
+ children: "Default"
343
+ }, undefined, false, undefined, this),
344
+ sortable.map((field) => /* @__PURE__ */ jsxDEV4("option", {
345
+ value: field,
346
+ children: field
347
+ }, field, false, undefined, this))
348
+ ]
349
+ }, undefined, true, undefined, this)
350
+ ]
351
+ }, undefined, true, undefined, this),
352
+ /* @__PURE__ */ jsxDEV4("label", {
353
+ className: labelClass,
354
+ children: [
355
+ /* @__PURE__ */ jsxDEV4("span", {
356
+ className: "text-text-muted text-xs font-medium",
357
+ children: "Order"
358
+ }, undefined, false, undefined, this),
359
+ /* @__PURE__ */ jsxDEV4("select", {
360
+ name: "order",
361
+ defaultValue: String(params.order ?? ""),
362
+ className: inputClass,
363
+ children: [
364
+ /* @__PURE__ */ jsxDEV4("option", {
365
+ value: "asc",
366
+ children: "Ascending"
367
+ }, undefined, false, undefined, this),
368
+ /* @__PURE__ */ jsxDEV4("option", {
369
+ value: "desc",
370
+ children: "Descending"
371
+ }, undefined, false, undefined, this)
372
+ ]
373
+ }, undefined, true, undefined, this)
374
+ ]
375
+ }, undefined, true, undefined, this)
376
+ ]
377
+ }, undefined, true, undefined, this) : null,
378
+ /* @__PURE__ */ jsxDEV4("button", {
379
+ type: "submit",
380
+ className: "border-border bg-background text-foreground hover:bg-navigation-hover h-8 rounded-md border px-3.5 text-sm font-medium",
381
+ children: "Apply"
382
+ }, undefined, false, undefined, this)
383
+ ]
384
+ }, undefined, true, undefined, this);
385
+ }
386
+
387
+ // src/react/page/Panel.tsx
388
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
389
+ var inputClass2 = "border-input bg-input-background text-foreground placeholder:text-form-placeholder h-9 rounded-md border px-2.5 text-sm";
390
+ var fieldLabelClass = "flex flex-col gap-1.5 text-sm font-medium text-form-label";
391
+ var secondaryButtonClass = "border-border bg-background text-foreground hover:bg-navigation-hover rounded-md border px-3.5 py-1.5 text-sm font-medium";
392
+ var primaryButtonClass = "bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-3.5 py-1.5 text-sm font-medium";
393
+ function FieldInput({ field, required }) {
394
+ switch (field.kind) {
395
+ case "enum":
396
+ return /* @__PURE__ */ jsxDEV5("label", {
397
+ className: fieldLabelClass,
398
+ children: [
399
+ field.name,
400
+ /* @__PURE__ */ jsxDEV5("select", {
401
+ name: field.name,
402
+ required,
403
+ defaultValue: "",
404
+ className: inputClass2,
405
+ children: [
406
+ /* @__PURE__ */ jsxDEV5("option", {
407
+ value: "",
408
+ children: "Select"
409
+ }, undefined, false, undefined, this),
410
+ (field.enumValues ?? []).map((v) => /* @__PURE__ */ jsxDEV5("option", {
411
+ value: v,
412
+ children: v
413
+ }, v, false, undefined, this))
414
+ ]
415
+ }, undefined, true, undefined, this)
416
+ ]
417
+ }, undefined, true, undefined, this);
418
+ case "boolean":
419
+ return /* @__PURE__ */ jsxDEV5("label", {
420
+ className: `${fieldLabelClass} flex-row items-center gap-2`,
421
+ children: [
422
+ /* @__PURE__ */ jsxDEV5("input", {
423
+ type: "checkbox",
424
+ name: field.name,
425
+ value: "true",
426
+ className: "accent-primary size-4"
427
+ }, undefined, false, undefined, this),
428
+ field.name
429
+ ]
430
+ }, undefined, true, undefined, this);
431
+ case "integer":
432
+ case "number":
433
+ return /* @__PURE__ */ jsxDEV5("label", {
434
+ className: fieldLabelClass,
435
+ children: [
436
+ field.name,
437
+ /* @__PURE__ */ jsxDEV5("input", {
438
+ type: "number",
439
+ step: field.kind === "number" ? "any" : "1",
440
+ name: field.name,
441
+ required,
442
+ className: inputClass2
443
+ }, undefined, false, undefined, this)
444
+ ]
445
+ }, undefined, true, undefined, this);
446
+ case "date":
447
+ return /* @__PURE__ */ jsxDEV5("label", {
448
+ className: fieldLabelClass,
449
+ children: [
450
+ field.name,
451
+ /* @__PURE__ */ jsxDEV5("input", {
452
+ type: "datetime-local",
453
+ name: field.name,
454
+ required,
455
+ className: inputClass2
456
+ }, undefined, false, undefined, this)
457
+ ]
458
+ }, undefined, true, undefined, this);
459
+ case "json":
460
+ return /* @__PURE__ */ jsxDEV5("label", {
461
+ className: fieldLabelClass,
462
+ children: [
463
+ field.name,
464
+ /* @__PURE__ */ jsxDEV5("textarea", {
465
+ name: field.name,
466
+ "data-json": true,
467
+ rows: 3,
468
+ placeholder: "{}",
469
+ className: `${inputClass2} font-mono`
470
+ }, undefined, false, undefined, this)
471
+ ]
472
+ }, undefined, true, undefined, this);
473
+ default:
474
+ return /* @__PURE__ */ jsxDEV5("label", {
475
+ className: fieldLabelClass,
476
+ children: [
477
+ field.name,
478
+ /* @__PURE__ */ jsxDEV5("input", {
479
+ name: field.name,
480
+ required,
481
+ className: inputClass2
482
+ }, undefined, false, undefined, this)
483
+ ]
484
+ }, undefined, true, undefined, this);
485
+ }
486
+ }
487
+ function itemLabel(r) {
488
+ return r.metadata.pluralLabel ?? r.metadata.label ?? r.metadata.id;
489
+ }
490
+ function toSidebarItem(node, activeId) {
491
+ return {
492
+ id: node.metadata.id,
493
+ label: itemLabel(node),
494
+ icon: node.metadata.icon,
495
+ href: node.path,
496
+ active: node.metadata.id === activeId,
497
+ children: node.children.filter((c) => !c.metadata.hidden).map((c) => toSidebarItem(c, activeId))
498
+ };
499
+ }
500
+ function buildGroupsFromTree(tree, activeId) {
501
+ const byGroup = new Map;
502
+ for (const root of tree) {
503
+ if (root.metadata.hidden)
504
+ continue;
505
+ const name = root.metadata.group ?? "General";
506
+ let group = byGroup.get(name);
507
+ if (!group) {
508
+ group = { name, items: [] };
509
+ byGroup.set(name, group);
510
+ }
511
+ group.items.push(toSidebarItem(root, activeId));
512
+ }
513
+ return [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
514
+ }
515
+ function plural(count, word) {
516
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
517
+ }
518
+ async function Panel(props) {
519
+ const params = props.params ?? {};
520
+ const page = Math.max(Number(params.page ?? 1) || 1, 1);
521
+ const limit = Math.max(Number(params.limit ?? props.query?.pagination?.default ?? 20) || 1, 1);
522
+ let rows = [];
523
+ let total = 0;
524
+ if (props.load) {
525
+ try {
526
+ const result = await props.load(params);
527
+ rows = result.data;
528
+ total = result.total;
529
+ } catch {}
530
+ }
531
+ const columns = rows.length > 0 ? Object.keys(rows[0] ?? {}).filter((c) => c !== "deletedAt") : [];
532
+ const title = props.metadata?.pluralLabel ?? props.metadata?.label ?? props.resource ?? "Panel";
533
+ const totalPages = Math.max(1, Math.ceil(total / limit));
534
+ const pk = props.primaryKey ?? "id";
535
+ const deletedAt = props.deletedAtField ?? "deletedAt";
536
+ const fieldDefs = props.fields?.length ? props.fields.filter((f) => !f.autoIncrement && f.name !== deletedAt).map((f) => ({
537
+ name: f.name,
538
+ kind: f.kind,
539
+ nullable: f.nullable,
540
+ enumValues: f.enumValues
541
+ })) : columns.filter((c) => c !== deletedAt).map((name) => ({
542
+ name,
543
+ kind: "text",
544
+ nullable: name === pk
545
+ }));
546
+ const createFields = fieldDefs;
547
+ const editFields = fieldDefs.filter((f) => f.name !== pk);
548
+ const isRequired = (f) => !f.nullable && f.kind !== "boolean";
549
+ const canCreate = props.operations?.create !== false;
550
+ const canEdit = props.operations?.update !== false || props.operations?.replace !== false;
551
+ const canDelete = props.operations?.delete !== false;
552
+ const selectable = canEdit || canDelete;
553
+ const tree = getRackTree().filter((r) => !r.metadata.hidden);
554
+ const groups = buildGroupsFromTree(tree, props.resource);
555
+ const donate = resolveDonateProp(props.donate);
556
+ const trail = [
557
+ { label: "Dashboard", href: "/" },
558
+ ...props.metadata?.group ? [{ label: props.metadata.group }] : [],
559
+ { label: title }
560
+ ];
561
+ return /* @__PURE__ */ jsxDEV5("div", {
562
+ className: "text-foreground flex min-h-screen flex-col lg:flex-row",
563
+ children: [
564
+ /* @__PURE__ */ jsxDEV5(Sidebar, {
565
+ title: "Panel",
566
+ groups
567
+ }, undefined, false, undefined, this),
568
+ /* @__PURE__ */ jsxDEV5("main", {
569
+ className: "min-w-0 flex-1",
570
+ children: /* @__PURE__ */ jsxDEV5("div", {
571
+ className: "mx-auto w-full max-w-5xl px-4 py-4 sm:px-6",
572
+ children: [
573
+ /* @__PURE__ */ jsxDEV5(Breadcrumb, {
574
+ trail
575
+ }, undefined, false, undefined, this),
576
+ /* @__PURE__ */ jsxDEV5("div", {
577
+ "data-query-url": props.queryUrl,
578
+ children: [
579
+ /* @__PURE__ */ jsxDEV5(Masthead, {
580
+ title,
581
+ detail: plural(total, "record"),
582
+ actions: canCreate ? /* @__PURE__ */ jsxDEV5("button", {
583
+ type: "button",
584
+ "data-open-create": true,
585
+ className: primaryButtonClass,
586
+ children: "New record"
587
+ }, undefined, false, undefined, this) : undefined
588
+ }, undefined, false, undefined, this),
589
+ /* @__PURE__ */ jsxDEV5(Toolbar, {
590
+ searchable: props.query?.searchable,
591
+ filterable: props.query?.filterable,
592
+ sortable: props.query?.sortable,
593
+ params
594
+ }, undefined, false, undefined, this),
595
+ /* @__PURE__ */ jsxDEV5("div", {
596
+ "data-bulk-bar": true,
597
+ style: { display: "none" },
598
+ className: "flex flex-wrap items-center gap-2 pb-3",
599
+ children: [
600
+ /* @__PURE__ */ jsxDEV5("span", {
601
+ "data-bulk-count": true,
602
+ className: "text-text-muted text-xs"
603
+ }, undefined, false, undefined, this),
604
+ canDelete ? /* @__PURE__ */ jsxDEV5("button", {
605
+ type: "button",
606
+ "data-bulk-delete": true,
607
+ className: "bg-destructive text-destructive-foreground rounded-md px-3 py-1.5 text-xs font-medium",
608
+ children: "Delete selected"
609
+ }, undefined, false, undefined, this) : null,
610
+ canEdit ? /* @__PURE__ */ jsxDEV5("span", {
611
+ className: "inline-flex items-center gap-2",
612
+ children: [
613
+ /* @__PURE__ */ jsxDEV5("select", {
614
+ "data-bulk-field": true,
615
+ "aria-label": "Bulk field",
616
+ className: "border-input bg-input-background h-8 rounded-md border px-2 text-xs",
617
+ children: editFields.map((f) => /* @__PURE__ */ jsxDEV5("option", {
618
+ value: f.name,
619
+ children: f.name
620
+ }, f.name, false, undefined, this))
621
+ }, undefined, false, undefined, this),
622
+ /* @__PURE__ */ jsxDEV5("input", {
623
+ "data-bulk-value": true,
624
+ placeholder: "New value",
625
+ "aria-label": "Bulk value",
626
+ className: "border-input bg-input-background placeholder:text-form-placeholder h-8 rounded-md border px-2 text-xs"
627
+ }, undefined, false, undefined, this),
628
+ /* @__PURE__ */ jsxDEV5("button", {
629
+ type: "button",
630
+ "data-bulk-apply": true,
631
+ className: "bg-secondary text-secondary-foreground hover:bg-navigation-hover rounded-md px-3 py-1.5 text-xs font-medium",
632
+ children: "Apply"
633
+ }, undefined, false, undefined, this)
634
+ ]
635
+ }, undefined, true, undefined, this) : null,
636
+ /* @__PURE__ */ jsxDEV5("button", {
637
+ type: "button",
638
+ "data-bulk-clear": true,
639
+ className: "text-text-muted hover:text-foreground text-xs underline",
640
+ children: "Clear"
641
+ }, undefined, false, undefined, this)
642
+ ]
643
+ }, undefined, true, undefined, this),
644
+ /* @__PURE__ */ jsxDEV5(DataTable, {
645
+ columns,
646
+ rows,
647
+ sortable: props.query?.sortable,
648
+ params,
649
+ selectable,
650
+ editable: canEdit || canDelete,
651
+ primaryKey: pk
652
+ }, undefined, false, undefined, this),
653
+ /* @__PURE__ */ jsxDEV5(Pagination, {
654
+ page,
655
+ totalPages,
656
+ total,
657
+ limit,
658
+ params
659
+ }, undefined, false, undefined, this),
660
+ /* @__PURE__ */ jsxDEV5("dialog", {
661
+ id: "rack-create",
662
+ className: "bg-card text-card-foreground w-full max-w-md rounded-lg border p-0 shadow-lg",
663
+ children: /* @__PURE__ */ jsxDEV5("form", {
664
+ "data-create-form": true,
665
+ className: "flex flex-col gap-4 p-5",
666
+ children: [
667
+ /* @__PURE__ */ jsxDEV5("h2", {
668
+ className: "font-display text-xl leading-tight",
669
+ children: "New record"
670
+ }, undefined, false, undefined, this),
671
+ createFields.length > 0 ? createFields.map((f) => /* @__PURE__ */ jsxDEV5(FieldInput, {
672
+ field: f,
673
+ required: isRequired(f)
674
+ }, f.name, false, undefined, this)) : /* @__PURE__ */ jsxDEV5("label", {
675
+ className: "flex flex-col gap-1.5 text-sm font-medium text-form-label",
676
+ children: [
677
+ "JSON body",
678
+ /* @__PURE__ */ jsxDEV5("textarea", {
679
+ "data-create-json": true,
680
+ rows: 4,
681
+ placeholder: '{"name": "..."}',
682
+ className: `${inputClass2} h-auto font-mono`
683
+ }, undefined, false, undefined, this)
684
+ ]
685
+ }, undefined, true, undefined, this),
686
+ /* @__PURE__ */ jsxDEV5("p", {
687
+ "data-form-error": true,
688
+ className: "text-form-error text-sm"
689
+ }, undefined, false, undefined, this),
690
+ /* @__PURE__ */ jsxDEV5("div", {
691
+ className: "border-border flex justify-end gap-2 border-t pt-4",
692
+ children: [
693
+ /* @__PURE__ */ jsxDEV5("button", {
694
+ type: "button",
695
+ "data-close": true,
696
+ className: secondaryButtonClass,
697
+ children: "Cancel"
698
+ }, undefined, false, undefined, this),
699
+ /* @__PURE__ */ jsxDEV5("button", {
700
+ type: "submit",
701
+ className: primaryButtonClass,
702
+ children: "Create"
703
+ }, undefined, false, undefined, this)
704
+ ]
705
+ }, undefined, true, undefined, this)
706
+ ]
707
+ }, undefined, true, undefined, this)
708
+ }, undefined, false, undefined, this),
709
+ /* @__PURE__ */ jsxDEV5("dialog", {
710
+ id: "rack-edit",
711
+ className: "bg-card text-card-foreground w-full max-w-md rounded-lg border p-0 shadow-lg",
712
+ children: /* @__PURE__ */ jsxDEV5("form", {
713
+ "data-edit-form": true,
714
+ className: "flex flex-col gap-4 p-5",
715
+ children: [
716
+ /* @__PURE__ */ jsxDEV5("h2", {
717
+ className: "font-display text-xl leading-tight",
718
+ children: [
719
+ "Edit record",
720
+ " ",
721
+ /* @__PURE__ */ jsxDEV5("span", {
722
+ "data-edit-id-label": true,
723
+ className: "text-text-muted font-mono text-sm"
724
+ }, undefined, false, undefined, this)
725
+ ]
726
+ }, undefined, true, undefined, this),
727
+ editFields.map((f) => /* @__PURE__ */ jsxDEV5(FieldInput, {
728
+ field: f
729
+ }, f.name, false, undefined, this)),
730
+ /* @__PURE__ */ jsxDEV5("p", {
731
+ "data-form-error": true,
732
+ className: "text-form-error text-sm"
733
+ }, undefined, false, undefined, this),
734
+ /* @__PURE__ */ jsxDEV5("div", {
735
+ className: "border-border flex justify-end gap-2 border-t pt-4",
736
+ children: [
737
+ /* @__PURE__ */ jsxDEV5("button", {
738
+ type: "button",
739
+ "data-close": true,
740
+ className: secondaryButtonClass,
741
+ children: "Cancel"
742
+ }, undefined, false, undefined, this),
743
+ /* @__PURE__ */ jsxDEV5("button", {
744
+ type: "submit",
745
+ className: primaryButtonClass,
746
+ children: "Save"
747
+ }, undefined, false, undefined, this)
748
+ ]
749
+ }, undefined, true, undefined, this)
750
+ ]
751
+ }, undefined, true, undefined, this)
752
+ }, undefined, false, undefined, this),
753
+ /* @__PURE__ */ jsxDEV5("script", {
754
+ type: "module",
755
+ src: "/__rack/panel-app.js"
756
+ }, undefined, false, undefined, this)
757
+ ]
758
+ }, undefined, true, undefined, this),
759
+ /* @__PURE__ */ jsxDEV5("footer", {
760
+ className: "border-border text-text-muted mt-6 flex flex-wrap items-center justify-between gap-2 border-t pt-3 text-xs",
761
+ children: [
762
+ /* @__PURE__ */ jsxDEV5("span", {
763
+ children: "elysia-rack"
764
+ }, undefined, false, undefined, this),
765
+ donate.enabled ? /* @__PURE__ */ jsxDEV5("a", {
766
+ href: donate.url,
767
+ target: "_blank",
768
+ rel: "noopener noreferrer",
769
+ className: "hover:text-foreground underline-offset-4 hover:underline",
770
+ children: donate.label
771
+ }, undefined, false, undefined, this) : null
772
+ ]
773
+ }, undefined, true, undefined, this)
774
+ ]
775
+ }, undefined, true, undefined, this)
776
+ }, undefined, false, undefined, this)
777
+ ]
778
+ }, undefined, true, undefined, this);
779
+ }
780
+ export {
781
+ Panel as default
782
+ };