@open-mercato/ui 0.6.4-develop.4371.1.8f3030407e → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/AGENTS.md +2 -1
- package/dist/backend/AppShell.js +6 -0
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/CrudForm.js +212 -85
- package/dist/backend/CrudForm.js.map +3 -3
- package/dist/backend/DataTable.js +77 -26
- package/dist/backend/DataTable.js.map +3 -3
- package/dist/backend/FlashMessages.js +21 -2
- package/dist/backend/FlashMessages.js.map +2 -2
- package/dist/backend/JsonBuilder.js +64 -41
- package/dist/backend/JsonBuilder.js.map +2 -2
- package/dist/backend/PerspectiveSidebar.js +8 -1
- package/dist/backend/PerspectiveSidebar.js.map +2 -2
- package/dist/backend/RowActions.js +1 -1
- package/dist/backend/RowActions.js.map +2 -2
- package/dist/backend/conflicts/RecordConflictBanner.js +73 -0
- package/dist/backend/conflicts/RecordConflictBanner.js.map +7 -0
- package/dist/backend/conflicts/index.js +32 -0
- package/dist/backend/conflicts/index.js.map +7 -0
- package/dist/backend/conflicts/store.js +50 -0
- package/dist/backend/conflicts/store.js.map +7 -0
- package/dist/backend/detail/ActivitiesSection.js +1 -0
- package/dist/backend/detail/ActivitiesSection.js.map +2 -2
- package/dist/backend/detail/InlineEditors.js +2 -51
- package/dist/backend/detail/InlineEditors.js.map +3 -3
- package/dist/backend/detail/RecordNotFoundState.js +10 -5
- package/dist/backend/detail/RecordNotFoundState.js.map +2 -2
- package/dist/backend/filters/AdvancedFilterPanel.js +14 -0
- package/dist/backend/filters/AdvancedFilterPanel.js.map +2 -2
- package/dist/backend/filters/FilteredEmptyResults.js +12 -4
- package/dist/backend/filters/FilteredEmptyResults.js.map +2 -2
- package/dist/backend/filters/ListEmptyState.js +44 -0
- package/dist/backend/filters/ListEmptyState.js.map +7 -0
- package/dist/backend/filters/SearchEmptyResults.js +28 -0
- package/dist/backend/filters/SearchEmptyResults.js.map +7 -0
- package/dist/backend/icons/lucideRegistry.generated.js +8 -0
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/injection/useGuardedMutation.js +8 -1
- package/dist/backend/injection/useGuardedMutation.js.map +2 -2
- package/dist/backend/inputs/MarkdownField.js +20 -0
- package/dist/backend/inputs/MarkdownField.js.map +7 -0
- package/dist/backend/inputs/MdxEditorImpl.js +98 -0
- package/dist/backend/inputs/MdxEditorImpl.js.map +7 -0
- package/dist/backend/inputs/SwitchableMarkdownInput.js +6 -38
- package/dist/backend/inputs/SwitchableMarkdownInput.js.map +2 -2
- package/dist/backend/markdown/markdownToPlainText.js +29 -0
- package/dist/backend/markdown/markdownToPlainText.js.map +2 -2
- package/dist/backend/messages/EmailThreadsPanel.js +212 -0
- package/dist/backend/messages/EmailThreadsPanel.js.map +7 -0
- package/dist/backend/messages/index.js +4 -0
- package/dist/backend/messages/index.js.map +2 -2
- package/dist/backend/messages/mergeOptimisticEmailThreads.js +55 -0
- package/dist/backend/messages/mergeOptimisticEmailThreads.js.map +7 -0
- package/dist/backend/notifications/NotificationDispatcher.js +1 -0
- package/dist/backend/notifications/NotificationDispatcher.js.map +2 -2
- package/dist/backend/notifications/useNotificationsPoll.js +7 -0
- package/dist/backend/notifications/useNotificationsPoll.js.map +2 -2
- package/dist/backend/notifications/useNotificationsSse.js +8 -0
- package/dist/backend/notifications/useNotificationsSse.js.map +2 -2
- package/dist/backend/sidebar/SidebarCustomizationEditor.js +23 -7
- package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +2 -2
- package/dist/backend/utils/optimisticLock.js +34 -0
- package/dist/backend/utils/optimisticLock.js.map +7 -0
- package/dist/portal/hooks/useTenantContext.js +1 -1
- package/dist/portal/hooks/useTenantContext.js.map +2 -2
- package/dist/primitives/alert.js.map +1 -1
- package/dist/primitives/checkbox.js +2 -1
- package/dist/primitives/checkbox.js.map +2 -2
- package/dist/types/css.d.js +1 -0
- package/dist/types/css.d.js.map +7 -0
- package/package.json +17 -13
- package/src/backend/AppShell.tsx +10 -0
- package/src/backend/CrudForm.tsx +320 -97
- package/src/backend/DataTable.tsx +113 -34
- package/src/backend/FlashMessages.tsx +31 -2
- package/src/backend/JsonBuilder.tsx +79 -26
- package/src/backend/PerspectiveSidebar.tsx +8 -1
- package/src/backend/RowActions.tsx +1 -1
- package/src/backend/__tests__/CrudForm.custom-fields.test.tsx +0 -1
- package/src/backend/__tests__/CrudForm.datetime.test.tsx +0 -1
- package/src/backend/__tests__/CrudForm.defaults.test.tsx +0 -1
- package/src/backend/__tests__/CrudForm.dotPath.test.tsx +311 -0
- package/src/backend/__tests__/CrudForm.doubleSubmit.test.tsx +0 -1
- package/src/backend/__tests__/CrudForm.navigation.test.tsx +120 -1
- package/src/backend/__tests__/CrudForm.optimisticLock.test.tsx +287 -0
- package/src/backend/__tests__/CrudForm.render.test.tsx +74 -2
- package/src/backend/__tests__/CrudForm.sortable.test.tsx +0 -1
- package/src/backend/__tests__/CrudForm.transformData.test.tsx +0 -1
- package/src/backend/__tests__/CrudForm.unsavedNavigation.test.tsx +51 -3
- package/src/backend/__tests__/CrudForm.validation.test.tsx +0 -1
- package/src/backend/__tests__/CustomDataSection.test.tsx +0 -1
- package/src/backend/__tests__/FlashMessages.test.tsx +47 -0
- package/src/backend/__tests__/JsonBuilder.test.tsx +135 -0
- package/src/backend/__tests__/lazy-heavy-libraries.test.ts +16 -3
- package/src/backend/conflicts/RecordConflictBanner.tsx +77 -0
- package/src/backend/conflicts/__tests__/RecordConflictBanner.test.tsx +61 -0
- package/src/backend/conflicts/__tests__/store.test.ts +78 -0
- package/src/backend/conflicts/index.ts +49 -0
- package/src/backend/conflicts/store.ts +94 -0
- package/src/backend/detail/ActivitiesSection.tsx +1 -0
- package/src/backend/detail/InlineEditors.tsx +14 -76
- package/src/backend/detail/RecordNotFoundState.tsx +13 -6
- package/src/backend/detail/__tests__/RecordNotFoundState.test.tsx +80 -0
- package/src/backend/filters/AdvancedFilterPanel.tsx +27 -0
- package/src/backend/filters/FilteredEmptyResults.tsx +23 -10
- package/src/backend/filters/ListEmptyState.tsx +69 -0
- package/src/backend/filters/SearchEmptyResults.tsx +42 -0
- package/src/backend/filters/__tests__/AdvancedFilterPanel.test.tsx +98 -0
- package/src/backend/filters/__tests__/ListEmptyState.test.tsx +53 -0
- package/src/backend/icons/lucideRegistry.generated.tsx +8 -0
- package/src/backend/injection/__tests__/useGuardedMutation.optimisticLock.test.tsx +118 -0
- package/src/backend/injection/useGuardedMutation.ts +16 -1
- package/src/backend/inputs/MarkdownField.tsx +34 -0
- package/src/backend/inputs/MdxEditorImpl.tsx +113 -0
- package/src/backend/inputs/SwitchableMarkdownInput.tsx +5 -54
- package/src/backend/markdown/__tests__/markdownToPlainText.test.ts +13 -0
- package/src/backend/markdown/markdownToPlainText.ts +41 -0
- package/src/backend/messages/EmailThreadsPanel.tsx +351 -0
- package/src/backend/messages/__tests__/mergeOptimisticEmailThreads.test.ts +126 -0
- package/src/backend/messages/index.ts +12 -0
- package/src/backend/messages/mergeOptimisticEmailThreads.ts +84 -0
- package/src/backend/notifications/NotificationDispatcher.ts +2 -0
- package/src/backend/notifications/useNotificationsPoll.ts +5 -0
- package/src/backend/notifications/useNotificationsSse.ts +6 -0
- package/src/backend/sidebar/SidebarCustomizationEditor.tsx +35 -11
- package/src/backend/utils/__tests__/optimisticLock.test.ts +126 -0
- package/src/backend/utils/optimisticLock.ts +70 -0
- package/src/portal/hooks/useTenantContext.ts +7 -2
- package/src/primitives/__tests__/checkbox.test.tsx +28 -0
- package/src/primitives/alert.tsx +1 -1
- package/src/primitives/checkbox.tsx +2 -1
- package/src/types/css.d.ts +5 -0
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { useRouter } from "next/navigation";
|
|
5
5
|
import { useReactTable, getCoreRowModel, getSortedRowModel, flexRender } from "@tanstack/react-table";
|
|
6
6
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
7
|
-
import { RefreshCw, Loader2, SlidersHorizontal, MoreHorizontal, Circle, Filter, ChevronUp, ChevronDown, ChevronsUpDown, Check } from "lucide-react";
|
|
7
|
+
import { RefreshCw, Loader2, SlidersHorizontal, MoreHorizontal, Circle, Filter, ChevronUp, ChevronDown, ChevronsUpDown, Check, Inbox } from "lucide-react";
|
|
8
8
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../primitives/table.js";
|
|
9
9
|
import { Button } from "../primitives/button.js";
|
|
10
10
|
import { Checkbox } from "../primitives/checkbox.js";
|
|
@@ -22,6 +22,7 @@ import { TooltipProvider } from "../primitives/tooltip.js";
|
|
|
22
22
|
import { TruncatedCell } from "./TruncatedCell.js";
|
|
23
23
|
import { FilterBar } from "./FilterBar.js";
|
|
24
24
|
import { FilteredEmptyResults } from "./filters/FilteredEmptyResults.js";
|
|
25
|
+
import { SearchEmptyResults } from "./filters/SearchEmptyResults.js";
|
|
25
26
|
import { useCustomFieldFilterDefs } from "./utils/customFieldFilters.js";
|
|
26
27
|
import { fetchCustomFieldDefinitionsPayload } from "./utils/customFieldDefs.js";
|
|
27
28
|
import { RowActions } from "./RowActions.js";
|
|
@@ -31,7 +32,9 @@ import { useAppEvent } from "./injection/useAppEvent.js";
|
|
|
31
32
|
import { useInjectionDataWidgets } from "./injection/useInjectionDataWidgets.js";
|
|
32
33
|
import { resolveInjectedIcon } from "./injection/resolveInjectedIcon.js";
|
|
33
34
|
import { serializeExport, defaultExportFilename } from "@open-mercato/shared/lib/crud/exporters";
|
|
34
|
-
import { apiCall } from "./utils/apiCall.js";
|
|
35
|
+
import { apiCall, withScopedApiRequestHeaders } from "./utils/apiCall.js";
|
|
36
|
+
import { buildOptimisticLockHeader } from "./utils/optimisticLock.js";
|
|
37
|
+
import { surfaceRecordConflict } from "./conflicts/index.js";
|
|
35
38
|
import { raiseCrudError } from "./utils/serverErrors.js";
|
|
36
39
|
import { PerspectiveSidebar } from "./PerspectiveSidebar.js";
|
|
37
40
|
import { Popover, PopoverTrigger, PopoverContent } from "../primitives/popover.js";
|
|
@@ -643,6 +646,7 @@ function DataTable({
|
|
|
643
646
|
injectionSpotId,
|
|
644
647
|
injectionContext,
|
|
645
648
|
replacementHandle,
|
|
649
|
+
extensionTableId: extensionTableIdProp,
|
|
646
650
|
stickyFirstColumn = false,
|
|
647
651
|
stickyActionsColumn = false,
|
|
648
652
|
virtualized = false,
|
|
@@ -803,14 +807,20 @@ function DataTable({
|
|
|
803
807
|
const initialPerspectiveAppliedRef = React.useRef(Boolean(mergedInitialSettings));
|
|
804
808
|
const extensionTableId = React.useMemo(() => {
|
|
805
809
|
if (perspective?.tableId) return perspective.tableId;
|
|
810
|
+
if (extensionTableIdProp) return extensionTableIdProp;
|
|
806
811
|
if (injectionSpotId?.startsWith("data-table:")) return injectionSpotId.slice("data-table:".length);
|
|
807
812
|
return null;
|
|
808
|
-
}, [injectionSpotId, perspective?.tableId]);
|
|
809
|
-
const resolvedInjectionSpotId = injectionSpotId ?? (perspective?.tableId ? `data-table:${perspective.tableId}` : null);
|
|
813
|
+
}, [injectionSpotId, perspective?.tableId, extensionTableIdProp]);
|
|
814
|
+
const resolvedInjectionSpotId = injectionSpotId ?? (perspective?.tableId ? `data-table:${perspective.tableId}` : null) ?? (extensionTableIdProp ? `data-table:${extensionTableIdProp}` : null);
|
|
810
815
|
const resolvedReplacementHandle = replacementHandle ?? ComponentReplacementHandles.dataTable(extensionTableId ?? "unknown");
|
|
811
816
|
const baseInjectionContext = React.useMemo(
|
|
812
|
-
() =>
|
|
813
|
-
|
|
817
|
+
() => {
|
|
818
|
+
if (injectionContext) return injectionContext;
|
|
819
|
+
const resolvedTableId = extensionTableId ?? perspective?.tableId ?? null;
|
|
820
|
+
const baseTitle = typeof title === "string" ? title : void 0;
|
|
821
|
+
return { tableId: resolvedTableId, title: baseTitle };
|
|
822
|
+
},
|
|
823
|
+
[injectionContext, perspective?.tableId, extensionTableId, title]
|
|
814
824
|
);
|
|
815
825
|
const headerInjectionSpotId = React.useMemo(
|
|
816
826
|
() => resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:header` : null,
|
|
@@ -1217,13 +1227,17 @@ function DataTable({
|
|
|
1217
1227
|
if (process.env.NODE_ENV !== "production") {
|
|
1218
1228
|
console.debug("[DataTable] perspective payload", payload);
|
|
1219
1229
|
}
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1230
|
+
const existing = input.perspectiveId ? perspectiveData?.perspectives.find((p) => p.id === input.perspectiveId) ?? null : null;
|
|
1231
|
+
const call = await withScopedApiRequestHeaders(
|
|
1232
|
+
buildOptimisticLockHeader(existing?.updatedAt ?? null),
|
|
1233
|
+
() => apiCall(
|
|
1234
|
+
`/api/perspectives/${encodeURIComponent(perspectiveTableId)}`,
|
|
1235
|
+
{
|
|
1236
|
+
method: "POST",
|
|
1237
|
+
headers: { "Content-Type": "application/json" },
|
|
1238
|
+
body: JSON.stringify(payload)
|
|
1239
|
+
}
|
|
1240
|
+
)
|
|
1227
1241
|
);
|
|
1228
1242
|
if (call.status === 404) {
|
|
1229
1243
|
throw new Error(t("ui.dataTable.perspectives.error.apiUnavailable", "Perspectives API is not available. Run `yarn generate` to regenerate module routes and restart the dev server."));
|
|
@@ -1242,6 +1256,12 @@ function DataTable({
|
|
|
1242
1256
|
if (data2.perspective) {
|
|
1243
1257
|
applyPerspectiveSettings(data2.perspective.settings, data2.perspective.id);
|
|
1244
1258
|
}
|
|
1259
|
+
},
|
|
1260
|
+
onError: (error2) => {
|
|
1261
|
+
if (perspectiveTableId) {
|
|
1262
|
+
void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey });
|
|
1263
|
+
}
|
|
1264
|
+
surfaceRecordConflict(error2, t);
|
|
1245
1265
|
}
|
|
1246
1266
|
});
|
|
1247
1267
|
const resolveColumnLabel = React.useCallback((column) => {
|
|
@@ -1940,6 +1960,20 @@ function DataTable({
|
|
|
1940
1960
|
const toolbarWrapperClassName = embedded ? "mt-2" : "mt-3 pt-3 border-t";
|
|
1941
1961
|
const tableScrollWrapperClassName = embedded ? "" : "overflow-auto";
|
|
1942
1962
|
const virtualScrollRef = React.useRef(null);
|
|
1963
|
+
const [tableScrollEl, setTableScrollEl] = React.useState(null);
|
|
1964
|
+
const [emptyStateViewportWidth, setEmptyStateViewportWidth] = React.useState(null);
|
|
1965
|
+
const setTableScrollWrapperRef = React.useCallback((node) => {
|
|
1966
|
+
setTableScrollEl(node);
|
|
1967
|
+
virtualScrollRef.current = node;
|
|
1968
|
+
}, []);
|
|
1969
|
+
React.useEffect(() => {
|
|
1970
|
+
if (!tableScrollEl || typeof ResizeObserver === "undefined") return;
|
|
1971
|
+
const update = () => setEmptyStateViewportWidth(tableScrollEl.clientWidth);
|
|
1972
|
+
update();
|
|
1973
|
+
const observer = new ResizeObserver(update);
|
|
1974
|
+
observer.observe(tableScrollEl);
|
|
1975
|
+
return () => observer.disconnect();
|
|
1976
|
+
}, [tableScrollEl]);
|
|
1943
1977
|
const allRows = table.getRowModel().rows;
|
|
1944
1978
|
const rowVirtualizer = virtualized ? useVirtualizer({
|
|
1945
1979
|
count: allRows.length,
|
|
@@ -2026,7 +2060,7 @@ function DataTable({
|
|
|
2026
2060
|
sensors: dndSensors,
|
|
2027
2061
|
columnIds: headerColumnIds,
|
|
2028
2062
|
onDragEnd: handleHeaderDragEnd,
|
|
2029
|
-
children: /* @__PURE__ */ jsx("div", { ref:
|
|
2063
|
+
children: /* @__PURE__ */ jsx("div", { ref: setTableScrollWrapperRef, className: tableScrollWrapperClassName, style: virtualMaxHeightStyle, children: /* @__PURE__ */ jsxs(Table, { className: "min-w-[640px] md:min-w-0", children: [
|
|
2030
2064
|
/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
2031
2065
|
hasInjectedBulkActions ? /* @__PURE__ */ jsx(TableHead, { className: "w-8", children: /* @__PURE__ */ jsx(
|
|
2032
2066
|
Checkbox,
|
|
@@ -2168,19 +2202,36 @@ function DataTable({
|
|
|
2168
2202
|
const bottomPadding = lastItem ? rowVirtualizer.getTotalSize() - lastItem.end : 0;
|
|
2169
2203
|
return bottomPadding > 0 ? /* @__PURE__ */ jsx("tr", { style: { height: `${bottomPadding}px` } }) : null;
|
|
2170
2204
|
})() : null
|
|
2171
|
-
] }) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: mergedColumns.length + (rowActions || injectedRowActions.length > 0 ? 1 : 0) + (hasInjectedBulkActions ? 1 : 0), className: "
|
|
2172
|
-
|
|
2205
|
+
] }) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: mergedColumns.length + (rowActions || injectedRowActions.length > 0 ? 1 : 0) + (hasInjectedBulkActions ? 1 : 0), className: "p-0", children: /* @__PURE__ */ jsx(
|
|
2206
|
+
"div",
|
|
2173
2207
|
{
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2208
|
+
className: cn("sticky left-0 flex justify-center py-6", emptyStateViewportWidth ? "" : "w-fit"),
|
|
2209
|
+
style: emptyStateViewportWidth ? { width: emptyStateViewportWidth } : void 0,
|
|
2210
|
+
children: filterAwareEmptyState?.active ? /* @__PURE__ */ jsx(
|
|
2211
|
+
FilteredEmptyResults,
|
|
2212
|
+
{
|
|
2213
|
+
entityNamePlural: filterAwareEmptyState.entityNamePlural,
|
|
2214
|
+
canRemoveLast: filterAwareEmptyState.canRemoveLast,
|
|
2215
|
+
onClearAll: filterAwareEmptyState.onClearAll,
|
|
2216
|
+
onRemoveLast: filterAwareEmptyState.onRemoveLast,
|
|
2217
|
+
onClearSearch: searchValue && searchValue.trim().length > 0 && onSearchChange ? () => onSearchChange("") : void 0
|
|
2218
|
+
}
|
|
2219
|
+
) : searchValue && searchValue.trim().length > 0 && onSearchChange ? /* @__PURE__ */ jsx(
|
|
2220
|
+
SearchEmptyResults,
|
|
2221
|
+
{
|
|
2222
|
+
query: searchValue.trim(),
|
|
2223
|
+
entityNamePlural: filterAwareEmptyState?.entityNamePlural,
|
|
2224
|
+
onClearSearch: () => onSearchChange("")
|
|
2225
|
+
}
|
|
2226
|
+
) : emptyState && typeof emptyState !== "string" ? emptyState : /* @__PURE__ */ jsx(
|
|
2227
|
+
EmptyState,
|
|
2228
|
+
{
|
|
2229
|
+
variant: "subtle",
|
|
2230
|
+
icon: /* @__PURE__ */ jsx(Inbox, { className: "size-6", "aria-hidden": true }),
|
|
2231
|
+
title: typeof emptyState === "string" ? emptyState : t("ui.dataTable.emptyState.default", "No results."),
|
|
2232
|
+
description: typeof emptyState === "string" ? void 0 : t("ui.dataTable.emptyState.defaultDescription", "Items will appear here once added.")
|
|
2233
|
+
}
|
|
2234
|
+
)
|
|
2184
2235
|
}
|
|
2185
2236
|
) }) }) })
|
|
2186
2237
|
] }) })
|