@vertebr/react 0.18.1 → 0.19.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.
- package/dist/components/admin/AdminContent.d.ts +5 -1
- package/dist/components/admin/AdminContent.d.ts.map +1 -1
- package/dist/components/admin/AdminContent.js +75 -50
- package/dist/components/admin/AuditBoard.d.ts +6 -2
- package/dist/components/admin/AuditBoard.d.ts.map +1 -1
- package/dist/components/admin/AuditBoard.js +145 -3
- package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
- package/dist/components/admin/VertebrAdmin.js +14 -3
- package/dist/components/config/EnumInput.d.ts.map +1 -1
- package/dist/components/config/EnumInput.js +9 -2
- package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/ConfigForm.js +14 -2
- package/dist/components/emails/EmailsActivityPanel.d.ts.map +1 -1
- package/dist/components/emails/EmailsActivityPanel.js +80 -2
- package/dist/components/metrics/MetricsBoard.d.ts.map +1 -1
- package/dist/components/metrics/MetricsBoard.js +7 -1
- package/dist/components/resources/ColumnVisibilityMenu.d.ts +4 -0
- package/dist/components/resources/ColumnVisibilityMenu.d.ts.map +1 -1
- package/dist/components/resources/ColumnVisibilityMenu.js +14 -7
- package/dist/components/resources/ResourceTable.d.ts +7 -1
- package/dist/components/resources/ResourceTable.d.ts.map +1 -1
- package/dist/components/resources/ResourceTable.js +84 -64
- package/dist/components/resources/SortableHeader.d.ts +9 -0
- package/dist/components/resources/SortableHeader.d.ts.map +1 -0
- package/dist/components/resources/SortableHeader.js +9 -0
- package/dist/components/table/TableFilters.d.ts +20 -0
- package/dist/components/table/TableFilters.d.ts.map +1 -0
- package/dist/components/table/TableFilters.js +55 -0
- package/dist/components/table/TableSearch.d.ts +11 -0
- package/dist/components/table/TableSearch.d.ts.map +1 -0
- package/dist/components/table/TableSearch.js +23 -0
- package/dist/components/table/TableSkeleton.d.ts +16 -0
- package/dist/components/table/TableSkeleton.d.ts.map +1 -0
- package/dist/components/table/TableSkeleton.js +29 -0
- package/dist/components/table/TableWrap.d.ts +23 -0
- package/dist/components/table/TableWrap.d.ts.map +1 -0
- package/dist/components/table/TableWrap.js +35 -0
- package/dist/components/table/filterModel.d.ts +35 -0
- package/dist/components/table/filterModel.d.ts.map +1 -0
- package/dist/components/table/filterModel.js +150 -0
- package/dist/components/table/index.d.ts +13 -0
- package/dist/components/table/index.d.ts.map +1 -0
- package/dist/components/table/index.js +6 -0
- package/dist/components/table/useTableOverflow.d.ts +17 -0
- package/dist/components/table/useTableOverflow.d.ts.map +1 -0
- package/dist/components/table/useTableOverflow.js +54 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/sort.d.ts +10 -0
- package/dist/lib/sort.d.ts.map +1 -0
- package/dist/lib/sort.js +59 -0
- package/dist/styles/audit.css +11 -1
- package/dist/styles/columns.css +1 -84
- package/dist/styles/metrics.css +27 -4
- package/dist/styles/panel.css +143 -0
- package/dist/styles/select.css +16 -0
- package/dist/styles/sync.css +4 -1
- package/dist/styles/table.css +115 -2
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CatalogResponse, ConfigResponse, EmailsBoardResponse, FontLibraryResponse, GalleryResponse, MetricsResponse, ResourceListResponse, VertebrClient } from "@vertebr/client";
|
|
2
2
|
import type { MetricsRangeId } from "../../lib/metricsRanges";
|
|
3
|
+
import type { TableSortState } from "../../lib/sort";
|
|
3
4
|
import type { Selection } from "./types";
|
|
4
5
|
export type AdminContentProps = {
|
|
5
6
|
client: VertebrClient;
|
|
@@ -24,8 +25,11 @@ export type AdminContentProps = {
|
|
|
24
25
|
emailsLoading: boolean;
|
|
25
26
|
onRefreshEmails: () => Promise<void>;
|
|
26
27
|
resourceData: ResourceListResponse | null;
|
|
28
|
+
resourceLoading?: boolean;
|
|
27
29
|
resourceSearch?: string;
|
|
28
30
|
onResourceSearchChange?: (q: string) => void;
|
|
31
|
+
resourceSort?: TableSortState | null;
|
|
32
|
+
onResourceSortChange?: (sort: TableSortState) => void;
|
|
29
33
|
onRefreshResource: () => Promise<void>;
|
|
30
34
|
onRefreshConfig?: () => Promise<void>;
|
|
31
35
|
configData: ConfigResponse | null;
|
|
@@ -45,5 +49,5 @@ export type AdminContentProps = {
|
|
|
45
49
|
onRefreshFonts?: () => Promise<void>;
|
|
46
50
|
};
|
|
47
51
|
};
|
|
48
|
-
export declare function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, resourceSearch, onResourceSearchChange, onRefreshResource, onRefreshConfig, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }: AdminContentProps): import("react").JSX.Element;
|
|
52
|
+
export declare function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, resourceLoading, resourceSearch, onResourceSearchChange, resourceSort, onResourceSortChange, onRefreshResource, onRefreshConfig, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }: AdminContentProps): import("react").JSX.Element;
|
|
49
53
|
//# sourceMappingURL=AdminContent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,aAAa,EACd,MAAM,iBAAiB,CAAC;AASzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"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"}
|
|
@@ -57,8 +57,9 @@ function ConfigEditor({ client, catalog, configData, isPageSelection, previewPat
|
|
|
57
57
|
};
|
|
58
58
|
}, [client, configData.id, relatedKey, relatedAddons]);
|
|
59
59
|
const onChange = useCallback((value) => {
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Merge — react-hook-form watch can omit unchanged / empty keys.
|
|
61
|
+
setDraft((prev) => ({ ...(prev ?? configData.value), ...value }));
|
|
62
|
+
}, [configData.value]);
|
|
62
63
|
const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, client: client, canWrite: canWrite, revisionsEnabled: showRevisions, canPublish: canPublish && Boolean(configData.draftRevisionId), onPublish: canPublish && configData.draftRevisionId
|
|
63
64
|
? async () => {
|
|
64
65
|
const published = await client.publishRevision(configData.id, configData.draftRevisionId);
|
|
@@ -76,7 +77,7 @@ function ConfigEditor({ client, catalog, configData, isPageSelection, previewPat
|
|
|
76
77
|
return (_jsxs("button", { type: "button", className: "vertebr-page-related-addon", "data-enabled": enabled ? "true" : "false", onClick: () => onSelectAddon?.(addon.id), children: [_jsx("span", { children: addon.label }), _jsx("em", { children: enabled ? "on" : "off" })] }, addon.id));
|
|
77
78
|
}) })] })) : null, showPreview && previewPath ? (_jsxs("div", { className: "vertebr-page-editor", children: [_jsx("div", { className: "vertebr-page-editor-form", children: form }), _jsx(PagePreviewPane, { previewPath: previewPath, controlId: configData.id, draft: draft ?? configData.value, relatedDrafts: Object.keys(relatedDrafts).length ? relatedDrafts : undefined })] })) : (form)] }));
|
|
78
79
|
}
|
|
79
|
-
export function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, resourceSearch, onResourceSearchChange, onRefreshResource, onRefreshConfig, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
|
|
80
|
+
export function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, resourceLoading = false, resourceSearch, onResourceSearchChange, resourceSort = null, onResourceSortChange, onRefreshResource, onRefreshConfig, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
|
|
80
81
|
if (!catalog) {
|
|
81
82
|
return (_jsx("div", { className: "vertebr-content", children: _jsx("section", { className: "vertebr-panel", children: _jsx("div", { className: "vertebr-empty", children: authRequired
|
|
82
83
|
? "Authenticated access required for this environment."
|
|
@@ -84,7 +85,7 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
|
|
|
84
85
|
? "Loading…"
|
|
85
86
|
: "Unable to load catalog." }) }) }));
|
|
86
87
|
}
|
|
87
|
-
return (_jsx("div", { className: "vertebr-content", children: _jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, windowLabel: metricsData?.windowLabel, compareLabel: metricsData?.compareLabel, metrics: metricsData?.metrics ?? [], charts: metricsData?.charts ?? [], range: metricsRange, onRangeChange: onMetricsRangeChange, loading: metricsLoading, onRefresh: () => void onRefreshMetrics() })) : null, selection?.kind === "audit" ? (_jsx(AuditBoard, { client: client, label: catalog.audit?.label })) : null, selection?.kind === "sync" ? (_jsx(AdminSyncPage, { client: client, catalog: catalog })) : null, selection?.kind === "gallery" && galleryData ? (_jsx(GalleryBoard, { label: galleryData.label, items: galleryData.items, accept: galleryData.accept, maxBytes: galleryData.maxBytes, canDelete: galleryData.canDelete, loading: galleryLoading, onRefresh: () => void onRefreshGallery(), onUpload: async (files) => {
|
|
88
|
+
return (_jsx("div", { className: "vertebr-content", children: _jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, windowLabel: metricsData?.windowLabel, compareLabel: metricsData?.compareLabel, metrics: metricsData?.metrics ?? [], charts: metricsData?.charts ?? [], range: metricsRange, onRangeChange: onMetricsRangeChange, loading: metricsLoading, onRefresh: () => void onRefreshMetrics() })) : null, selection?.kind === "audit" ? (_jsx(AuditBoard, { client: client, label: catalog.audit?.label, columns: catalog.audit?.columns })) : null, selection?.kind === "sync" ? (_jsx(AdminSyncPage, { client: client, catalog: catalog })) : null, selection?.kind === "gallery" && galleryData ? (_jsx(GalleryBoard, { label: galleryData.label, items: galleryData.items, accept: galleryData.accept, maxBytes: galleryData.maxBytes, canDelete: galleryData.canDelete, loading: galleryLoading, onRefresh: () => void onRefreshGallery(), onUpload: async (files) => {
|
|
88
89
|
for (const file of files) {
|
|
89
90
|
await client.uploadGalleryFile(file);
|
|
90
91
|
}
|
|
@@ -151,52 +152,76 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
|
|
|
151
152
|
? async (id) => {
|
|
152
153
|
await client.deleteEmailLibraryTemplate(id);
|
|
153
154
|
}
|
|
154
|
-
: undefined })) : null, selection?.kind === "resource"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
?
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
155
|
+
: undefined })) : null, selection?.kind === "resource"
|
|
156
|
+
? (() => {
|
|
157
|
+
const catalogResource = catalog.resources.find((item) => item.id === selection.id);
|
|
158
|
+
const ready = resourceData != null && resourceData.id === selection.id;
|
|
159
|
+
const schema = ready
|
|
160
|
+
? resourceData.schema
|
|
161
|
+
: (catalogResource?.schema ?? {});
|
|
162
|
+
if (!ready && Object.keys(schema).length === 0)
|
|
163
|
+
return null;
|
|
164
|
+
return (_jsx(ResourcePanel, { schema: schema, items: ready ? resourceData.items : [], label: ready
|
|
165
|
+
? resourceData.label
|
|
166
|
+
: (catalogResource?.label ?? selection.id), listQuery: ready
|
|
167
|
+
? resourceData.listQuery
|
|
168
|
+
: catalogResource?.listQuery, total: ready ? resourceData.total : undefined, loading: resourceLoading || !ready, searchQuery: resourceSearch, onSearchChange: onResourceSearchChange, sort: resourceSort, onSortChange: onResourceSortChange, initialEditRowId: ready && selection.rowId ? selection.rowId : undefined, onEditRowIdChange: (rowId) => {
|
|
169
|
+
onSelectionChange?.({
|
|
170
|
+
kind: "resource",
|
|
171
|
+
id: selection.id,
|
|
172
|
+
rowId: rowId ?? undefined,
|
|
173
|
+
});
|
|
174
|
+
}, permissions: ready
|
|
175
|
+
? resourceData.permissions
|
|
176
|
+
: { create: false, update: false, delete: false }, actions: ready ? resourceData.actions : {}, collectionActions: ready ? resourceData.collectionActions : {}, bulkActions: ready ? resourceData.bulkActions : {}, uploads: ready ? resourceData.uploads : undefined, storageKey: selection.id, onUpdate: ready && resourceData.permissions.update
|
|
177
|
+
? async (rowId, value) => {
|
|
178
|
+
await client.updateResource(resourceData.id, rowId, value);
|
|
179
|
+
await onRefreshResource();
|
|
180
|
+
}
|
|
181
|
+
: undefined, onCreate: ready && resourceData.permissions.create
|
|
182
|
+
? async (value) => {
|
|
183
|
+
await client.createResource(resourceData.id, value);
|
|
184
|
+
await onRefreshResource();
|
|
185
|
+
}
|
|
186
|
+
: undefined, onDelete: ready && resourceData.permissions.delete
|
|
187
|
+
? async (rowId) => {
|
|
188
|
+
await client.deleteResource(resourceData.id, rowId);
|
|
189
|
+
await onRefreshResource();
|
|
190
|
+
}
|
|
191
|
+
: undefined, onAction: ready
|
|
192
|
+
? async (rowId, actionId, input) => {
|
|
193
|
+
await client.runResourceAction(resourceData.id, rowId, actionId, input);
|
|
194
|
+
}
|
|
195
|
+
: undefined, onActionDone: onRefreshResource, onPreviewMail: ready
|
|
196
|
+
? async (rowId, actionId, input) => {
|
|
197
|
+
return client.previewResourceMail(resourceData.id, rowId, actionId, input);
|
|
198
|
+
}
|
|
199
|
+
: undefined, onCollectionAction: ready
|
|
200
|
+
? async (actionId, input) => {
|
|
201
|
+
return client.runCollectionAction(resourceData.id, actionId, input);
|
|
202
|
+
}
|
|
203
|
+
: undefined, onBulkAction: ready && resourceData.bulkActions
|
|
204
|
+
? async (actionId, rowIds, input) => {
|
|
205
|
+
await client.runBulkAction(resourceData.id, actionId, {
|
|
206
|
+
rowIds,
|
|
207
|
+
input,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
: undefined, onUpload: ready && resourceData.uploads
|
|
211
|
+
? async (field, files) => {
|
|
212
|
+
const uploaded = [];
|
|
213
|
+
for (const file of files) {
|
|
214
|
+
const result = await client.uploadResourceFile(resourceData.id, field, file);
|
|
215
|
+
uploaded.push({
|
|
216
|
+
url: result.url,
|
|
217
|
+
name: result.name,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return uploaded;
|
|
221
|
+
}
|
|
222
|
+
: undefined, ...galleryPicker }));
|
|
223
|
+
})()
|
|
224
|
+
: null, selection?.kind === "addons" ? (_jsx(AddonOverview, { client: client, catalog: catalog, onSelectAddon: (id) => onSelectionChange?.({ kind: "addon", id }) })) : null, selection?.kind === "config" && configData ? (_jsx(ConfigEditor, { client: client, catalog: catalog, configData: configData, isPageSelection: isPageSelection, previewPath: catalog.configs.find((item) => item.id === configData.id)
|
|
200
225
|
?.previewPath, relatedAddons: catalog.configs.find((item) => item.id === configData.id)
|
|
201
226
|
?.relatedAddons, onConfigSaved: onConfigSaved, onRefreshConfig: onRefreshConfig, onSelectAddon: (id) => onSelectionChange?.({ kind: "addon", id }), galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }, configData.id)) : null, selection?.kind === "addon" && configData ? ((() => {
|
|
202
227
|
const addon = catalog.addons?.find((item) => item.id === configData.id);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type { VertebrClient } from "@vertebr/client";
|
|
1
|
+
import type { AuditColumn, AuditEntry, VertebrClient } from "@vertebr/client";
|
|
2
2
|
export type AuditBoardProps = {
|
|
3
3
|
client: VertebrClient;
|
|
4
4
|
label?: string;
|
|
5
|
+
/** From `catalog.audit.columns`. Omit for Vertebr defaults. */
|
|
6
|
+
columns?: AuditColumn[];
|
|
5
7
|
};
|
|
6
|
-
|
|
8
|
+
/** Resolve a column path against an audit entry (`meta.x`, `target`, or field). */
|
|
9
|
+
export declare function resolveAuditColumnValue(entry: AuditEntry, path: string): unknown;
|
|
10
|
+
export declare function AuditBoard({ client, label, columns: columnsProp, }: AuditBoardProps): import("react").JSX.Element;
|
|
7
11
|
//# sourceMappingURL=AuditBoard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditBoard.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AuditBoard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"AuditBoard.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AuditBoard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,aAAa,EACd,MAAM,iBAAiB,CAAC;AAoBzB,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB,CAAC;AAsEF,mFAAmF;AACnF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAST;AA6CD,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAe,EACf,OAAO,EAAE,WAAW,GACrB,EAAE,eAAe,+BA2LjB"}
|
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { SortableHeader } from "../resources/SortableHeader";
|
|
4
|
+
import { TableFilters, TableScrollControls, TableSearch, TableSkeleton, TableWrap, matchesTableFilters, matchesTableQuery, resolveAuditFilter, useTableOverflow, } from "../table";
|
|
5
|
+
import { compareFieldValues, nextTableSort, } from "../../lib/sort";
|
|
6
|
+
/** Fallback when catalog omits columns — keep in sync with `defaultAuditColumns`. */
|
|
7
|
+
const DEFAULT_COLUMNS = [
|
|
8
|
+
{ id: "at", label: "When", path: "at", type: "datetime" },
|
|
9
|
+
{ id: "actor", label: "Email", path: "actor" },
|
|
10
|
+
{
|
|
11
|
+
id: "action",
|
|
12
|
+
label: "Action",
|
|
13
|
+
path: "action",
|
|
14
|
+
filter: {
|
|
15
|
+
options: [
|
|
16
|
+
"config.set",
|
|
17
|
+
"config.publish",
|
|
18
|
+
"config.revision.save",
|
|
19
|
+
"config.revision.publish",
|
|
20
|
+
"config.revision.restore",
|
|
21
|
+
"addon.publish",
|
|
22
|
+
"resource.create",
|
|
23
|
+
"resource.update",
|
|
24
|
+
"resource.delete",
|
|
25
|
+
"sync.run",
|
|
26
|
+
],
|
|
27
|
+
optionLabels: {
|
|
28
|
+
"config.set": "Config set",
|
|
29
|
+
"config.publish": "Config publish",
|
|
30
|
+
"config.revision.save": "Revision save",
|
|
31
|
+
"config.revision.publish": "Revision publish",
|
|
32
|
+
"config.revision.restore": "Revision restore",
|
|
33
|
+
"addon.publish": "Addon publish",
|
|
34
|
+
"resource.create": "Resource create",
|
|
35
|
+
"resource.update": "Resource update",
|
|
36
|
+
"resource.delete": "Resource delete",
|
|
37
|
+
"sync.run": "Sync run",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{ id: "target", label: "Target", path: "target" },
|
|
42
|
+
{ id: "summary", label: "Summary", path: "summary" },
|
|
43
|
+
];
|
|
3
44
|
function formatWhen(at) {
|
|
4
45
|
const date = new Date(at);
|
|
5
46
|
if (Number.isNaN(date.getTime()))
|
|
@@ -11,10 +52,72 @@ function formatWhen(at) {
|
|
|
11
52
|
minute: "2-digit",
|
|
12
53
|
});
|
|
13
54
|
}
|
|
14
|
-
|
|
55
|
+
function formatCellDisplay(value, column) {
|
|
56
|
+
if (value == null || value === "")
|
|
57
|
+
return "—";
|
|
58
|
+
if (column.type === "datetime" || column.path === "at") {
|
|
59
|
+
return formatWhen(String(value));
|
|
60
|
+
}
|
|
61
|
+
if (column.type === "boolean") {
|
|
62
|
+
return value ? "Yes" : "No";
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === "object") {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.stringify(value);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return String(value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return String(value);
|
|
73
|
+
}
|
|
74
|
+
/** Resolve a column path against an audit entry (`meta.x`, `target`, or field). */
|
|
75
|
+
export function resolveAuditColumnValue(entry, path) {
|
|
76
|
+
if (path === "target") {
|
|
77
|
+
return `${entry.targetType}${entry.targetId ? ` · ${entry.targetId}` : ""}`;
|
|
78
|
+
}
|
|
79
|
+
if (path.startsWith("meta.")) {
|
|
80
|
+
const key = path.slice("meta.".length);
|
|
81
|
+
return entry.meta?.[key];
|
|
82
|
+
}
|
|
83
|
+
return entry[path];
|
|
84
|
+
}
|
|
85
|
+
function columnSortType(column) {
|
|
86
|
+
if (column.type === "datetime" || column.path === "at")
|
|
87
|
+
return "datetime";
|
|
88
|
+
if (column.type === "number")
|
|
89
|
+
return "number";
|
|
90
|
+
return "string";
|
|
91
|
+
}
|
|
92
|
+
function AuditCell({ entry, column, }) {
|
|
93
|
+
const value = resolveAuditColumnValue(entry, column.path);
|
|
94
|
+
const display = formatCellDisplay(value, column);
|
|
95
|
+
if (column.path === "at" || column.type === "datetime") {
|
|
96
|
+
return _jsx("td", { className: "vertebr-audit-when", children: display });
|
|
97
|
+
}
|
|
98
|
+
if (column.path === "actor") {
|
|
99
|
+
return _jsx("td", { className: "vertebr-audit-actor", children: display });
|
|
100
|
+
}
|
|
101
|
+
if (column.path === "action") {
|
|
102
|
+
return (_jsx("td", { children: display === "—" ? (display) : (_jsx("code", { className: "vertebr-audit-action", children: display })) }));
|
|
103
|
+
}
|
|
104
|
+
if (column.path === "target" || column.path === "targetType") {
|
|
105
|
+
return (_jsx("td", { children: _jsx("span", { className: "vertebr-audit-target", children: display }) }));
|
|
106
|
+
}
|
|
107
|
+
return _jsx("td", { children: display });
|
|
108
|
+
}
|
|
109
|
+
export function AuditBoard({ client, label = "Audit", columns: columnsProp, }) {
|
|
110
|
+
const columns = columnsProp && columnsProp.length > 0 ? columnsProp : DEFAULT_COLUMNS;
|
|
15
111
|
const [items, setItems] = useState([]);
|
|
16
112
|
const [loading, setLoading] = useState(true);
|
|
17
113
|
const [error, setError] = useState(null);
|
|
114
|
+
const [query, setQuery] = useState("");
|
|
115
|
+
const [filters, setFilters] = useState({});
|
|
116
|
+
const [sort, setSort] = useState(() => ({
|
|
117
|
+
field: columns.find((column) => column.path === "at")?.id ?? columns[0].id,
|
|
118
|
+
order: "desc",
|
|
119
|
+
}));
|
|
120
|
+
const tableOverflow = useTableOverflow(items.length);
|
|
18
121
|
useEffect(() => {
|
|
19
122
|
let cancelled = false;
|
|
20
123
|
(async () => {
|
|
@@ -39,5 +142,44 @@ export function AuditBoard({ client, label = "Audit" }) {
|
|
|
39
142
|
cancelled = true;
|
|
40
143
|
};
|
|
41
144
|
}, [client]);
|
|
42
|
-
|
|
145
|
+
const filterColumns = useMemo(() => columns
|
|
146
|
+
.map((column) => resolveAuditFilter(column.id, column.label, column.filter))
|
|
147
|
+
.filter((column) => Boolean(column)), [columns]);
|
|
148
|
+
const filterGetters = useMemo(() => Object.fromEntries(filterColumns.map((column) => {
|
|
149
|
+
const source = columns.find((item) => item.id === column.id);
|
|
150
|
+
return [
|
|
151
|
+
column.id,
|
|
152
|
+
(entry) => resolveAuditColumnValue(entry, source?.path ?? column.id),
|
|
153
|
+
];
|
|
154
|
+
})), [filterColumns, columns]);
|
|
155
|
+
const searchedItems = useMemo(() => {
|
|
156
|
+
if (!query.trim())
|
|
157
|
+
return items;
|
|
158
|
+
return items.filter((entry) => columns.some((col) => matchesTableQuery(resolveAuditColumnValue(entry, col.path), query)));
|
|
159
|
+
}, [items, query, columns]);
|
|
160
|
+
const sortedItems = useMemo(() => {
|
|
161
|
+
const column = columns.find((item) => item.id === sort.field) ?? columns[0];
|
|
162
|
+
const filtered = searchedItems.filter((entry) => matchesTableFilters(entry, filters, filterGetters));
|
|
163
|
+
if (!column)
|
|
164
|
+
return filtered;
|
|
165
|
+
const type = columnSortType(column);
|
|
166
|
+
return [...filtered].sort((left, right) => compareFieldValues(resolveAuditColumnValue(left, column.path), resolveAuditColumnValue(right, column.path), type, sort.order));
|
|
167
|
+
}, [searchedItems, sort, columns, filters, filterGetters]);
|
|
168
|
+
return (_jsxs("div", { className: "vertebr-audit", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: label }), _jsxs("div", { className: "vertebr-panel-head-actions", children: [filterColumns.length > 0 ? (_jsx(TableFilters, { columns: filterColumns, selected: filters, onChange: setFilters })) : null, _jsx(TableSearch, { value: query, onChange: setQuery, "aria-label": "Search audit" }), _jsx(TableScrollControls, { canLeft: tableOverflow.canLeft, canRight: tableOverflow.canRight, onScroll: tableOverflow.scrollBy }), _jsx("span", { className: "vertebr-count", children: loading
|
|
169
|
+
? "…"
|
|
170
|
+
: sortedItems.length !== items.length
|
|
171
|
+
? `${sortedItems.length} of ${items.length}`
|
|
172
|
+
: `${items.length} events` })] })] }), error ? _jsx("div", { className: "vertebr-error", children: error }) : null, loading && items.length === 0 ? (_jsx(TableSkeleton, { columns: columns.map((column) => ({
|
|
173
|
+
id: column.id,
|
|
174
|
+
label: column.label,
|
|
175
|
+
})) })) : items.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: "No audit events yet." })) : sortedItems.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: "No matching events." })) : (_jsx(TableWrap, { overflow: tableOverflow, transitionKey: JSON.stringify(filters), children: _jsxs("table", { className: "vertebr-table vertebr-audit-table", children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((column) => {
|
|
176
|
+
const sortable = column.sortable !== false;
|
|
177
|
+
const active = sort.field === column.id;
|
|
178
|
+
if (!sortable) {
|
|
179
|
+
return _jsx("th", { children: column.label }, column.id);
|
|
180
|
+
}
|
|
181
|
+
return (_jsx("th", { children: _jsx(SortableHeader, { label: column.label, active: active, order: active ? sort.order : undefined, onSort: () => setSort(nextTableSort(sort, column.id, column.type === "datetime" || column.path === "at"
|
|
182
|
+
? "desc"
|
|
183
|
+
: "asc")) }) }, column.id));
|
|
184
|
+
}) }) }), _jsx("tbody", { children: sortedItems.map((entry) => (_jsx("tr", { children: columns.map((column) => (_jsx(AuditCell, { entry: entry, column: column }, column.id))) }, entry.id))) })] }) }))] }));
|
|
43
185
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,QAAQ,EAAE,YAAY,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,eAAiC,EACjC,sBAAgD,EAChD,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,gBAAgB,GACjB,EAAE,iBAAiB,+BA+yBnB"}
|
|
@@ -40,7 +40,9 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
|
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
42
|
const [resourceData, setResourceData] = useState(null);
|
|
43
|
+
const [resourceLoading, setResourceLoading] = useState(false);
|
|
43
44
|
const [resourceSearch, setResourceSearch] = useState("");
|
|
45
|
+
const [resourceSort, setResourceSort] = useState(null);
|
|
44
46
|
const [configData, setConfigData] = useState(null);
|
|
45
47
|
const [metricsData, setMetricsData] = useState(null);
|
|
46
48
|
const [metricsLoading, setMetricsLoading] = useState(false);
|
|
@@ -213,8 +215,12 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
|
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
else if (selection.kind === "resource") {
|
|
218
|
+
setResourceLoading(true);
|
|
216
219
|
const data = await client.listResource(selection.id, {
|
|
217
220
|
...(resourceSearch.trim() ? { q: resourceSearch.trim() } : {}),
|
|
221
|
+
...(resourceSort
|
|
222
|
+
? { sort: resourceSort.field, order: resourceSort.order }
|
|
223
|
+
: {}),
|
|
218
224
|
fresh: true,
|
|
219
225
|
});
|
|
220
226
|
if (!cancelled) {
|
|
@@ -272,16 +278,18 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
|
|
|
272
278
|
setGalleryLoading(false);
|
|
273
279
|
setFontLibraryLoading(false);
|
|
274
280
|
setEmailsLoading(false);
|
|
281
|
+
setResourceLoading(false);
|
|
275
282
|
}
|
|
276
283
|
}
|
|
277
284
|
})();
|
|
278
285
|
return () => {
|
|
279
286
|
cancelled = true;
|
|
280
287
|
};
|
|
281
|
-
}, [client, selection, catalog, environment, metricsRange, resourceSearch]);
|
|
288
|
+
}, [client, selection, catalog, environment, metricsRange, resourceSearch, resourceSort]);
|
|
282
289
|
const resourceSelectionId = selection?.kind === "resource" ? selection.id : null;
|
|
283
290
|
useEffect(() => {
|
|
284
291
|
setResourceSearch("");
|
|
292
|
+
setResourceSort(null);
|
|
285
293
|
}, [resourceSelectionId]);
|
|
286
294
|
const refreshHealth = useCallback(async () => {
|
|
287
295
|
if (!catalog?.health) {
|
|
@@ -317,10 +325,13 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
|
|
|
317
325
|
return;
|
|
318
326
|
const data = await client.listResource(selection.id, {
|
|
319
327
|
...(resourceSearch.trim() ? { q: resourceSearch.trim() } : {}),
|
|
328
|
+
...(resourceSort
|
|
329
|
+
? { sort: resourceSort.field, order: resourceSort.order }
|
|
330
|
+
: {}),
|
|
320
331
|
fresh: true,
|
|
321
332
|
});
|
|
322
333
|
setResourceData(data);
|
|
323
|
-
}, [client, selection, resourceSearch]);
|
|
334
|
+
}, [client, selection, resourceSearch, resourceSort]);
|
|
324
335
|
const refreshConfig = useCallback(async () => {
|
|
325
336
|
if (selection?.kind !== "config" && selection?.kind !== "addon")
|
|
326
337
|
return;
|
|
@@ -669,5 +680,5 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
|
|
|
669
680
|
: selection?.kind === "addon"
|
|
670
681
|
? catalog?.addons?.find((item) => item.id === selection.id)?.description
|
|
671
682
|
: undefined;
|
|
672
|
-
return (_jsx(SaveBarProvider, { children: _jsx(ToastProvider, { children: _jsxs("div", { className: "vertebr-root", "data-env": environment, "data-sidebar-collapsed": sidebarCollapsed || undefined, children: [_jsx(AdminTopBar, { brand: bootstrap?.brand ?? catalog?.brand ?? "Vertebr", brandLogoUrl: brandLogoUrl, brandLogoUrlDark: brandLogoUrlDark, hostName: bootstrap?.appearance?.hostName ?? catalog?.appearance?.hostName, hostLogoUrl: bootstrap?.appearance?.hostLogoUrl ?? catalog?.appearance?.hostLogoUrl, envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, authUserLabel: authUserLabel, onSignOut: onSignOut, getAccessToken: getAccessToken, authViaTokenOnly: authViaTokenOnly, authRequired: authRequired, token: token, tokenDraft: tokenDraft, onTokenDraftChange: setTokenDraft, onSaveToken: saveToken, onClearToken: clearToken, theme: theme, onThemeChange: setTheme, sidebarCollapsed: sidebarCollapsed, onToggleSidebar: () => setSidebarCollapsed((prev) => !prev), syncEnabled: syncEnabled, onOpenSync: () => setSelection({ kind: "sync" }), onOpenSearch: () => setSearchOpen(true), healthChecks: healthChecks, onRefreshHealth: catalog?.health ? refreshHealth : undefined }), catalog && searchOpen ? (_jsx(AdminCommandPalette, { catalog: catalog, onClose: () => setSearchOpen(false), onSelect: setSelection })) : null, _jsx(AdminSidebar, { navGroups: navGroups, collapsed: sidebarCollapsed }), _jsxs("main", { className: "vertebr-main", children: [_jsx(AdminHeader, { activeLabel: activeLabel ?? (catalog ? "Dashboard" : "Sign in"), catalogLoaded: Boolean(catalog), authRequired: authRequired, selection: selection, isPageSelection: isPageSelection, hasPagePreview: hasPagePreview, selectionDescription: selectionDescription, error: error, breadcrumbs: breadcrumbsForSelection(selection, catalog) }), _jsx(AdminContent, { client: client, catalog: catalog, selection: selection, onSelectionChange: setSelection, authRequired: authRequired, loading: loading, isPageSelection: isPageSelection, metricsData: metricsData, metricsLoading: metricsLoading, metricsRange: metricsRange, onMetricsRangeChange: changeMetricsRange, onRefreshMetrics: refreshMetrics, galleryData: galleryData, galleryLoading: galleryLoading, onRefreshGallery: refreshGallery, fontLibraryData: fontLibraryData, fontLibraryLoading: fontLibraryLoading, onRefreshFonts: refreshFonts, emailsData: emailsData, emailsLoading: emailsLoading, onRefreshEmails: refreshEmails, resourceData: resourceData, resourceSearch: resourceSearch, onResourceSearchChange: setResourceSearch, onRefreshResource: refreshResource, configData: configData, onConfigSaved: setConfigData, onRefreshConfig: refreshConfig, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }), _jsx(AdminSaveBar, {})] }), _jsx(AdminToastStack, {})] }) }) }));
|
|
683
|
+
return (_jsx(SaveBarProvider, { children: _jsx(ToastProvider, { children: _jsxs("div", { className: "vertebr-root", "data-env": environment, "data-sidebar-collapsed": sidebarCollapsed || undefined, children: [_jsx(AdminTopBar, { brand: bootstrap?.brand ?? catalog?.brand ?? "Vertebr", brandLogoUrl: brandLogoUrl, brandLogoUrlDark: brandLogoUrlDark, hostName: bootstrap?.appearance?.hostName ?? catalog?.appearance?.hostName, hostLogoUrl: bootstrap?.appearance?.hostLogoUrl ?? catalog?.appearance?.hostLogoUrl, envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, authUserLabel: authUserLabel, onSignOut: onSignOut, getAccessToken: getAccessToken, authViaTokenOnly: authViaTokenOnly, authRequired: authRequired, token: token, tokenDraft: tokenDraft, onTokenDraftChange: setTokenDraft, onSaveToken: saveToken, onClearToken: clearToken, theme: theme, onThemeChange: setTheme, sidebarCollapsed: sidebarCollapsed, onToggleSidebar: () => setSidebarCollapsed((prev) => !prev), syncEnabled: syncEnabled, onOpenSync: () => setSelection({ kind: "sync" }), onOpenSearch: () => setSearchOpen(true), healthChecks: healthChecks, onRefreshHealth: catalog?.health ? refreshHealth : undefined }), catalog && searchOpen ? (_jsx(AdminCommandPalette, { catalog: catalog, onClose: () => setSearchOpen(false), onSelect: setSelection })) : null, _jsx(AdminSidebar, { navGroups: navGroups, collapsed: sidebarCollapsed }), _jsxs("main", { className: "vertebr-main", children: [_jsx(AdminHeader, { activeLabel: activeLabel ?? (catalog ? "Dashboard" : "Sign in"), catalogLoaded: Boolean(catalog), authRequired: authRequired, selection: selection, isPageSelection: isPageSelection, hasPagePreview: hasPagePreview, selectionDescription: selectionDescription, error: error, breadcrumbs: breadcrumbsForSelection(selection, catalog) }), _jsx(AdminContent, { client: client, catalog: catalog, selection: selection, onSelectionChange: setSelection, authRequired: authRequired, loading: loading, isPageSelection: isPageSelection, metricsData: metricsData, metricsLoading: metricsLoading, metricsRange: metricsRange, onMetricsRangeChange: changeMetricsRange, onRefreshMetrics: refreshMetrics, galleryData: galleryData, galleryLoading: galleryLoading, onRefreshGallery: refreshGallery, fontLibraryData: fontLibraryData, fontLibraryLoading: fontLibraryLoading, onRefreshFonts: refreshFonts, emailsData: emailsData, emailsLoading: emailsLoading, onRefreshEmails: refreshEmails, resourceData: resourceData, resourceLoading: resourceLoading, resourceSearch: resourceSearch, onResourceSearchChange: setResourceSearch, resourceSort: resourceSort, onResourceSortChange: setResourceSort, onRefreshResource: refreshResource, configData: configData, onConfigSaved: setConfigData, onRefreshConfig: refreshConfig, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }), _jsx(AdminSaveBar, {})] }), _jsx(AdminToastStack, {})] }) }) }));
|
|
673
684
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnumInput.d.ts","sourceRoot":"","sources":["../../../src/components/config/EnumInput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,wBAAgB,SAAS,CAAC,EACxB,EAAE,EACF,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,+
|
|
1
|
+
{"version":3,"file":"EnumInput.d.ts","sourceRoot":"","sources":["../../../src/components/config/EnumInput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,wBAAgB,SAAS,CAAC,EACxB,EAAE,EACF,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,+BAsEhB"}
|
|
@@ -5,11 +5,18 @@ export function EnumInput({ id, value, field, readOnly, onChange, }) {
|
|
|
5
5
|
const baseOptions = field.options?.filter(Boolean) ?? [];
|
|
6
6
|
const raw = typeof value === "string" ? value : "";
|
|
7
7
|
const options = raw && !baseOptions.includes(raw) ? [raw, ...baseOptions] : baseOptions;
|
|
8
|
-
const
|
|
8
|
+
const allowClear = field.required !== true;
|
|
9
|
+
// Required enums still fall back to the first option when empty.
|
|
10
|
+
// Optional enums keep an empty value so links/CTAs can be cleared.
|
|
11
|
+
const current = raw && options.includes(raw)
|
|
12
|
+
? raw
|
|
13
|
+
: allowClear
|
|
14
|
+
? ""
|
|
15
|
+
: (options[0] ?? "");
|
|
9
16
|
if (options.length === 0) {
|
|
10
17
|
return (_jsx("input", { id: id, type: "text", readOnly: readOnly, value: current, disabled: true }));
|
|
11
18
|
}
|
|
12
|
-
return (_jsxs(Select.Root, { value: current, onValueChange: onChange, disabled: readOnly, children: [_jsxs(Select.Trigger, { id: id, className: "vertebr-select-trigger", children: [_jsx("span", { className: "vertebr-select-value", children: _jsx(Select.Value, {
|
|
19
|
+
return (_jsxs("div", { className: "vertebr-enum", children: [_jsxs(Select.Root, { value: current || undefined, onValueChange: onChange, disabled: readOnly, children: [_jsxs(Select.Trigger, { id: id, className: "vertebr-select-trigger", children: [_jsx("span", { className: "vertebr-select-value", children: _jsx(Select.Value, { placeholder: "None" }) }), _jsx(Select.Icon, { className: "vertebr-select-icon", children: _jsx(ChevronIcon, {}) })] }), _jsx(Select.Portal, { children: _jsx(Select.Content, { className: "vertebr-select-content", position: "popper", sideOffset: 6, children: _jsx(Select.Viewport, { className: "vertebr-select-viewport", children: options.map((option) => (_jsxs(Select.Item, { value: option, className: "vertebr-select-item", children: [_jsx(Select.ItemText, { children: optionLabel(option, field) }), _jsx(Select.ItemIndicator, { className: "vertebr-select-check", children: _jsx(CheckIcon, {}) })] }, option))) }) }) })] }), allowClear && current && !readOnly ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-enum-clear", onClick: () => onChange(""), children: "Clear" })) : null] }));
|
|
13
20
|
}
|
|
14
21
|
function ChevronIcon() {
|
|
15
22
|
return (_jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", children: _jsx("path", { d: "M2.5 4.5 6 8l3.5-3.5", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigForm.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAkB,EAClB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAe,EACf,gBAAwB,EACxB,SAAS,EACT,UAAkB,GACnB,EAAE,eAAe,+
|
|
1
|
+
{"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigForm.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAkB,EAClB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAe,EACf,gBAAwB,EACxB,SAAS,EACT,UAAkB,GACnB,EAAE,eAAe,+BAqMjB"}
|
|
@@ -28,11 +28,23 @@ export function ConfigForm({ schema, value, onSave, submitLabel, variant = "conf
|
|
|
28
28
|
if (!onChange)
|
|
29
29
|
return;
|
|
30
30
|
const subscription = form.watch((values) => {
|
|
31
|
-
|
|
31
|
+
// Keep empty strings for optional fields so clears reach the host preview
|
|
32
|
+
// (react-hook-form can emit undefined for cleared optional inputs).
|
|
33
|
+
const next = { ...(values ?? {}) };
|
|
34
|
+
for (const [key, field] of Object.entries(editorSchema)) {
|
|
35
|
+
if (field.type !== "string" && field.type !== "richtext" && field.type !== "enum") {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (field.required)
|
|
39
|
+
continue;
|
|
40
|
+
if (next[key] == null)
|
|
41
|
+
next[key] = "";
|
|
42
|
+
}
|
|
43
|
+
onChange(next);
|
|
32
44
|
});
|
|
33
45
|
onChange((form.getValues() ?? {}));
|
|
34
46
|
return () => subscription.unsubscribe();
|
|
35
|
-
}, [form, onChange]);
|
|
47
|
+
}, [form, onChange, editorSchema]);
|
|
36
48
|
useEffect(() => {
|
|
37
49
|
form.reset(editorValue);
|
|
38
50
|
}, [editorValue, form]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmailsActivityPanel.d.ts","sourceRoot":"","sources":["../../../src/components/emails/EmailsActivityPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"EmailsActivityPanel.d.ts","sourceRoot":"","sources":["../../../src/components/emails/EmailsActivityPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAetE,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC;AAgIF,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,OAAO,EACP,WAAW,EACX,UAAU,EACV,IAAI,EACJ,QAAQ,GACT,EAAE,wBAAwB,+BAyR1B"}
|