@vertebr/react 0.4.0 → 0.5.1
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/components/admin/AdminContent.d.ts +36 -0
- package/dist/components/admin/AdminContent.d.ts.map +1 -0
- package/dist/components/admin/AdminContent.js +107 -0
- package/dist/components/admin/AdminHeader.d.ts +13 -0
- package/dist/components/admin/AdminHeader.d.ts.map +1 -0
- package/dist/components/admin/AdminHeader.js +32 -0
- package/dist/components/admin/AdminSidebar.d.ts +25 -0
- package/dist/components/admin/AdminSidebar.d.ts.map +1 -0
- package/dist/components/admin/AdminSidebar.js +31 -0
- package/dist/components/admin/VertebrAdmin.d.ts +3 -18
- package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
- package/dist/components/admin/VertebrAdmin.js +130 -120
- package/dist/components/admin/sidebar.d.ts +3 -0
- package/dist/components/admin/sidebar.d.ts.map +1 -0
- package/dist/components/admin/sidebar.js +13 -0
- package/dist/components/admin/storage.d.ts +4 -0
- package/dist/components/admin/storage.d.ts.map +1 -0
- package/dist/components/admin/storage.js +24 -0
- package/dist/components/admin/types.d.ts +53 -0
- package/dist/components/admin/types.d.ts.map +1 -0
- package/dist/components/admin/types.js +1 -0
- package/dist/components/config/form/ConfigForm.d.ts +1 -1
- package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/ConfigForm.js +5 -1
- package/dist/components/config/form/RecordConfigForm.d.ts +1 -0
- package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/SettingsConfigForm.d.ts +2 -1
- package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/SettingsConfigForm.js +2 -2
- package/dist/components/config/form/helpers/fields.js +1 -1
- package/dist/components/config/form/types.d.ts +4 -0
- package/dist/components/config/form/types.d.ts.map +1 -1
- package/dist/components/emails/EmailRecipientSelect.d.ts +16 -0
- package/dist/components/emails/EmailRecipientSelect.d.ts.map +1 -0
- package/dist/components/emails/EmailRecipientSelect.js +10 -0
- package/dist/components/emails/EmailsActivityPanel.d.ts +11 -0
- package/dist/components/emails/EmailsActivityPanel.d.ts.map +1 -0
- package/dist/components/emails/EmailsActivityPanel.js +139 -0
- package/dist/components/emails/EmailsBoard.d.ts +47 -0
- package/dist/components/emails/EmailsBoard.d.ts.map +1 -0
- package/dist/components/emails/EmailsBoard.js +594 -0
- package/dist/components/emails/EmailsLibraryPanel.d.ts +25 -0
- package/dist/components/emails/EmailsLibraryPanel.d.ts.map +1 -0
- package/dist/components/emails/EmailsLibraryPanel.js +379 -0
- package/dist/components/emails/EmailsNavSelect.d.ts +18 -0
- package/dist/components/emails/EmailsNavSelect.d.ts.map +1 -0
- package/dist/components/emails/EmailsNavSelect.js +37 -0
- package/dist/components/emails/MultiCheckSelect.d.ts +18 -0
- package/dist/components/emails/MultiCheckSelect.d.ts.map +1 -0
- package/dist/components/emails/MultiCheckSelect.js +74 -0
- package/dist/components/metrics/MetricsBoard.d.ts +8 -2
- package/dist/components/metrics/MetricsBoard.d.ts.map +1 -1
- package/dist/components/metrics/MetricsBoard.js +55 -2
- package/dist/components/metrics/charts.d.ts +14 -0
- package/dist/components/metrics/charts.d.ts.map +1 -0
- package/dist/components/metrics/charts.js +212 -0
- package/dist/components/resources/ResourceActionModal.d.ts +14 -0
- package/dist/components/resources/ResourceActionModal.d.ts.map +1 -0
- package/dist/components/resources/ResourceActionModal.js +64 -0
- package/dist/components/resources/ResourceCell.d.ts.map +1 -1
- package/dist/components/resources/ResourceCell.js +25 -9
- package/dist/components/resources/ResourceMailModal.d.ts +12 -0
- package/dist/components/resources/ResourceMailModal.d.ts.map +1 -0
- package/dist/components/resources/ResourceMailModal.js +196 -0
- package/dist/components/resources/ResourceTable.d.ts +7 -3
- package/dist/components/resources/ResourceTable.d.ts.map +1 -1
- package/dist/components/resources/ResourceTable.js +204 -34
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/metricsRanges.d.ts +20 -0
- package/dist/lib/metricsRanges.d.ts.map +1 -0
- package/dist/lib/metricsRanges.js +14 -0
- package/dist/styles/cells.css +42 -0
- package/dist/styles/emails.css +632 -0
- package/dist/styles/index.css +3 -0
- package/dist/styles/mail.css +240 -0
- package/dist/styles/metrics.css +633 -0
- package/dist/styles/nav.css +103 -1
- package/dist/styles/panel.css +2 -68
- package/dist/styles/table.css +26 -3
- package/package.json +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { EnumInput } from "../config/EnumInput";
|
|
4
|
+
import { actionInputDefaults } from "./ResourceActionModal";
|
|
5
|
+
/** Presets that still need subject + body from the operator. */
|
|
6
|
+
const FREEFORM_PRESETS = new Set(["custom", "promo", "custom_discount"]);
|
|
7
|
+
const DISCOUNT_PRESETS = new Set([
|
|
8
|
+
"discount",
|
|
9
|
+
"welcome_offer",
|
|
10
|
+
"flash_sale",
|
|
11
|
+
"custom_discount",
|
|
12
|
+
]);
|
|
13
|
+
const DISCOUNT_FIELD_KEYS = new Set([
|
|
14
|
+
"discountId",
|
|
15
|
+
"discountCode",
|
|
16
|
+
"discountOffer",
|
|
17
|
+
"discountExpires",
|
|
18
|
+
]);
|
|
19
|
+
function formatRecipients(to) {
|
|
20
|
+
return Array.isArray(to) ? to.join(", ") : to;
|
|
21
|
+
}
|
|
22
|
+
function fieldValue(value) {
|
|
23
|
+
return typeof value === "string" ? value : String(value ?? "");
|
|
24
|
+
}
|
|
25
|
+
function isMultiline(key, field) {
|
|
26
|
+
const hay = `${key} ${field.label ?? ""}`;
|
|
27
|
+
return field.type === "richtext" || /(body|message|html|notes)/i.test(hay);
|
|
28
|
+
}
|
|
29
|
+
function needsFreeformCopy(schema, value) {
|
|
30
|
+
if (!schema.preset)
|
|
31
|
+
return true;
|
|
32
|
+
const preset = fieldValue(value.preset);
|
|
33
|
+
return !preset || FREEFORM_PRESETS.has(preset);
|
|
34
|
+
}
|
|
35
|
+
function isDiscountPreset(preset) {
|
|
36
|
+
return DISCOUNT_PRESETS.has(preset);
|
|
37
|
+
}
|
|
38
|
+
function fieldVisible(key, schema, value) {
|
|
39
|
+
if (key === "subject" || key === "body") {
|
|
40
|
+
return needsFreeformCopy(schema, value);
|
|
41
|
+
}
|
|
42
|
+
if (DISCOUNT_FIELD_KEYS.has(key)) {
|
|
43
|
+
const preset = fieldValue(value.preset);
|
|
44
|
+
if (!isDiscountPreset(preset))
|
|
45
|
+
return false;
|
|
46
|
+
if (key === "discountId" && !(schema.discountId?.options?.length)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function requiredFilled(schema, value) {
|
|
54
|
+
return Object.entries(schema).every(([key, field]) => {
|
|
55
|
+
if (!fieldVisible(key, schema, value))
|
|
56
|
+
return true;
|
|
57
|
+
const freeformCopy = (key === "subject" || key === "body") &&
|
|
58
|
+
needsFreeformCopy(schema, value);
|
|
59
|
+
const discountRequired = (key === "discountCode" || key === "discountOffer") &&
|
|
60
|
+
isDiscountPreset(fieldValue(value.preset));
|
|
61
|
+
if (field.required === false && !freeformCopy && !discountRequired) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (field.type !== "string" &&
|
|
65
|
+
field.type !== "richtext" &&
|
|
66
|
+
field.type !== "enum") {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return Boolean(fieldValue(value[key]).trim());
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function errorMessage(error) {
|
|
73
|
+
if (!(error instanceof Error))
|
|
74
|
+
return "Request failed";
|
|
75
|
+
const raw = error.message.trim();
|
|
76
|
+
if (!raw.startsWith("{"))
|
|
77
|
+
return raw;
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(raw);
|
|
80
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
81
|
+
return parsed.error;
|
|
82
|
+
}
|
|
83
|
+
if (parsed.error &&
|
|
84
|
+
typeof parsed.error === "object" &&
|
|
85
|
+
typeof parsed.error.message === "string" &&
|
|
86
|
+
parsed.error.message.trim()) {
|
|
87
|
+
return parsed.error.message;
|
|
88
|
+
}
|
|
89
|
+
if (typeof parsed.message === "string" && parsed.message.trim()) {
|
|
90
|
+
return parsed.message;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* keep raw */
|
|
95
|
+
}
|
|
96
|
+
return raw;
|
|
97
|
+
}
|
|
98
|
+
export function ResourceMailModal({ title, count, schema, submitLabel, onClose, onPreview, onSubmit, }) {
|
|
99
|
+
const defaults = useMemo(() => actionInputDefaults(schema), [schema]);
|
|
100
|
+
const fields = useMemo(() => Object.entries(schema), [schema]);
|
|
101
|
+
const hasFields = fields.length > 0;
|
|
102
|
+
const [draft, setDraft] = useState(defaults);
|
|
103
|
+
const [preview, setPreview] = useState(null);
|
|
104
|
+
const [previewError, setPreviewError] = useState(null);
|
|
105
|
+
const [previewing, setPreviewing] = useState(false);
|
|
106
|
+
const [sending, setSending] = useState(false);
|
|
107
|
+
const canSend = !sending && (!hasFields || requiredFilled(schema, draft));
|
|
108
|
+
const visibleFields = fields.filter(([key]) => fieldVisible(key, schema, draft));
|
|
109
|
+
const loadPreview = useCallback(async (value) => {
|
|
110
|
+
if (hasFields && !requiredFilled(schema, value)) {
|
|
111
|
+
setPreview(null);
|
|
112
|
+
setPreviewError(null);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setPreviewing(true);
|
|
116
|
+
setPreviewError(null);
|
|
117
|
+
try {
|
|
118
|
+
const next = await onPreview(value);
|
|
119
|
+
setPreview(next);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
setPreview(null);
|
|
123
|
+
setPreviewError(errorMessage(error));
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
setPreviewing(false);
|
|
127
|
+
}
|
|
128
|
+
}, [hasFields, onPreview, schema]);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
const onKeyDown = (event) => {
|
|
131
|
+
if (event.key === "Escape")
|
|
132
|
+
onClose();
|
|
133
|
+
};
|
|
134
|
+
window.addEventListener("keydown", onKeyDown);
|
|
135
|
+
const previous = document.body.style.overflow;
|
|
136
|
+
document.body.style.overflow = "hidden";
|
|
137
|
+
return () => {
|
|
138
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
139
|
+
document.body.style.overflow = previous;
|
|
140
|
+
};
|
|
141
|
+
}, [onClose]);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!hasFields) {
|
|
144
|
+
void loadPreview({});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const timer = window.setTimeout(() => {
|
|
148
|
+
void loadPreview(draft);
|
|
149
|
+
}, 280);
|
|
150
|
+
return () => window.clearTimeout(timer);
|
|
151
|
+
}, [draft, hasFields, loadPreview]);
|
|
152
|
+
const send = async () => {
|
|
153
|
+
if (!canSend)
|
|
154
|
+
return;
|
|
155
|
+
setSending(true);
|
|
156
|
+
try {
|
|
157
|
+
await onSubmit(hasFields ? draft : {});
|
|
158
|
+
onClose();
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
setPreviewError(errorMessage(error));
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
setSending(false);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return (_jsx("div", { className: "vertebr-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "vertebr-modal vertebr-modal-form vertebr-mail-modal", role: "dialog", "aria-modal": "true", "aria-label": title, onClick: (event) => event.stopPropagation(), children: [_jsxs("header", { className: "vertebr-modal-head", children: [_jsxs("div", { className: "vertebr-modal-heading", children: [_jsx("p", { className: "vertebr-modal-eyebrow", children: count === 1 ? "1 person" : `${count} people` }), _jsx("h2", { children: title })] }), _jsx("div", { className: "vertebr-modal-actions", children: _jsx("button", { type: "button", className: "vertebr-btn", onClick: onClose, "aria-label": "Close", children: "Close" }) })] }), _jsxs("div", { className: "vertebr-mail-layout", children: [_jsxs("form", { className: "vertebr-mail-compose", onSubmit: (event) => {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
void send();
|
|
170
|
+
}, children: [hasFields ? (_jsxs("div", { className: "vertebr-mail-fields", children: [visibleFields.map(([key, field]) => {
|
|
171
|
+
const multiline = isMultiline(key, field);
|
|
172
|
+
return (_jsxs("label", { className: "vertebr-mail-field", children: [_jsx("span", { children: field.label ?? key }), field.type === "enum" ? (_jsx(EnumInput, { id: `mail-${key}`, value: draft[key], field: field, onChange: (next) => setDraft((current) => ({
|
|
173
|
+
...current,
|
|
174
|
+
[key]: next,
|
|
175
|
+
})) })) : multiline ? (_jsx("textarea", { rows: 7, value: fieldValue(draft[key]), placeholder: "Plain text \u2014 the host template styles it.", onChange: (event) => setDraft((current) => ({
|
|
176
|
+
...current,
|
|
177
|
+
[key]: event.target.value,
|
|
178
|
+
})) })) : (_jsx("input", { type: "text", value: fieldValue(draft[key]), onChange: (event) => setDraft((current) => ({
|
|
179
|
+
...current,
|
|
180
|
+
[key]: event.target.value,
|
|
181
|
+
})) }))] }, key));
|
|
182
|
+
}), schema.preset && !needsFreeformCopy(schema, draft) ? (_jsx("p", { className: "vertebr-mail-compose-hint", children: "Preview updates from the selected preset. Switch to Custom to write your own copy." })) : null] })) : (_jsx("p", { className: "vertebr-mail-compose-note", children: "Host template \u2014 Vertebr only delivers it." })), previewError ? (_jsx("p", { className: "vertebr-mail-compose-error", children: previewError })) : null, _jsx("div", { className: "vertebr-mail-compose-foot", children: _jsx("button", { type: "submit", className: "vertebr-btn vertebr-btn-primary", disabled: !canSend, children: sending ? "Sending…" : submitLabel }) })] }), _jsxs("div", { className: "vertebr-mail-preview", children: [_jsxs("div", { className: "vertebr-mail-preview-meta", children: [_jsxs("p", { children: [_jsx("span", { children: "To" }), _jsx("strong", { children: preview
|
|
183
|
+
? formatRecipients(preview.to)
|
|
184
|
+
: count === 1
|
|
185
|
+
? "—"
|
|
186
|
+
: `${count} recipients` })] }), _jsxs("p", { children: [_jsx("span", { children: "Subject" }), _jsx("strong", { children: preview?.subject ?? (previewing ? "Updating…" : "—") })] })] }), _jsx("div", { className: "vertebr-mail-preview-stage", children: _jsx("div", { className: "vertebr-mail-preview-frame", children: preview?.html ? (_jsx("iframe", { title: "Email preview", sandbox: "", srcDoc: preview.html })) : preview?.text ? (_jsx("pre", { children: preview.text })) : (_jsx("div", { className: "vertebr-mail-preview-empty", children: previewError ??
|
|
187
|
+
(previewing
|
|
188
|
+
? "Rendering host template…"
|
|
189
|
+
: hasFields
|
|
190
|
+
? schema.preset && needsFreeformCopy(schema, draft)
|
|
191
|
+
? "Write a subject and body to preview."
|
|
192
|
+
: schema.preset
|
|
193
|
+
? "Pick a preset to preview."
|
|
194
|
+
: "Write a subject and body to preview."
|
|
195
|
+
: "Loading preview…") })) }) })] })] })] }) }));
|
|
196
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldSchema, GalleryItem, ResourceActionMeta, ResourceUploadMeta } from "@vertebr/client";
|
|
1
|
+
import type { FieldSchema, GalleryItem, MailPreviewResponse, ResourceActionMeta, ResourceUploadMeta } from "@vertebr/client";
|
|
2
2
|
type ResourcePanelProps = {
|
|
3
3
|
schema: Record<string, FieldSchema>;
|
|
4
4
|
items: Record<string, unknown>[];
|
|
@@ -17,7 +17,11 @@ type ResourcePanelProps = {
|
|
|
17
17
|
onUpdate?: (rowId: string, value: Record<string, unknown>) => Promise<void>;
|
|
18
18
|
onCreate?: (value: Record<string, unknown>) => Promise<void>;
|
|
19
19
|
onDelete?: (rowId: string) => Promise<void>;
|
|
20
|
-
onAction?: (rowId: string, actionId: string) => Promise<void>;
|
|
20
|
+
onAction?: (rowId: string, actionId: string, input?: Record<string, unknown>) => Promise<void>;
|
|
21
|
+
onActionDone?: () => Promise<void> | void;
|
|
22
|
+
onPreviewMail?: (rowId: string, actionId: string, input?: Record<string, unknown>) => Promise<MailPreviewResponse>;
|
|
23
|
+
collectionActions?: Record<string, ResourceActionMeta>;
|
|
24
|
+
onCollectionAction?: (actionId: string, input?: Record<string, unknown>) => Promise<void>;
|
|
21
25
|
onUpload?: (field: string, files: File[]) => Promise<Array<{
|
|
22
26
|
url: string;
|
|
23
27
|
name?: string;
|
|
@@ -26,7 +30,7 @@ type ResourcePanelProps = {
|
|
|
26
30
|
onListGallery?: () => Promise<GalleryItem[]>;
|
|
27
31
|
onUploadGallery?: (files: File[]) => Promise<GalleryItem[]>;
|
|
28
32
|
};
|
|
29
|
-
export declare function ResourcePanel({ schema, items, permissions, actions, label, uploads, rowIdKeys, storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }: ResourcePanelProps): import("react").JSX.Element;
|
|
33
|
+
export declare function ResourcePanel({ schema, items, permissions, actions, label, uploads, rowIdKeys, storageKey, onUpdate, onCreate, onDelete, onAction, onActionDone, onPreviewMail, collectionActions, onCollectionAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }: ResourcePanelProps): import("react").JSX.Element;
|
|
30
34
|
/** @deprecated Prefer ResourcePanel */
|
|
31
35
|
export declare function ResourceTable(props: {
|
|
32
36
|
schema: Record<string, FieldSchema>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceTable.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceTable.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table";
|
|
4
4
|
import { ConfigForm } from "../config/ConfigForm";
|
|
@@ -6,7 +6,23 @@ import { BulkImageUploadModal } from "./BulkImageUploadModal";
|
|
|
6
6
|
import { ColumnVisibilityMenu } from "./ColumnVisibilityMenu";
|
|
7
7
|
import { ResourceCell } from "./ResourceCell";
|
|
8
8
|
import { ResourceCreateModal } from "./ResourceCreateModal";
|
|
9
|
+
import { actionInputSchema, ResourceActionModal, } from "./ResourceActionModal";
|
|
10
|
+
import { ResourceMailModal } from "./ResourceMailModal";
|
|
9
11
|
import { ResourceViewModal } from "./ResourceViewModal";
|
|
12
|
+
function actionHasInput(meta) {
|
|
13
|
+
return Boolean(meta?.input && Object.keys(meta.input).length > 0);
|
|
14
|
+
}
|
|
15
|
+
function actionIsMail(meta) {
|
|
16
|
+
return Boolean(meta?.mail);
|
|
17
|
+
}
|
|
18
|
+
function toggleId(current, id) {
|
|
19
|
+
const next = new Set(current);
|
|
20
|
+
if (next.has(id))
|
|
21
|
+
next.delete(id);
|
|
22
|
+
else
|
|
23
|
+
next.add(id);
|
|
24
|
+
return next;
|
|
25
|
+
}
|
|
10
26
|
function resolveRowId(row, keys) {
|
|
11
27
|
for (const key of keys) {
|
|
12
28
|
const value = row[key];
|
|
@@ -32,30 +48,56 @@ function rowTitle(row, keys) {
|
|
|
32
48
|
}
|
|
33
49
|
return "Record";
|
|
34
50
|
}
|
|
35
|
-
function
|
|
51
|
+
function writeVisibility(storageKey, value) {
|
|
36
52
|
if (!storageKey || typeof localStorage === "undefined")
|
|
37
|
-
return
|
|
53
|
+
return;
|
|
38
54
|
try {
|
|
39
|
-
|
|
40
|
-
if (!raw)
|
|
41
|
-
return {};
|
|
42
|
-
const parsed = JSON.parse(raw);
|
|
43
|
-
if (!parsed || typeof parsed !== "object")
|
|
44
|
-
return {};
|
|
45
|
-
return parsed;
|
|
55
|
+
localStorage.setItem(`vertebr:columns:v2:${storageKey}`, JSON.stringify(value));
|
|
46
56
|
}
|
|
47
57
|
catch {
|
|
48
|
-
|
|
58
|
+
/* ignore quota / private mode */
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
|
-
function
|
|
61
|
+
function isDefaultHiddenColumn(key, field, schema) {
|
|
62
|
+
if (/^show[A-Z]/.test(key))
|
|
63
|
+
return true;
|
|
64
|
+
if (field?.type === "richtext")
|
|
65
|
+
return true;
|
|
66
|
+
if (key === "updatedAt" || key === "createdAt")
|
|
67
|
+
return true;
|
|
68
|
+
if ((key === "imageSrc" || key === "imageAlt") && "images" in schema) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (key === "slug" && "name" in schema)
|
|
72
|
+
return true;
|
|
73
|
+
if (field?.type === "string" && /^(details|description)$/i.test(key)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function defaultVisibility(schema) {
|
|
79
|
+
const next = {};
|
|
80
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
81
|
+
if (isDefaultHiddenColumn(key, field, schema))
|
|
82
|
+
next[key] = false;
|
|
83
|
+
}
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
function readVisibility(storageKey, schema) {
|
|
87
|
+
const defaults = defaultVisibility(schema);
|
|
52
88
|
if (!storageKey || typeof localStorage === "undefined")
|
|
53
|
-
return;
|
|
89
|
+
return defaults;
|
|
54
90
|
try {
|
|
55
|
-
localStorage.
|
|
91
|
+
const raw = localStorage.getItem(`vertebr:columns:v2:${storageKey}`);
|
|
92
|
+
if (!raw)
|
|
93
|
+
return defaults;
|
|
94
|
+
const parsed = JSON.parse(raw);
|
|
95
|
+
if (!parsed || typeof parsed !== "object")
|
|
96
|
+
return defaults;
|
|
97
|
+
return { ...defaults, ...parsed };
|
|
56
98
|
}
|
|
57
99
|
catch {
|
|
58
|
-
|
|
100
|
+
return defaults;
|
|
59
101
|
}
|
|
60
102
|
}
|
|
61
103
|
function addRecordLabel(resourceLabel) {
|
|
@@ -76,28 +118,106 @@ function titleFromFilename(name) {
|
|
|
76
118
|
.replace(/\s+/g, " ")
|
|
77
119
|
.trim();
|
|
78
120
|
}
|
|
79
|
-
export function ResourcePanel({ schema, items, permissions, actions = {}, label, uploads, rowIdKeys = ["id", "slug", "uid", "email"], storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }) {
|
|
121
|
+
export function ResourcePanel({ schema, items, permissions, actions = {}, label, uploads, rowIdKeys = ["id", "slug", "uid", "email"], storageKey, onUpdate, onCreate, onDelete, onAction, onActionDone, onPreviewMail, collectionActions = {}, onCollectionAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }) {
|
|
80
122
|
const [editing, setEditing] = useState(null);
|
|
81
123
|
const [creating, setCreating] = useState(false);
|
|
82
124
|
const [viewing, setViewing] = useState(null);
|
|
83
125
|
const [bulkOpen, setBulkOpen] = useState(false);
|
|
84
126
|
const [busyAction, setBusyAction] = useState(null);
|
|
127
|
+
const [selected, setSelected] = useState(new Set());
|
|
128
|
+
const [pendingAction, setPendingAction] = useState(null);
|
|
129
|
+
const [pendingCollectionAction, setPendingCollectionAction] = useState(null);
|
|
85
130
|
const tableWrapRef = useRef(null);
|
|
86
131
|
const [tableOverflow, setTableOverflow] = useState({
|
|
87
132
|
overflow: false,
|
|
88
133
|
canLeft: false,
|
|
89
134
|
canRight: false,
|
|
90
135
|
});
|
|
91
|
-
const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey));
|
|
136
|
+
const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey, schema));
|
|
92
137
|
const canUpdate = Boolean(permissions?.update && onUpdate);
|
|
93
138
|
const canCreate = Boolean(permissions?.create && onCreate);
|
|
94
139
|
const canDelete = Boolean(permissions?.delete && onDelete);
|
|
95
140
|
const createLabel = addRecordLabel(label);
|
|
96
141
|
const canUpload = Boolean(uploads && onUpload && Object.keys(uploads).length);
|
|
97
142
|
const actionEntries = Object.entries(actions);
|
|
143
|
+
const collectionActionEntries = Object.entries(collectionActions);
|
|
98
144
|
const fieldKeys = Object.keys(schema);
|
|
145
|
+
const selectable = Boolean(onAction && actionEntries.length > 0);
|
|
99
146
|
useEffect(() => {
|
|
100
|
-
|
|
147
|
+
const valid = new Set(items
|
|
148
|
+
.map((row) => resolveRowId(row, rowIdKeys))
|
|
149
|
+
.filter((id) => Boolean(id)));
|
|
150
|
+
setSelected((current) => {
|
|
151
|
+
const next = new Set([...current].filter((id) => valid.has(id)));
|
|
152
|
+
return next.size === current.size ? current : next;
|
|
153
|
+
});
|
|
154
|
+
}, [items, rowIdKeys]);
|
|
155
|
+
const runActions = useCallback(async (actionId, rowIds, input) => {
|
|
156
|
+
if (!onAction || rowIds.length === 0)
|
|
157
|
+
return;
|
|
158
|
+
setBusyAction(actionId);
|
|
159
|
+
const errors = [];
|
|
160
|
+
try {
|
|
161
|
+
for (const rowId of rowIds) {
|
|
162
|
+
try {
|
|
163
|
+
await onAction(rowId, actionId, input);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
errors.push(`${rowId}: ${error instanceof Error ? error.message : "Failed"}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
await onActionDone?.();
|
|
170
|
+
setSelected(new Set());
|
|
171
|
+
if (errors.length > 0) {
|
|
172
|
+
window.alert(`Finished with ${errors.length} error(s).\n\n${errors.slice(0, 8).join("\n")}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
setBusyAction(null);
|
|
177
|
+
}
|
|
178
|
+
}, [onAction, onActionDone]);
|
|
179
|
+
const queueAction = useCallback((actionId, rowIds) => {
|
|
180
|
+
const meta = actions[actionId];
|
|
181
|
+
if (!meta || rowIds.length === 0 || !onAction)
|
|
182
|
+
return;
|
|
183
|
+
if (actionIsMail(meta) || actionHasInput(meta)) {
|
|
184
|
+
setPendingAction({ actionId, rowIds });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const who = rowIds.length === 1 ? "this person" : `${rowIds.length} people`;
|
|
188
|
+
if (!window.confirm(`${meta.label} for ${who}?`))
|
|
189
|
+
return;
|
|
190
|
+
void runActions(actionId, rowIds);
|
|
191
|
+
}, [actions, onAction, runActions]);
|
|
192
|
+
const runCollectionAction = useCallback(async (actionId, input) => {
|
|
193
|
+
if (!onCollectionAction)
|
|
194
|
+
return;
|
|
195
|
+
setBusyAction(actionId);
|
|
196
|
+
try {
|
|
197
|
+
await onCollectionAction(actionId, input);
|
|
198
|
+
await onActionDone?.();
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
window.alert(error instanceof Error ? error.message : "Action failed");
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
setBusyAction(null);
|
|
205
|
+
}
|
|
206
|
+
}, [onCollectionAction, onActionDone]);
|
|
207
|
+
const queueCollectionAction = useCallback((actionId) => {
|
|
208
|
+
const meta = collectionActions[actionId];
|
|
209
|
+
if (!meta || !onCollectionAction)
|
|
210
|
+
return;
|
|
211
|
+
if (actionHasInput(meta)) {
|
|
212
|
+
setPendingCollectionAction(actionId);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (!window.confirm(`${meta.label}?`))
|
|
216
|
+
return;
|
|
217
|
+
void runCollectionAction(actionId);
|
|
218
|
+
}, [collectionActions, onCollectionAction, runCollectionAction]);
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
setColumnVisibility(readVisibility(storageKey, schema));
|
|
101
221
|
}, [storageKey]);
|
|
102
222
|
useEffect(() => {
|
|
103
223
|
writeVisibility(storageKey, columnVisibility);
|
|
@@ -124,6 +244,9 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
|
|
|
124
244
|
el.addEventListener("scroll", onScroll, { passive: true });
|
|
125
245
|
const observer = new ResizeObserver(updateTableOverflow);
|
|
126
246
|
observer.observe(el);
|
|
247
|
+
const table = el.querySelector("table");
|
|
248
|
+
if (table)
|
|
249
|
+
observer.observe(table);
|
|
127
250
|
return () => {
|
|
128
251
|
el.removeEventListener("scroll", onScroll);
|
|
129
252
|
observer.disconnect();
|
|
@@ -144,7 +267,33 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
|
|
|
144
267
|
label: schema[key]?.label ?? humanizeKey(key),
|
|
145
268
|
})), [fieldKeys, schema]);
|
|
146
269
|
const columns = useMemo(() => {
|
|
147
|
-
const defs =
|
|
270
|
+
const defs = [];
|
|
271
|
+
if (selectable) {
|
|
272
|
+
const visibleIds = items
|
|
273
|
+
.map((row) => resolveRowId(row, rowIdKeys))
|
|
274
|
+
.filter((id) => Boolean(id));
|
|
275
|
+
const allSelected = visibleIds.length > 0 && visibleIds.every((id) => selected.has(id));
|
|
276
|
+
defs.push({
|
|
277
|
+
id: "_select",
|
|
278
|
+
enableHiding: false,
|
|
279
|
+
header: () => (_jsx("input", { type: "checkbox", className: "vertebr-row-check", checked: allSelected, "aria-label": "Select all", onChange: () => {
|
|
280
|
+
setSelected((current) => {
|
|
281
|
+
if (visibleIds.every((id) => current.has(id))) {
|
|
282
|
+
return new Set();
|
|
283
|
+
}
|
|
284
|
+
return new Set(visibleIds);
|
|
285
|
+
});
|
|
286
|
+
} })),
|
|
287
|
+
cell: ({ row }) => {
|
|
288
|
+
const rowId = resolveRowId(row.original, rowIdKeys);
|
|
289
|
+
if (!rowId)
|
|
290
|
+
return null;
|
|
291
|
+
return (_jsx("input", { type: "checkbox", className: "vertebr-row-check", checked: selected.has(rowId), "aria-label": "Select row", onClick: (event) => event.stopPropagation(), onChange: () => setSelected((current) => toggleId(current, rowId)) }));
|
|
292
|
+
},
|
|
293
|
+
size: 44,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
defs.push(...(fieldKeys.length === 0
|
|
148
297
|
? [
|
|
149
298
|
{
|
|
150
299
|
id: "raw",
|
|
@@ -161,10 +310,10 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
|
|
|
161
310
|
accessorKey: key,
|
|
162
311
|
header: field?.label ?? humanizeKey(key),
|
|
163
312
|
meta: { fieldKey: key, field },
|
|
164
|
-
cell: ({ getValue }) => (_jsx(ResourceCell, { fieldKey: key, field: field, value: getValue() })),
|
|
313
|
+
cell: ({ getValue, }) => (_jsx(ResourceCell, { fieldKey: key, field: field, value: getValue() })),
|
|
165
314
|
size: isImage ? 220 : undefined,
|
|
166
315
|
};
|
|
167
|
-
});
|
|
316
|
+
})));
|
|
168
317
|
defs.push({
|
|
169
318
|
id: "_actions",
|
|
170
319
|
header: () => _jsx("span", { className: "vertebr-sr-only", children: "Actions" }),
|
|
@@ -172,12 +321,7 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
|
|
|
172
321
|
cell: ({ row }) => {
|
|
173
322
|
const rowId = resolveRowId(row.original, rowIdKeys);
|
|
174
323
|
return (_jsxs("div", { className: "vertebr-row-actions", onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setViewing(row.original), children: "View" }), canUpdate ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setEditing(row.original), children: "Edit" })) : null, rowId
|
|
175
|
-
? actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction
|
|
176
|
-
if (!onAction)
|
|
177
|
-
return;
|
|
178
|
-
setBusyAction(`${rowId}:${actionId}`);
|
|
179
|
-
void onAction(rowId, actionId).finally(() => setBusyAction(null));
|
|
180
|
-
}, children: meta.label }, actionId)))
|
|
324
|
+
? actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction != null, onClick: () => queueAction(actionId, [rowId]), children: meta.label }, actionId)))
|
|
181
325
|
: null, canDelete && rowId ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-danger-quiet", onClick: () => {
|
|
182
326
|
if (window.confirm("Delete this row? This cannot be undone.")) {
|
|
183
327
|
void onDelete?.(rowId);
|
|
@@ -189,11 +333,14 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
|
|
|
189
333
|
}, [
|
|
190
334
|
schema,
|
|
191
335
|
fieldKeys,
|
|
336
|
+
items,
|
|
337
|
+
selected,
|
|
338
|
+
selectable,
|
|
192
339
|
canUpdate,
|
|
193
340
|
canDelete,
|
|
194
341
|
actionEntries,
|
|
195
342
|
busyAction,
|
|
196
|
-
|
|
343
|
+
queueAction,
|
|
197
344
|
onDelete,
|
|
198
345
|
rowIdKeys,
|
|
199
346
|
]);
|
|
@@ -230,7 +377,7 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
|
|
|
230
377
|
setEditing(null);
|
|
231
378
|
} })] }));
|
|
232
379
|
}
|
|
233
|
-
const toolbar = (_jsxs("div", { className: "vertebr-table-toolbar", children: [_jsxs("div", { className: "vertebr-table-toolbar-start", children: [columnOptions.length > 0 ? (_jsx(ColumnVisibilityMenu, { columns: columnOptions, visibility: columnVisibility, onChange: setColumnVisibility })) : null, canUpload ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setBulkOpen(true), children: [_jsx(ImageUploadIcon, {}), "Bulk upload"] })) : null] }), tableOverflow.overflow ? (_jsxs("div", { className: "vertebr-table-scroll-controls", role: "group", "aria-label": "Scroll columns", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canLeft, onClick: () => scrollTable("left"), "aria-label": "Scroll columns left", children: _jsx(ChevronLeftIcon, {}) }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canRight, onClick: () => scrollTable("right"), "aria-label": "Scroll columns right", children: _jsx(ChevronRightIcon, {}) })] })) : null] }));
|
|
380
|
+
const toolbar = (_jsxs("div", { className: "vertebr-table-toolbar", children: [_jsxs("div", { className: "vertebr-table-toolbar-start", children: [columnOptions.length > 0 ? (_jsx(ColumnVisibilityMenu, { columns: columnOptions, visibility: columnVisibility, onChange: setColumnVisibility })) : null, canUpload ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setBulkOpen(true), children: [_jsx(ImageUploadIcon, {}), "Bulk upload"] })) : null, collectionActionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction != null, onClick: () => queueCollectionAction(actionId), children: meta.label }, actionId))), selectable && selected.size > 0 ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "vertebr-count", children: [selected.size, " selected"] }), actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction != null, onClick: () => queueAction(actionId, [...selected]), children: meta.label }, actionId))), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setSelected(new Set()), children: "Clear" })] })) : null] }), tableOverflow.overflow ? (_jsxs("div", { className: "vertebr-table-scroll-controls", role: "group", "aria-label": "Scroll columns", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canLeft, onClick: () => scrollTable("left"), "aria-label": "Scroll columns left", children: _jsx(ChevronLeftIcon, {}) }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canRight, onClick: () => scrollTable("right"), "aria-label": "Scroll columns right", children: _jsx(ChevronRightIcon, {}) })] })) : null] }));
|
|
234
381
|
const bulkModal = bulkOpen && uploads && onUpload ? (_jsx(BulkImageUploadModal, { uploads: uploads, canCreate: canCreate, fieldLabels: Object.fromEntries(Object.keys(uploads).map((key) => [
|
|
235
382
|
key,
|
|
236
383
|
schema[key]?.label ?? humanizeKey(key),
|
|
@@ -247,25 +394,48 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
|
|
|
247
394
|
}
|
|
248
395
|
: undefined })) : null;
|
|
249
396
|
const createModal = creating && onCreate ? (_jsx(ResourceCreateModal, { title: createLabel, schema: createSchema, value: createDefaults, uploads: uploads, onUpload: onUpload, galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onClose: () => setCreating(false), onCreate: onCreate })) : null;
|
|
397
|
+
const pendingActionModal = pendingAction ? (actionIsMail(actions[pendingAction.actionId]) && onPreviewMail ? (_jsx(ResourceMailModal, { title: actions[pendingAction.actionId]?.label ?? "Send email", count: pendingAction.rowIds.length, schema: actionInputSchema(actions[pendingAction.actionId]?.input ?? {}), submitLabel: busyAction
|
|
398
|
+
? "Sending…"
|
|
399
|
+
: pendingAction.rowIds.length === 1
|
|
400
|
+
? "Send"
|
|
401
|
+
: `Send to ${pendingAction.rowIds.length}`, onClose: () => setPendingAction(null), onPreview: (value) => onPreviewMail(pendingAction.rowIds[0], pendingAction.actionId, value), onSubmit: (value) => runActions(pendingAction.actionId, pendingAction.rowIds, value) })) : actions[pendingAction.actionId]?.input ? (_jsx(ResourceActionModal, { title: actions[pendingAction.actionId]?.label ?? "Run action", count: pendingAction.rowIds.length, schema: actionInputSchema(actions[pendingAction.actionId].input), submitLabel: busyAction
|
|
402
|
+
? "Sending…"
|
|
403
|
+
: pendingAction.rowIds.length === 1
|
|
404
|
+
? actions[pendingAction.actionId]?.label ?? "Send"
|
|
405
|
+
: `Send to ${pendingAction.rowIds.length}`, onClose: () => setPendingAction(null), onSubmit: (value) => runActions(pendingAction.actionId, pendingAction.rowIds, value) })) : null) : null;
|
|
406
|
+
const pendingCollectionModal = pendingCollectionAction &&
|
|
407
|
+
collectionActions[pendingCollectionAction]?.input ? (_jsx(ResourceActionModal, { title: collectionActions[pendingCollectionAction]?.label ?? "Run action", eyebrow: "This resource", schema: actionInputSchema(collectionActions[pendingCollectionAction].input), submitLabel: busyAction ? "Working…" : "Run", onClose: () => setPendingCollectionAction(null), onSubmit: (value) => runCollectionAction(pendingCollectionAction, value) })) : null;
|
|
250
408
|
const recordsHead = (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsxs("div", { className: "vertebr-panel-head-actions", children: [canCreate ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setCreating(true), children: [_jsx(PlusIcon, {}), createLabel] })) : null, _jsx("span", { className: "vertebr-count", children: items.length })] })] }));
|
|
251
409
|
if (items.length === 0) {
|
|
252
|
-
return (_jsxs("div", { className: "vertebr-resource-body", children: [recordsHead, canUpload ||
|
|
410
|
+
return (_jsxs("div", { className: "vertebr-resource-body", children: [recordsHead, canUpload ||
|
|
411
|
+
columnOptions.length > 0 ||
|
|
412
|
+
collectionActionEntries.length > 0
|
|
413
|
+
? toolbar
|
|
414
|
+
: null, _jsx("div", { className: "vertebr-empty", children: "No rows yet." }), bulkModal, createModal, pendingActionModal, pendingCollectionModal] }));
|
|
253
415
|
}
|
|
254
416
|
return (_jsxs("div", { className: "vertebr-resource-body", children: [recordsHead, toolbar, _jsx("div", { className: "vertebr-table-wrap", ref: tableWrapRef, "data-overflow": tableOverflow.overflow ? "true" : undefined, "data-overflow-left": tableOverflow.canLeft ? "true" : undefined, "data-overflow-right": tableOverflow.canRight ? "true" : undefined, children: _jsxs("table", { className: "vertebr-table", children: [_jsx("thead", { children: table.getHeaderGroups().map((headerGroup) => (_jsx("tr", { children: headerGroup.headers.map((header) => (_jsx("th", { className: header.column.id === "_actions"
|
|
255
417
|
? "vertebr-col-actions"
|
|
256
|
-
:
|
|
418
|
+
: header.column.id === "_select"
|
|
419
|
+
? "vertebr-col-select"
|
|
420
|
+
: undefined, children: header.isPlaceholder
|
|
257
421
|
? null
|
|
258
|
-
: flexRender(header.column.columnDef.header, header.getContext()) }, header.id))) }, headerGroup.id))) }), _jsx("tbody", { children: table.getRowModel().rows.map((row) => (_jsx("tr", { className: "vertebr-table-row-clickable",
|
|
422
|
+
: flexRender(header.column.columnDef.header, header.getContext()) }, header.id))) }, headerGroup.id))) }), _jsx("tbody", { children: table.getRowModel().rows.map((row) => (_jsx("tr", { className: "vertebr-table-row-clickable", "data-selected": selectable &&
|
|
423
|
+
resolveRowId(row.original, rowIdKeys) &&
|
|
424
|
+
selected.has(resolveRowId(row.original, rowIdKeys))
|
|
425
|
+
? "true"
|
|
426
|
+
: undefined, tabIndex: 0, onClick: () => setViewing(row.original), onKeyDown: (event) => {
|
|
259
427
|
if (event.key === "Enter" || event.key === " ") {
|
|
260
428
|
event.preventDefault();
|
|
261
429
|
setViewing(row.original);
|
|
262
430
|
}
|
|
263
431
|
}, children: row.getVisibleCells().map((cell) => (_jsx("td", { className: cell.column.id === "_actions"
|
|
264
432
|
? "vertebr-col-actions"
|
|
265
|
-
:
|
|
433
|
+
: cell.column.id === "_select"
|
|
434
|
+
? "vertebr-col-select"
|
|
435
|
+
: undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id))) })] }) }), viewing ? (_jsx(ResourceViewModal, { schema: schema, row: viewing, title: rowTitle(viewing, rowIdKeys), canEdit: canUpdate, onClose: () => setViewing(null), onEdit: () => {
|
|
266
436
|
setEditing(viewing);
|
|
267
437
|
setViewing(null);
|
|
268
|
-
} })) : null, bulkModal, createModal] }));
|
|
438
|
+
} })) : null, pendingActionModal, pendingCollectionModal, bulkModal, createModal] }));
|
|
269
439
|
}
|
|
270
440
|
/** @deprecated Prefer ResourcePanel */
|
|
271
441
|
export function ResourceTable(props) {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ export { VertebrAdmin } from "./components/admin/VertebrAdmin";
|
|
|
2
2
|
export type { VertebrAdminProps } from "./components/admin/VertebrAdmin";
|
|
3
3
|
export { MetricsBoard } from "./components/metrics/MetricsBoard";
|
|
4
4
|
export { GalleryBoard } from "./components/gallery/GalleryBoard";
|
|
5
|
+
export { EmailsBoard } from "./components/emails/EmailsBoard";
|
|
6
|
+
export type { EmailsBoardProps } from "./components/emails/EmailsBoard";
|
|
7
|
+
export type { EmailRecipientOption } from "./components/emails/EmailRecipientSelect";
|
|
8
|
+
export type { MultiCheckOption } from "./components/emails/MultiCheckSelect";
|
|
9
|
+
export type { EmailsLibraryPanelProps } from "./components/emails/EmailsLibraryPanel";
|
|
10
|
+
export type { EmailsActivityPanelProps } from "./components/emails/EmailsActivityPanel";
|
|
5
11
|
export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
|
|
6
12
|
export { ThemeToggle } from "./components/theme/ThemeToggle";
|
|
7
13
|
export type { ThemeToggleProps } from "./components/theme/ThemeToggle";
|
|
@@ -21,5 +27,5 @@ export { createVertebrCatalog, catalogHref, catalogMatch, } from "@vertebr/clien
|
|
|
21
27
|
export type { VertebrCatalog, VertebrCatalogOptions, PublicResourceResponse, } from "@vertebr/client";
|
|
22
28
|
export { VertebrCatalogProvider, useVertebrCatalog, useVertebrCatalogItem, } from "@vertebr/client/react";
|
|
23
29
|
export type { VertebrCatalogProviderProps, VertebrCatalogState, } from "@vertebr/client/react";
|
|
24
|
-
export type { VertebrClient, VertebrClientOptions, VertebrReadOptions, AdminEnvironment, BootstrapResponse, CatalogResponse, CatalogResource, CatalogConfig, CatalogConfigSection, EnvironmentAuthPublic, FieldSchema, FieldType, GalleryItem, GalleryMeta, GalleryResponse, MetricCard, MetricsResponse, ResourceListResponse, ResourceActionMeta, ResourceUploadMeta, ResourceUploadResponse, ResourceBulkUploadResponse, ConfigResponse, VertebrControlsMap, } from "@vertebr/client";
|
|
30
|
+
export type { VertebrClient, VertebrClientOptions, VertebrMetricsOptions, VertebrReadOptions, AdminEnvironment, BootstrapResponse, CatalogResponse, CatalogResource, CatalogConfig, CatalogConfigSection, EnvironmentAuthPublic, FieldSchema, FieldType, GalleryItem, GalleryMeta, GalleryResponse, MailPreviewResponse, MailLibraryTemplate, MailHistoryEntry, MailProductOption, MailDiscountOption, EmailsBoardResponse, MetricCard, MetricChart, MetricsResponse, ResourceListResponse, ResourceActionMeta, ResourceUploadMeta, ResourceUploadResponse, ResourceBulkUploadResponse, ConfigResponse, VertebrControlsMap, } from "@vertebr/client";
|
|
25
31
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,YAAY,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AACrF,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,YAAY,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACtF,YAAY,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
|