@tekki-forge/runtime-admin 0.1.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 (138) hide show
  1. package/README.md +5 -0
  2. package/dist/account.d.ts +1 -0
  3. package/dist/account.js +105 -0
  4. package/dist/account.js.map +1 -0
  5. package/dist/activity.d.ts +65 -0
  6. package/dist/activity.js +299 -0
  7. package/dist/activity.js.map +1 -0
  8. package/dist/admin-access.d.ts +4 -0
  9. package/dist/admin-access.js +177 -0
  10. package/dist/admin-access.js.map +1 -0
  11. package/dist/admin.d.ts +5 -0
  12. package/dist/admin.js +129 -0
  13. package/dist/admin.js.map +1 -0
  14. package/dist/auth-pages.d.ts +13 -0
  15. package/dist/auth-pages.js +207 -0
  16. package/dist/auth-pages.js.map +1 -0
  17. package/dist/brand-art.d.ts +24 -0
  18. package/dist/brand-art.js +33 -0
  19. package/dist/brand-art.js.map +1 -0
  20. package/dist/brand.d.ts +11 -0
  21. package/dist/brand.js +16 -0
  22. package/dist/brand.js.map +1 -0
  23. package/dist/charts.d.ts +23 -0
  24. package/dist/charts.js +259 -0
  25. package/dist/charts.js.map +1 -0
  26. package/dist/command-palette.d.ts +9 -0
  27. package/dist/command-palette.js +115 -0
  28. package/dist/command-palette.js.map +1 -0
  29. package/dist/context.d.ts +242 -0
  30. package/dist/context.js +107 -0
  31. package/dist/context.js.map +1 -0
  32. package/dist/dashboard.d.ts +66 -0
  33. package/dist/dashboard.js +144 -0
  34. package/dist/dashboard.js.map +1 -0
  35. package/dist/fields.d.ts +71 -0
  36. package/dist/fields.js +326 -0
  37. package/dist/fields.js.map +1 -0
  38. package/dist/i18n/catalog.d.ts +7 -0
  39. package/dist/i18n/catalog.js +23 -0
  40. package/dist/i18n/catalog.js.map +1 -0
  41. package/dist/i18n/locale.d.ts +15 -0
  42. package/dist/i18n/locale.js +69 -0
  43. package/dist/i18n/locale.js.map +1 -0
  44. package/dist/i18n/messages/de.d.ts +3 -0
  45. package/dist/i18n/messages/de.js +627 -0
  46. package/dist/i18n/messages/de.js.map +1 -0
  47. package/dist/i18n/messages/en.d.ts +3 -0
  48. package/dist/i18n/messages/en.js +627 -0
  49. package/dist/i18n/messages/en.js.map +1 -0
  50. package/dist/i18n/messages/es.d.ts +3 -0
  51. package/dist/i18n/messages/es.js +627 -0
  52. package/dist/i18n/messages/es.js.map +1 -0
  53. package/dist/i18n/messages/fr.d.ts +3 -0
  54. package/dist/i18n/messages/fr.js +627 -0
  55. package/dist/i18n/messages/fr.js.map +1 -0
  56. package/dist/i18n/messages/pt.d.ts +3 -0
  57. package/dist/i18n/messages/pt.js +627 -0
  58. package/dist/i18n/messages/pt.js.map +1 -0
  59. package/dist/i18n/validation.d.ts +3 -0
  60. package/dist/i18n/validation.js +36 -0
  61. package/dist/i18n/validation.js.map +1 -0
  62. package/dist/illustrations.d.ts +6 -0
  63. package/dist/illustrations.js +16 -0
  64. package/dist/illustrations.js.map +1 -0
  65. package/dist/import-dialog.d.ts +11 -0
  66. package/dist/import-dialog.js +152 -0
  67. package/dist/import-dialog.js.map +1 -0
  68. package/dist/index.d.ts +38 -0
  69. package/dist/index.js +39 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/inline-edit.d.ts +18 -0
  72. package/dist/inline-edit.js +115 -0
  73. package/dist/inline-edit.js.map +1 -0
  74. package/dist/landing.d.ts +79 -0
  75. package/dist/landing.js +122 -0
  76. package/dist/landing.js.map +1 -0
  77. package/dist/locale-switcher.d.ts +4 -0
  78. package/dist/locale-switcher.js +15 -0
  79. package/dist/locale-switcher.js.map +1 -0
  80. package/dist/map.d.ts +26 -0
  81. package/dist/map.js +232 -0
  82. package/dist/map.js.map +1 -0
  83. package/dist/notifications.d.ts +12 -0
  84. package/dist/notifications.js +75 -0
  85. package/dist/notifications.js.map +1 -0
  86. package/dist/record-mutation.d.ts +19 -0
  87. package/dist/record-mutation.js +39 -0
  88. package/dist/record-mutation.js.map +1 -0
  89. package/dist/resource-detail.d.ts +4 -0
  90. package/dist/resource-detail.js +96 -0
  91. package/dist/resource-detail.js.map +1 -0
  92. package/dist/resource-form.d.ts +11 -0
  93. package/dist/resource-form.js +92 -0
  94. package/dist/resource-form.js.map +1 -0
  95. package/dist/resource-list.d.ts +10 -0
  96. package/dist/resource-list.js +154 -0
  97. package/dist/resource-list.js.map +1 -0
  98. package/dist/saved-views.d.ts +16 -0
  99. package/dist/saved-views.js +64 -0
  100. package/dist/saved-views.js.map +1 -0
  101. package/dist/schema-editor-diagram.d.ts +14 -0
  102. package/dist/schema-editor-diagram.js +54 -0
  103. package/dist/schema-editor-diagram.js.map +1 -0
  104. package/dist/schema-editor.d.ts +98 -0
  105. package/dist/schema-editor.js +571 -0
  106. package/dist/schema-editor.js.map +1 -0
  107. package/dist/shell.d.ts +10 -0
  108. package/dist/shell.js +228 -0
  109. package/dist/shell.js.map +1 -0
  110. package/dist/skeleton.d.ts +25 -0
  111. package/dist/skeleton.js +34 -0
  112. package/dist/skeleton.js.map +1 -0
  113. package/dist/theme-catalog.d.ts +71 -0
  114. package/dist/theme-catalog.js +55 -0
  115. package/dist/theme-catalog.js.map +1 -0
  116. package/dist/theme-head.d.ts +8 -0
  117. package/dist/theme-head.js +11 -0
  118. package/dist/theme-head.js.map +1 -0
  119. package/dist/theme-preview.d.ts +2 -0
  120. package/dist/theme-preview.js +23 -0
  121. package/dist/theme-preview.js.map +1 -0
  122. package/dist/theme-switcher.d.ts +20 -0
  123. package/dist/theme-switcher.js +159 -0
  124. package/dist/theme-switcher.js.map +1 -0
  125. package/dist/theme.d.ts +24 -0
  126. package/dist/theme.js +30 -0
  127. package/dist/theme.js.map +1 -0
  128. package/dist/toast.d.ts +21 -0
  129. package/dist/toast.js +51 -0
  130. package/dist/toast.js.map +1 -0
  131. package/dist/ui.d.ts +86 -0
  132. package/dist/ui.js +106 -0
  133. package/dist/ui.js.map +1 -0
  134. package/dist/views.d.ts +23 -0
  135. package/dist/views.js +238 -0
  136. package/dist/views.js.map +1 -0
  137. package/package.json +58 -0
  138. package/styles.css +778 -0
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { Bell, CheckCheck } from "lucide-react";
5
+ import Link from "next/link";
6
+ import { useRouter } from "next/navigation";
7
+ import { useEffect, useRef, useState } from "react";
8
+ import { useForge } from "./context.js";
9
+ import { ListSkeleton } from "./skeleton.js";
10
+ import { Button, Card, EmptyState, PageHeader, Pagination, cn } from "./ui.js";
11
+ function ago(iso, t, locale) {
12
+ const s = Math.round((Date.now() - new Date(iso).getTime()) / 1000);
13
+ if (s < 60)
14
+ return t("notifications.justNow");
15
+ if (s < 3600)
16
+ return t("notifications.minutesAgo", { count: Math.floor(s / 60) });
17
+ if (s < 86400)
18
+ return t("notifications.hoursAgo", { count: Math.floor(s / 3600) });
19
+ return new Date(iso).toLocaleDateString(locale);
20
+ }
21
+ /** Live updates: server-sent events, with slow polling as a fallback. */
22
+ function useNotificationStream() {
23
+ const { app } = useForge();
24
+ const queryClient = useQueryClient();
25
+ useEffect(() => {
26
+ if (typeof EventSource === "undefined")
27
+ return;
28
+ const source = new EventSource(`${app.api.prefix}/notifications/stream`, { withCredentials: true });
29
+ source.addEventListener("notification", () => void queryClient.invalidateQueries({ queryKey: ["forge", "notifications"] }));
30
+ return () => source.close();
31
+ }, [app.api.prefix, queryClient]);
32
+ }
33
+ function useMarkRead() {
34
+ const { api } = useForge();
35
+ const queryClient = useQueryClient();
36
+ return async (body) => {
37
+ await api("/notifications/read", { method: "POST", body: JSON.stringify(body) });
38
+ await queryClient.invalidateQueries({ queryKey: ["forge", "notifications"] });
39
+ };
40
+ }
41
+ export function NotificationBell({ align = "start" } = {}) {
42
+ const { api, locale, t } = useForge();
43
+ const router = useRouter();
44
+ const [open, setOpen] = useState(false);
45
+ const ref = useRef(null);
46
+ const markRead = useMarkRead();
47
+ useNotificationStream();
48
+ const q = useQuery({ queryKey: ["forge", "notifications", "latest"], queryFn: () => api("/notifications", { query: { pageSize: 8 } }), refetchInterval: 60_000 });
49
+ useEffect(() => {
50
+ const close = (e) => ref.current && !ref.current.contains(e.target) && setOpen(false);
51
+ document.addEventListener("mousedown", close);
52
+ return () => document.removeEventListener("mousedown", close);
53
+ }, []);
54
+ const unread = q.data?.unread ?? 0;
55
+ return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("button", { type: "button", "aria-label": unread ? t("notifications.labelUnread", { count: unread }) : t("notifications.title"), "aria-expanded": open, onClick: () => setOpen((o) => !o), className: "forge-btn forge-btn-ghost relative p-1.5", children: [_jsx(Bell, { className: "size-4" }), unread > 0 && (_jsx("span", { className: "absolute -top-0.5 -right-0.5 flex min-w-4 items-center justify-center rounded-full bg-danger px-1 text-[10px] leading-4 font-semibold text-white tabular-nums", children: unread > 99 ? "99+" : unread }))] }), open && (_jsxs("div", { className: cn("forge-popover absolute top-9 z-50 w-80", align === "end" ? "right-0" : "left-0"), children: [_jsxs("div", { className: "flex items-center justify-between border-b border-line px-3 py-2", children: [_jsx("span", { className: "text-sm font-semibold text-ink", children: t("notifications.title") }), unread > 0 && (_jsxs("button", { type: "button", className: "inline-flex items-center gap-1 text-xs text-link hover:underline", onClick: () => void markRead({ all: true }), children: [_jsx(CheckCheck, { className: "size-3.5" }), " ", t("notifications.markAllRead")] }))] }), _jsxs("ul", { className: "max-h-96 overflow-auto", children: [q.data?.data.length === 0 && _jsx("li", { className: "px-3 py-6 text-center text-sm text-ink-subtle", children: t("notifications.caughtUp") }), q.data?.data.map((n) => (_jsx("li", { children: _jsxs("button", { type: "button", className: "flex w-full gap-2.5 px-3 py-2.5 text-left hover:bg-hover", onClick: async () => {
56
+ if (!n.readAt)
57
+ await markRead({ ids: [n.id] });
58
+ setOpen(false);
59
+ if (n.link)
60
+ router.push(n.link);
61
+ }, children: [_jsx("span", { className: cn("mt-1.5 size-2 shrink-0 rounded-full", n.readAt ? "bg-transparent" : "bg-primary") }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: cn("block text-sm text-ink", !n.readAt && "font-medium"), children: n.title }), n.body && _jsx("span", { className: "block truncate text-xs text-ink-muted", children: n.body }), _jsx("span", { className: "block text-xs text-ink-subtle", children: ago(n.createdAt, t, locale) })] })] }) }, n.id)))] }), _jsx(Link, { href: "/notifications", onClick: () => setOpen(false), className: "block border-t border-line px-3 py-2 text-center text-xs text-link hover:underline", children: t("notifications.seeAll") })] }))] }));
62
+ }
63
+ export function NotificationsPage() {
64
+ const { api, locale, t } = useForge();
65
+ const router = useRouter();
66
+ const [page, setPage] = useState(1);
67
+ const [unreadOnly, setUnreadOnly] = useState(false);
68
+ const markRead = useMarkRead();
69
+ const q = useQuery({
70
+ queryKey: ["forge", "notifications", "page", page, unreadOnly],
71
+ queryFn: () => api("/notifications", { query: { page, pageSize: 25, ...(unreadOnly ? { unread: "true" } : {}) } }),
72
+ });
73
+ return (_jsxs("div", { children: [_jsx(PageHeader, { title: t("notifications.title"), actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: unreadOnly ? "primary" : "secondary", onClick: () => (setUnreadOnly(!unreadOnly), setPage(1)), children: t("notifications.unreadOnly") }), _jsxs(Button, { onClick: () => void markRead({ all: true }), disabled: !q.data?.unread, children: [_jsx(CheckCheck, { className: "size-4" }), " ", t("notifications.markAllRead")] })] }) }), _jsxs(Card, { children: [q.isLoading ? (_jsx(ListSkeleton, {})) : q.data.data.length === 0 ? (_jsx(EmptyState, { illustration: "notifications", title: t("notifications.emptyTitle"), description: t("notifications.emptyDescription") })) : (_jsx("ul", { className: "divide-y divide-line", children: q.data.data.map((n) => (_jsxs("li", { className: "flex items-start gap-3 px-4 py-3", children: [_jsx("span", { className: cn("mt-1.5 size-2 shrink-0 rounded-full", n.readAt ? "bg-transparent" : "bg-primary") }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: cn("text-sm text-ink", !n.readAt && "font-medium"), children: n.title }), n.body && _jsx("p", { className: "text-sm text-ink-muted", children: n.body }), _jsx("p", { className: "text-xs text-ink-subtle", children: new Date(n.createdAt).toLocaleString(locale) })] }), n.link && (_jsx(Button, { size: "sm", variant: "ghost", onClick: async () => (n.readAt ? undefined : await markRead({ ids: [n.id] }), router.push(n.link)), children: t("common.open") })), !n.readAt && (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => void markRead({ ids: [n.id] }), children: t("notifications.markRead") }))] }, n.id))) })), q.data && q.data.meta.total > 0 && _jsx(Pagination, { page: q.data.meta.page, pages: q.data.meta.pages, total: q.data.meta.total, onPage: setPage })] })] }));
74
+ }
75
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { ResourceMeta } from "@tekki-forge/compiler";
2
+ type Row = Record<string, any>;
3
+ export interface RecordChange {
4
+ id: string;
5
+ /** PATCH body (API names). Ignored when `transition` is set. */
6
+ patch?: Row;
7
+ /** Run this workflow transition instead of a PATCH. */
8
+ transition?: string;
9
+ /** Fields merged into cached rows right away so the UI moves before the server answers. */
10
+ optimistic?: Row;
11
+ }
12
+ /**
13
+ * Update one record from a list-like view (inline cell, board card, calendar event) with an optimistic
14
+ * cache update across every cached list of the resource, rolled back if the server refuses.
15
+ */
16
+ export declare function useRecordChange(resource: Pick<ResourceMeta, "path">): import("@tanstack/react-query").UseMutationResult<Row, Error, RecordChange, {
17
+ snapshot: [readonly unknown[], unknown][];
18
+ }>;
19
+ export {};
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { useForge } from "./context.js";
4
+ /**
5
+ * Update one record from a list-like view (inline cell, board card, calendar event) with an optimistic
6
+ * cache update across every cached list of the resource, rolled back if the server refuses.
7
+ */
8
+ export function useRecordChange(resource) {
9
+ const { api } = useForge();
10
+ const queryClient = useQueryClient();
11
+ const listKey = ["resource", resource.path, "list"];
12
+ const patchRows = (id, fields) => queryClient.setQueriesData({ queryKey: listKey }, (old) => (old?.data ? { ...old, data: old.data.map((r) => (r.id === id ? { ...r, ...fields } : r)) } : old));
13
+ return useMutation({
14
+ mutationFn: ({ id, patch, transition }) => transition
15
+ ? api(`/${resource.path}/${id}/transitions/${transition}`, { method: "POST", body: "{}" })
16
+ : api(`/${resource.path}/${id}`, { method: "PATCH", body: JSON.stringify(patch ?? {}) }),
17
+ onMutate: async ({ id, optimistic }) => {
18
+ await queryClient.cancelQueries({ queryKey: listKey });
19
+ const snapshot = queryClient.getQueriesData({ queryKey: listKey });
20
+ if (optimistic)
21
+ patchRows(id, optimistic);
22
+ return { snapshot };
23
+ },
24
+ onError: (_err, _vars, ctx) => {
25
+ for (const [key, data] of ctx?.snapshot ?? [])
26
+ queryClient.setQueryData(key, data);
27
+ },
28
+ onSuccess: (saved, { id }) => {
29
+ if (saved && typeof saved === "object")
30
+ patchRows(id, saved);
31
+ },
32
+ onSettled: (_data, _err, { id }) => {
33
+ queryClient.invalidateQueries({ queryKey: listKey });
34
+ queryClient.invalidateQueries({ queryKey: ["resource", resource.path, "get", id] });
35
+ queryClient.invalidateQueries({ queryKey: ["forge", "dashboard"] });
36
+ },
37
+ });
38
+ }
39
+ //# sourceMappingURL=record-mutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-mutation.js","sourceRoot":"","sources":["../src/record-mutation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function ResourceDetailPage({ path, id }: {
2
+ path: string;
3
+ id: string;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,96 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { canOnRecord } from "@tekki-forge/runtime-permissions";
4
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import { ChevronLeft, Pencil, RotateCcw, Trash2 } from "lucide-react";
6
+ import Link from "next/link";
7
+ import { useRouter } from "next/navigation";
8
+ import { useState } from "react";
9
+ import { ActivityPanel } from "./activity.js";
10
+ import { useForge, useMe } from "./context.js";
11
+ import { FieldValue } from "./fields.js";
12
+ import { ResourceListPage } from "./resource-list.js";
13
+ import { DetailSkeleton } from "./skeleton.js";
14
+ import { useToast } from "./toast.js";
15
+ import { Alert, Button, Card, ConfirmDialog, EmptyState, PageHeader } from "./ui.js";
16
+ export function ResourceDetailPage({ path, id }) {
17
+ const { entityByPath, api, locale, t } = useForge();
18
+ const { principal } = useMe();
19
+ const router = useRouter();
20
+ const queryClient = useQueryClient();
21
+ const found = entityByPath(path);
22
+ const [confirm, setConfirm] = useState(null);
23
+ const toast = useToast();
24
+ const setNotice = (n) => (n.tone === "success" ? toast.success(n.text) : toast.error(t("toast.failed"), n.text));
25
+ const record = useQuery({
26
+ queryKey: ["resource", path, "get", id],
27
+ queryFn: () => api(`/${path}/${id}`, { query: found?.entity.features.softDelete ? { trashed: "with" } : {} }),
28
+ enabled: !!found,
29
+ });
30
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: ["resource", path] });
31
+ const remove = useMutation({
32
+ mutationFn: () => api(`/${path}/${id}`, { method: "DELETE" }),
33
+ onSuccess: () => {
34
+ invalidate();
35
+ toast.success(t("toast.deleted", { item: found?.resource.label ?? "" }));
36
+ router.push(`/${path}`);
37
+ },
38
+ onError: (e) => {
39
+ setConfirm(null);
40
+ setNotice({ tone: "error", text: e.message });
41
+ },
42
+ });
43
+ const run = useMutation({
44
+ mutationFn: (action) => api(`/${path}/${id}/actions/${action.name}`, { method: "POST", body: "{}" }),
45
+ onSuccess: (_d, action) => {
46
+ setConfirm(null);
47
+ setNotice({ tone: "success", text: t("detail.actionCompleted", { action: action.label }) });
48
+ invalidate();
49
+ },
50
+ onError: (e) => {
51
+ setConfirm(null);
52
+ setNotice({ tone: "error", text: e.message });
53
+ },
54
+ });
55
+ const transition = useMutation({
56
+ mutationFn: (t) => api(`/${path}/${id}/transitions/${t.name}`, { method: "POST", body: "{}" }),
57
+ onSuccess: (_d, tr) => {
58
+ setConfirm(null);
59
+ setNotice({ tone: "success", text: t("detail.transitioned", { transition: tr.label, state: stateLabel(tr.to) }) });
60
+ invalidate();
61
+ },
62
+ onError: (e) => {
63
+ setConfirm(null);
64
+ setNotice({ tone: "error", text: e.message });
65
+ },
66
+ });
67
+ const restore = useMutation({
68
+ mutationFn: () => api(`/${path}/${id}/restore`, { method: "POST" }),
69
+ onSuccess: () => {
70
+ setNotice({ tone: "success", text: t("detail.restored") });
71
+ invalidate();
72
+ },
73
+ });
74
+ const stateLabel = (state) => {
75
+ const wf = found?.entity.workflow;
76
+ return found?.entity.fields.find((f) => f.name === wf?.field)?.enumValues?.find((v) => v.value === state)?.label ?? state.replaceAll("_", " ");
77
+ };
78
+ if (!found)
79
+ return _jsx(EmptyState, { title: t("common.notFound") });
80
+ const { entity, resource } = found;
81
+ if (record.isLoading)
82
+ return _jsx(DetailSkeleton, { fields: Math.min(8, entity.fields.length) || 4 });
83
+ if (record.error || !record.data)
84
+ return _jsx(Alert, { children: record.error?.message ?? t("common.notFound") });
85
+ const r = record.data;
86
+ const canUpdate = canOnRecord(entity, "update", principal, r);
87
+ const canDelete = canOnRecord(entity, "delete", principal, r);
88
+ const title = r[entity.display] ? String(r[entity.display]) : `${resource.label} ${r.id.slice(0, 8)}`;
89
+ return (_jsxs("div", { children: [_jsxs(Link, { href: `/${path}`, className: "mb-3 inline-flex items-center gap-1 text-sm text-ink-muted hover:text-ink", children: [_jsx(ChevronLeft, { className: "size-4" }), " ", resource.pluralLabel] }), _jsx(PageHeader, { title: _jsxs("span", { className: "inline-flex items-center gap-2.5", children: [title, entity.workflow && _jsx(FieldValue, { field: entity.fields.find((f) => f.name === entity.workflow.field), value: r[entity.workflow.field] })] }), description: r.deletedAt ? t("detail.deleted") : undefined, actions: _jsxs(_Fragment, { children: [!r.deletedAt &&
90
+ entity.workflow?.transitions
91
+ .filter((t) => (t.from.length === 0 || t.from.includes(r[entity.workflow.field])) && canOnRecord({ ...entity, permissions: { ...entity.permissions, update: t.permission } }, "update", principal, r))
92
+ .map((t) => (_jsx(Button, { variant: t.variant === "danger" ? "danger" : "primary", loading: transition.isPending && transition.variables?.name === t.name, onClick: () => (t.confirm || t.variant === "danger" ? setConfirm({ kind: "transition", transition: t }) : transition.mutate(t)), children: t.label }, t.name))), entity.actions.map((a) => canOnRecord({ ...entity, permissions: { ...entity.permissions, update: a.permission } }, "update", principal, r) ? (_jsx(Button, { variant: a.variant === "danger" ? "danger" : "secondary", onClick: () => (a.confirm ? setConfirm({ kind: "action", action: a }) : run.mutate(a)), loading: run.isPending && run.variables?.name === a.name, children: a.label }, a.name)) : null), r.deletedAt && canUpdate && (_jsxs(Button, { onClick: () => restore.mutate(), loading: restore.isPending, children: [_jsx(RotateCcw, { className: "size-4" }), " ", t("detail.restore")] })), canUpdate && !r.deletedAt && (_jsxs(Link, { href: `/${path}/${id}/edit`, className: "forge-btn forge-btn-secondary inline-flex h-9 items-center gap-1.5 px-3.5 text-sm font-medium", children: [_jsx(Pencil, { className: "size-4" }), " ", t("common.edit")] })), canDelete && !r.deletedAt && (_jsxs(Button, { variant: "danger-ghost", onClick: () => setConfirm({ kind: "delete" }), children: [_jsx(Trash2, { className: "size-4" }), " ", t("common.delete")] }))] }) }), _jsx(Card, { children: _jsxs("dl", { className: "divide-y divide-line", children: [entity.fields
93
+ .filter((f) => !f.hidden)
94
+ .map((f) => (_jsxs("div", { className: "grid grid-cols-3 gap-4 px-6 py-3.5 text-sm", children: [_jsx("dt", { className: "font-medium text-ink-muted", children: f.label }), _jsx("dd", { className: "col-span-2 text-ink", children: f.relation?.type === "many-to-many" && Array.isArray(r[f.name]) && r[f.name].length > 0 ? (_jsx("span", { className: "flex flex-wrap gap-1.5", children: r[f.name].map((x) => f.relation.path ? (_jsx(Link, { href: `/${f.relation.path}/${x.id}`, className: "forge-badge px-2 py-0.5 text-xs hover:underline", children: x.label }, x.id)) : (_jsx("span", { className: "forge-badge px-2 py-0.5 text-xs", children: x.label }, x.id))) })) : f.relation?.type === "many-to-one" && r[f.name] && f.relation.path ? (_jsx(Link, { className: "text-link hover:underline", href: `/${f.relation.path}/${r[f.name].id}`, children: r[f.name].label })) : (_jsx(FieldValue, { field: f, value: f.relation ? r[f.name] : r[f.name] })) })] }, f.name))), entity.features.timestamps && (_jsxs("div", { className: "grid grid-cols-3 gap-4 px-6 py-3.5 text-sm", children: [_jsx("dt", { className: "font-medium text-ink-muted", children: t("detail.created") }), _jsx("dd", { className: "col-span-2 text-ink-muted tabular-nums", children: t("detail.createdUpdated", { created: new Date(r.createdAt).toLocaleString(locale), updated: new Date(r.updatedAt).toLocaleString(locale) }) })] }))] }) }), resource.detail.related.map((rel) => (_jsxs("section", { className: "mt-8", children: [_jsx("h2", { className: "mb-3 text-sm font-semibold text-ink", children: rel.label }), _jsx(ResourceListPage, { path: rel.resourcePath, fixed: { [rel.filterKey]: id }, embedded: true })] }, rel.name))), (entity.features.audit || entity.features.comments) && _jsx(ActivityPanel, { path: path, entity: entity, recordId: id, readOnly: !!r.deletedAt }), _jsx(ConfirmDialog, { open: confirm?.kind === "delete", title: t("detail.deleteTitle", { item: resource.label.toLowerCase() }), message: entity.features.softDelete ? t("detail.deleteSoft") : t("detail.deleteHard"), confirmLabel: t("common.delete"), danger: true, loading: remove.isPending, onConfirm: () => remove.mutate(), onClose: () => setConfirm(null) }), _jsx(ConfirmDialog, { open: confirm?.kind === "transition", title: confirm?.kind === "transition" ? confirm.transition.label : "", message: confirm?.kind === "transition" ? (confirm.transition.confirm ?? t("detail.transitionConfirm", { item: resource.label.toLowerCase(), state: stateLabel(confirm.transition.to) })) : "", danger: confirm?.kind === "transition" && confirm.transition.variant === "danger", loading: transition.isPending, confirmLabel: confirm?.kind === "transition" ? confirm.transition.label : t("common.confirm"), onConfirm: () => confirm?.kind === "transition" && transition.mutate(confirm.transition), onClose: () => setConfirm(null) }), _jsx(ConfirmDialog, { open: confirm?.kind === "action", title: confirm?.kind === "action" ? confirm.action.label : "", message: confirm?.kind === "action" ? confirm.action.confirm : "", danger: confirm?.kind === "action" && confirm.action.variant === "danger", loading: run.isPending, onConfirm: () => confirm?.kind === "action" && run.mutate(confirm.action), onClose: () => setConfirm(null) })] }));
95
+ }
96
+ //# sourceMappingURL=resource-detail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-detail.js","sourceRoot":"","sources":["../src/resource-detail.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { EntityIR, ResourceMeta } from "@tekki-forge/compiler";
2
+ export declare function ResourceFormPage({ path, id }: {
3
+ path: string;
4
+ id?: string;
5
+ }): import("react").JSX.Element;
6
+ export declare function ResourceForm({ entity, resource, id, initial }: {
7
+ entity: EntityIR;
8
+ resource: ResourceMeta;
9
+ id?: string;
10
+ initial?: Record<string, unknown>;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,92 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { validateInput } from "@tekki-forge/runtime-schema";
4
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import Link from "next/link";
6
+ import { useRouter, useSearchParams } from "next/navigation";
7
+ import { useEffect, useState } from "react";
8
+ import { ApiError, useForge } from "./context.js";
9
+ import { FieldInput, toFormValue, toPayloadValue } from "./fields.js";
10
+ import { FormSkeleton } from "./skeleton.js";
11
+ import { useToast } from "./toast.js";
12
+ import { Alert, Button, Card, EmptyState, PageHeader } from "./ui.js";
13
+ export function ResourceFormPage({ path, id }) {
14
+ const { entityByPath, api, t } = useForge();
15
+ const found = entityByPath(path);
16
+ const record = useQuery({
17
+ queryKey: ["resource", path, "get", id],
18
+ queryFn: () => api(`/${path}/${id}`),
19
+ enabled: !!id && !!found,
20
+ });
21
+ if (!found)
22
+ return _jsx(EmptyState, { title: t("common.notFound") });
23
+ if (id && record.isLoading)
24
+ return _jsx(FormSkeleton, { fields: Math.min(6, found.resource.form.fields.length) || 4 });
25
+ if (id && record.error)
26
+ return _jsx(Alert, { children: record.error.message });
27
+ return _jsx(ResourceForm, { ...found, id: id, initial: record.data });
28
+ }
29
+ export function ResourceForm({ entity, resource, id, initial }) {
30
+ const { api, t, tv } = useForge();
31
+ const router = useRouter();
32
+ const search = useSearchParams();
33
+ const queryClient = useQueryClient();
34
+ const fields = resource.form.fields;
35
+ const [values, setValues] = useState({});
36
+ const [errors, setErrors] = useState({});
37
+ const [formError, setFormError] = useState(null);
38
+ const toast = useToast();
39
+ useEffect(() => {
40
+ const v = {};
41
+ for (const f of fields) {
42
+ const fromRecord = initial ? (f.file ? initial[f.name] : initial[f.apiName]) : undefined;
43
+ const prefill = !initial ? search.get(f.apiName) : null;
44
+ v[f.name] = toFormValue(f, fromRecord ?? prefill ?? (initial ? null : (f.defaultValue ?? null)));
45
+ }
46
+ setValues(v);
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [initial]);
49
+ const save = useMutation({
50
+ mutationFn: (payload) => api(id ? `/${resource.path}/${id}` : `/${resource.path}`, { method: id ? "PATCH" : "POST", body: JSON.stringify(payload) }),
51
+ onSuccess: (saved) => {
52
+ toast.success(id ? t("toast.saved") : t("toast.created", { item: resource.label }));
53
+ queryClient.invalidateQueries({ queryKey: ["resource", resource.path] });
54
+ queryClient.invalidateQueries({ queryKey: ["forge", "stats"] });
55
+ router.push(`/${resource.path}/${saved.id}`);
56
+ },
57
+ onError: (err) => {
58
+ if (err instanceof ApiError && err.body?.issues?.length) {
59
+ setErrors(Object.fromEntries(err.body.issues.map((i) => [fields.find((f) => f.apiName === i.path)?.name ?? i.path, tv(i.message)])));
60
+ }
61
+ setFormError(err.message);
62
+ if (!(err instanceof ApiError && err.body?.issues?.length))
63
+ toast.error(t("toast.failed"), err.message);
64
+ },
65
+ });
66
+ const submit = (e) => {
67
+ e.preventDefault();
68
+ setFormError(null);
69
+ const payload = {};
70
+ for (const f of fields) {
71
+ const value = toPayloadValue(f, values[f.name]);
72
+ // On edit, only send what changed so concurrent edits to other fields are preserved.
73
+ if (id && initial && JSON.stringify(value) === JSON.stringify(initial[f.apiName] ?? (f.component === "switch" ? false : null)))
74
+ continue;
75
+ if (!id && value === null)
76
+ continue;
77
+ payload[f.apiName] = value;
78
+ }
79
+ const result = validateInput(entity, id ? "update" : "create", payload);
80
+ if (!result.success) {
81
+ setErrors(Object.fromEntries(result.issues.map((i) => [fields.find((f) => f.apiName === i.path)?.name ?? i.path, tv(i.message)])));
82
+ setFormError(t("form.fixErrors"));
83
+ return;
84
+ }
85
+ setErrors({});
86
+ save.mutate(payload);
87
+ };
88
+ const title = id ? t("form.editTitle", { item: resource.label.toLowerCase() }) : t("list.new", { item: resource.label.toLowerCase() });
89
+ const cancelHref = id ? `/${resource.path}/${id}` : `/${resource.path}`;
90
+ return (_jsxs("form", { onSubmit: submit, noValidate: true, className: "mx-auto max-w-2xl", children: [_jsx(PageHeader, { title: title, description: id && initial ? String(initial[entity.display] ?? "") : undefined }), _jsxs(Card, { className: "forge-form", children: [formError && _jsx(Alert, { children: formError }), fields.map((f) => (_jsx(FieldInput, { entity: entity.name, field: f, value: values[f.name], error: errors[f.name], onChange: (v) => setValues((prev) => ({ ...prev, [f.name]: v })) }, f.name)))] }), _jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [_jsx(Link, { href: cancelHref, className: "forge-btn forge-btn-ghost inline-flex h-[var(--forge-control-h)] items-center px-3.5 text-sm font-medium", children: t("common.cancel") }), _jsx(Button, { type: "submit", variant: "primary", loading: save.isPending, children: id ? t("form.saveChanges") : t("form.createItem", { item: resource.label.toLowerCase() }) })] })] }));
91
+ }
92
+ //# sourceMappingURL=resource-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-form.js","sourceRoot":"","sources":["../src/resource-form.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ type Params = Record<string, string>;
2
+ export interface ResourceListProps {
3
+ path: string;
4
+ /** Filters that are always applied and hidden from the UI (used for related lists). */
5
+ fixed?: Params;
6
+ /** Compact rendering inside another page; state is kept locally instead of in the URL. */
7
+ embedded?: boolean;
8
+ }
9
+ export declare function ResourceListPage(props: ResourceListProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,154 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { can, evaluate } from "@tekki-forge/runtime-permissions";
4
+ import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import { ArrowDown, ArrowUp, ArrowUpDown, CalendarDays, Columns3, Download, Map as MapIcon, Plus, Search, Table2, Trash2, Upload } from "lucide-react";
6
+ import Link from "next/link";
7
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
8
+ import { useEffect, useMemo, useState } from "react";
9
+ import { ApiError, useForge, useMe } from "./context.js";
10
+ import { FieldValue, RelationSelect } from "./fields.js";
11
+ import { ImportDialog } from "./import-dialog.js";
12
+ import { EditableCell, inlineField } from "./inline-edit.js";
13
+ import { MapView } from "./map.js";
14
+ import { SavedViewsMenu } from "./saved-views.js";
15
+ import { BoardView, CalendarView } from "./views.js";
16
+ import { TableSkeleton } from "./skeleton.js";
17
+ import { useToast } from "./toast.js";
18
+ import { Alert, Button, Card, ConfirmDialog, EmptyState, Input, PageHeader, Pagination, Select, cn } from "./ui.js";
19
+ function useUrlParams() {
20
+ const router = useRouter();
21
+ const pathname = usePathname();
22
+ const search = useSearchParams();
23
+ const params = useMemo(() => Object.fromEntries(search.entries()), [search]);
24
+ return [
25
+ params,
26
+ (next) => {
27
+ const qs = new URLSearchParams(Object.entries(next).filter(([, v]) => v !== "" && v !== undefined)).toString();
28
+ router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
29
+ },
30
+ ];
31
+ }
32
+ function useLocalParams() {
33
+ const [params, setParams] = useState({});
34
+ return [params, setParams];
35
+ }
36
+ export function ResourceListPage(props) {
37
+ const { entityByPath, pages, t } = useForge();
38
+ const found = entityByPath(props.path);
39
+ const PluginPage = !found && !props.embedded ? pages[props.path] : undefined;
40
+ if (PluginPage)
41
+ return _jsx(PluginPage, { path: props.path });
42
+ if (!found)
43
+ return _jsx(EmptyState, { title: t("common.notFound"), description: t("list.noResource", { path: `/${props.path}` }) });
44
+ return props.embedded ? _jsx(EmbeddedList, { ...props, ...found }) : _jsx(UrlList, { ...props, ...found });
45
+ }
46
+ function UrlList(props) {
47
+ const [params, setParams] = useUrlParams();
48
+ return _jsx(ResourceList, { ...props, params: params, setParams: setParams });
49
+ }
50
+ function EmbeddedList(props) {
51
+ const [params, setParams] = useLocalParams();
52
+ return _jsx(ResourceList, { ...props, params: params, setParams: setParams });
53
+ }
54
+ function ResourceList({ entity, resource, fixed, embedded, params, setParams, }) {
55
+ const { api, t } = useForge();
56
+ const { principal } = useMe();
57
+ const queryClient = useQueryClient();
58
+ const [selected, setSelected] = useState(new Set());
59
+ const [confirmBulk, setConfirmBulk] = useState(false);
60
+ const [searchText, setSearchText] = useState(params.search ?? "");
61
+ const [importing, setImporting] = useState(false);
62
+ // A view is offered only when the viewer can read the field it is built on for every record.
63
+ const fullyReadable = (name) => {
64
+ const rule = name ? entity.fields.find((f) => f.name === name)?.access?.read : undefined;
65
+ if (!rule)
66
+ return true;
67
+ const d = evaluate(rule, principal);
68
+ return d.allowed && d.scope === "all";
69
+ };
70
+ const viewField = { board: entity.views.board?.groupBy, calendar: entity.views.calendar?.date, map: entity.views.map?.location };
71
+ const views = VIEW_TABS.filter((v) => v.key === "table" || (entity.views[v.key] && fullyReadable(viewField[v.key])));
72
+ const view = views.find((v) => v.key === params.view)?.key ?? "table";
73
+ const { view: _view, month: _month, ...listParams } = params;
74
+ const query = { ...listParams, ...fixed, ...(embedded ? { pageSize: "10" } : {}) };
75
+ const list = useQuery({
76
+ queryKey: ["resource", resource.path, "list", query],
77
+ queryFn: () => api(`/${resource.path}`, { query }),
78
+ placeholderData: keepPreviousData,
79
+ enabled: view === "table",
80
+ });
81
+ useEffect(() => {
82
+ const t = setTimeout(() => {
83
+ if ((params.search ?? "") !== searchText)
84
+ setParams({ ...params, search: searchText, page: "" });
85
+ }, 300);
86
+ return () => clearTimeout(t);
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ }, [searchText]);
89
+ const toast = useToast();
90
+ const bulkDelete = useMutation({
91
+ mutationFn: () => api(`/${resource.path}/bulk-delete`, { method: "POST", body: JSON.stringify({ ids: [...selected] }) }),
92
+ onSuccess: () => {
93
+ toast.success(t("toast.bulkDeleted", { count: selected.size }));
94
+ setSelected(new Set());
95
+ setConfirmBulk(false);
96
+ queryClient.invalidateQueries({ queryKey: ["resource", resource.path] });
97
+ },
98
+ onError: (e) => {
99
+ setConfirmBulk(false);
100
+ toast.error(t("toast.failed"), e.message);
101
+ },
102
+ });
103
+ const set = (patch) => setParams({ ...params, ...patch });
104
+ const sort = params.sort ?? resource.list.defaultSort;
105
+ const toggleSort = (key) => set({ sort: sort === key ? `-${key}` : sort === `-${key}` ? "" : key, page: "" });
106
+ const rows = list.data?.data ?? [];
107
+ const allSelected = rows.length > 0 && rows.every((r) => selected.has(r.id));
108
+ const canCreate = can(entity, "create", principal);
109
+ const canDelete = can(entity, "delete", principal) && entity.features.bulkDelete;
110
+ const detail = (id) => `/${resource.path}/${id}`;
111
+ const newHref = `/${resource.path}/new${fixed ? `?${new URLSearchParams(Object.entries(fixed).map(([k, v]) => [`${k}Id`, v])).toString()}` : ""}`;
112
+ const fieldByName = new Map(entity.fields.map((f) => [f.name, f]));
113
+ // createdAt / updatedAt are framework columns: their labels come from the UI catalog.
114
+ const columnLabel = (c) => (c.kind === "system" ? t(`list.column.${c.key}`) : c.label);
115
+ // Columns editable in place (record-level permissions are checked per cell).
116
+ const editable = new Map(embedded || !can(entity, "update", principal) ? [] : resource.list.columns.flatMap((c) => {
117
+ const found = inlineField(entity, resource, c);
118
+ return found ? [[c.key, found]] : [];
119
+ }));
120
+ const filters = resource.list.filters.filter((f) => !fixed?.[f.key]);
121
+ const exportHref = `/api/${resource.path}/export?${new URLSearchParams(Object.entries({ ...params, ...fixed }).filter(([k, v]) => v && k !== "page" && k !== "pageSize")).toString()}`;
122
+ return (_jsxs("div", { children: [!embedded && (_jsx(PageHeader, { title: resource.pluralLabel, description: entity.description, actions: _jsxs(_Fragment, { children: [canCreate && (_jsxs(Button, { onClick: () => setImporting(true), children: [_jsx(Upload, { className: "size-4" }), " ", t("list.import")] })), entity.features.export && (_jsxs("a", { href: exportHref, className: "forge-btn forge-btn-secondary inline-flex h-9 items-center gap-1.5 px-3.5 text-sm font-medium", children: [_jsx(Download, { className: "size-4" }), " ", t("list.export")] })), canCreate && (_jsxs(Link, { href: newHref, className: "forge-btn forge-btn-primary inline-flex h-9 items-center gap-1.5 px-3.5 text-sm font-medium", children: [_jsx(Plus, { className: "size-4" }), " ", t("list.new", { item: resource.label.toLowerCase() })] }))] }) })), _jsxs(Card, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-line p-3", children: [resource.searchable && (_jsxs("div", { className: "relative min-w-52 flex-1", children: [_jsx(Search, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-ink-subtle" }), _jsx(Input, { "aria-label": t("common.search"), placeholder: t("list.searchPlaceholder", { items: resource.pluralLabel.toLowerCase() }), value: searchText, onChange: (e) => setSearchText(e.target.value), className: "h-8 pl-8" })] })), !embedded && filters.map((f) => (_jsx(FilterControl, { filter: f, params: params, onChange: (patch) => set({ ...patch, page: "" }) }, f.key))), entity.features.softDelete && !embedded && (_jsxs(Select, { "aria-label": t("list.deletedRecords"), value: params.trashed ?? "", onChange: (e) => set({ trashed: e.target.value, page: "" }), className: "h-8 !w-auto py-0 text-xs", children: [_jsx("option", { value: "", children: t("list.trashed.active") }), _jsx("option", { value: "only", children: t("list.trashed.deleted") }), _jsx("option", { value: "with", children: t("list.trashed.all") })] })), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [!embedded && views.length > 1 && (_jsx("div", { role: "tablist", "aria-label": t("list.view"), className: "forge-input flex h-8 items-center gap-0.5 p-0.5", children: views.map((v) => (_jsxs("button", { type: "button", role: "tab", "aria-selected": view === v.key, title: t(v.label), onClick: () => set({ view: v.key === "table" ? "" : v.key, page: "" }), className: cn("flex h-full items-center gap-1 rounded px-2 text-xs", view === v.key ? "bg-selected text-ink" : "text-ink-muted hover:text-ink"), children: [v.icon, _jsx("span", { className: "hidden sm:inline", children: t(v.label) })] }, v.key))) })), !embedded && _jsx(SavedViewsMenu, { resource: resource, params: params, setParams: setParams }), embedded && canCreate && (_jsxs(Link, { href: newHref, className: "forge-btn forge-btn-secondary inline-flex h-7 items-center gap-1 px-2.5 text-xs font-medium", children: [_jsx(Plus, { className: "size-3.5" }), " ", t("list.add")] })), selected.size > 0 && canDelete && (_jsxs(Button, { size: "sm", variant: "danger", onClick: () => setConfirmBulk(true), children: [_jsx(Trash2, { className: "size-3.5" }), " ", t("list.deleteSelected", { count: selected.size })] }))] })] }), view === "board" && _jsx(BoardView, { entity: entity, resource: resource, params: params, ...(fixed ? { fixed } : {}) }), view === "calendar" && _jsx(CalendarView, { entity: entity, resource: resource, params: params, setParams: setParams, ...(fixed ? { fixed } : {}) }), view === "map" && _jsx(MapView, { entity: entity, resource: resource, params: params, ...(fixed ? { fixed } : {}) }), view === "table" && list.error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: list.error.message }) })), view !== "table" ? null : list.isLoading ? (_jsx(TableSkeleton, { columns: Math.min(5, resource.list.columns.length) || 3 })) : rows.length === 0 ? (_jsx(EmptyState, { illustration: params.search || Object.keys(params).length ? "search" : "list", title: params.search || Object.keys(params).length ? t("list.noMatch", { items: resource.pluralLabel.toLowerCase() }) : t("list.emptyYet", { items: resource.pluralLabel.toLowerCase() }), ...(canCreate && !embedded ? { action: _jsx(Link, { className: "text-sm font-medium text-link hover:underline", href: newHref, children: t("list.createFirst") }) } : {}) })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: cn("forge-table w-full text-sm", list.isFetching && "opacity-70"), children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-xs font-medium tracking-wide uppercase", children: [canDelete && (_jsx("th", { className: "w-10 px-4 py-2.5", children: _jsx("input", { type: "checkbox", "aria-label": t("list.selectAll"), checked: allSelected, onChange: () => setSelected(allSelected ? new Set() : new Set(rows.map((r) => r.id))), className: "forge-check" }) })), resource.list.columns.map((c) => (_jsx("th", { className: "px-4 py-2.5 font-medium whitespace-nowrap", children: c.sortable ? (_jsxs("button", { type: "button", onClick: () => toggleSort(c.key), className: "inline-flex items-center gap-1 uppercase hover:text-ink", children: [columnLabel(c), _jsx(SortIcon, { column: c, sort: sort })] })) : (columnLabel(c)) }, c.key)))] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { className: cn("group", row.deletedAt && "text-ink-subtle"), children: [canDelete && (_jsx("td", { className: "px-4 py-2.5", children: _jsx("input", { type: "checkbox", "aria-label": t("list.selectRow"), checked: selected.has(row.id), onChange: () => {
123
+ const next = new Set(selected);
124
+ if (next.has(row.id))
125
+ next.delete(row.id);
126
+ else
127
+ next.add(row.id);
128
+ setSelected(next);
129
+ }, className: "forge-check" }) })), resource.list.columns.map((c, i) => (_jsx("td", { className: "max-w-xs truncate px-4 py-2.5", children: i === 0 ? (_jsx(Link, { href: detail(row.id), className: "font-medium text-ink hover:text-link", children: row[c.key] === null || row[c.key] === "" ? t("common.untitled") : _jsx(FieldValue, { field: fieldByName.get(c.key), value: row[c.key], cell: c.cell }) })) : editable.has(c.key) ? (_jsx(EditableCell, { entity: entity, resource: resource, row: row, column: c, ...editable.get(c.key), principal: principal })) : (_jsx(FieldValue, { field: fieldByName.get(c.key), value: row[c.key], cell: c.kind === "system" ? "datetime" : c.cell })) }, c.key)))] }, row.id))) })] }) })), view === "table" && list.data && list.data.meta.total > 0 && (_jsx(Pagination, { page: list.data.meta.page, pages: list.data.meta.pages, total: list.data.meta.total, onPage: (p) => set({ page: String(p) }) }))] }), importing && _jsx(ImportDialog, { entity: entity, resource: resource, open: importing, onClose: () => setImporting(false) }), _jsx(ConfirmDialog, { open: confirmBulk, title: t("list.bulkDeleteTitle", { count: selected.size, item: resource.label.toLowerCase(), items: resource.pluralLabel.toLowerCase() }), message: entity.features.softDelete ? t("list.bulkDeleteSoft") : t("detail.deleteHard"), confirmLabel: t("common.delete"), danger: true, loading: bulkDelete.isPending, onConfirm: () => bulkDelete.mutate(), onClose: () => setConfirmBulk(false) })] }));
130
+ }
131
+ const VIEW_TABS = [
132
+ { key: "table", label: "list.views.table", icon: _jsx(Table2, { className: "size-3.5" }) },
133
+ { key: "board", label: "list.views.board", icon: _jsx(Columns3, { className: "size-3.5" }) },
134
+ { key: "calendar", label: "list.views.calendar", icon: _jsx(CalendarDays, { className: "size-3.5" }) },
135
+ { key: "map", label: "list.views.map", icon: _jsx(MapIcon, { className: "size-3.5" }) },
136
+ ];
137
+ function SortIcon({ column, sort }) {
138
+ if (sort === column.key)
139
+ return _jsx(ArrowUp, { className: "size-3" });
140
+ if (sort === `-${column.key}`)
141
+ return _jsx(ArrowDown, { className: "size-3" });
142
+ return _jsx(ArrowUpDown, { className: "size-3 opacity-0 group-hover:opacity-40" });
143
+ }
144
+ function FilterControl({ filter, params, onChange }) {
145
+ const { t } = useForge();
146
+ const value = params[filter.key] ?? "";
147
+ if (filter.type === "relation") {
148
+ return (_jsx("div", { className: "w-48", children: _jsx(RelationSelect, { lookupPath: filter.relationPath, value: value || null, onChange: (v) => onChange({ [filter.key]: v ?? "" }), placeholder: t("list.filterSearch", { filter: filter.label.toLowerCase() }), emptyLabel: t("list.filterAll", { filter: filter.label }), compact: true }) }));
149
+ }
150
+ if (filter.type === "range")
151
+ return null;
152
+ return (_jsxs(Select, { "aria-label": filter.label, value: value, onChange: (e) => onChange({ [filter.key]: e.target.value }), className: "h-8 !w-auto py-0 text-xs", children: [_jsx("option", { value: "", children: t("list.filterAll", { filter: filter.label }) }), filter.type === "boolean" ? (_jsxs(_Fragment, { children: [_jsx("option", { value: "true", children: t("common.yes") }), _jsx("option", { value: "false", children: t("common.no") })] })) : (filter.options?.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value))))] }));
153
+ }
154
+ //# sourceMappingURL=resource-list.js.map