@open-mercato/ui 0.6.4-develop.4382.1.6b4f656b77 → 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
|
@@ -8,6 +8,22 @@ const flashKindToAlertStatus = {
|
|
|
8
8
|
warning: "warning",
|
|
9
9
|
info: "information"
|
|
10
10
|
};
|
|
11
|
+
function normalizeFlashKind(value) {
|
|
12
|
+
if (value && Object.prototype.hasOwnProperty.call(flashKindToAlertStatus, value)) {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
return "success";
|
|
16
|
+
}
|
|
17
|
+
function isSameOriginFlashNavigation() {
|
|
18
|
+
if (typeof window === "undefined") return false;
|
|
19
|
+
const referrer = document.referrer;
|
|
20
|
+
if (!referrer) return true;
|
|
21
|
+
try {
|
|
22
|
+
return new URL(referrer).origin === window.location.origin;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
11
27
|
function flash(message, type = "info") {
|
|
12
28
|
if (typeof window === "undefined") return;
|
|
13
29
|
const evt = new CustomEvent("flash", { detail: { message, type } });
|
|
@@ -101,12 +117,15 @@ function FlashMessagesInner() {
|
|
|
101
117
|
if (typeof window === "undefined") return;
|
|
102
118
|
const url = new URL(window.location.href);
|
|
103
119
|
const message = url.searchParams.get("flash");
|
|
104
|
-
const type = url.searchParams.get("type")
|
|
120
|
+
const type = normalizeFlashKind(url.searchParams.get("type"));
|
|
105
121
|
if (message) {
|
|
106
|
-
|
|
122
|
+
const trusted = isSameOriginFlashNavigation();
|
|
107
123
|
url.searchParams.delete("flash");
|
|
108
124
|
url.searchParams.delete("type");
|
|
109
125
|
window.history.replaceState({}, "", url.toString());
|
|
126
|
+
if (trusted) {
|
|
127
|
+
showFlash(message, type);
|
|
128
|
+
}
|
|
110
129
|
}
|
|
111
130
|
}, [locationKey, showFlash]);
|
|
112
131
|
React.useEffect(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/backend/FlashMessages.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Alert, type AlertStatus } from '../primitives/alert'\n\nexport type FlashKind = 'success' | 'error' | 'warning' | 'info'\n\nconst flashKindToAlertStatus: Record<FlashKind, AlertStatus> = {\n success: 'success',\n error: 'error',\n warning: 'warning',\n info: 'information',\n}\n\n// Programmatic API to show a flash message without navigation.\n// Consumers can import { flash } and call flash('text', 'error').\nexport function flash(message: string, type: FlashKind = 'info') {\n if (typeof window === 'undefined') return\n const evt = new CustomEvent('flash', { detail: { message, type } })\n window.dispatchEvent(evt)\n}\n\ntype HistoryMethod = History['pushState']\n\nfunction useLocationKey() {\n const [locationKey, setLocationKey] = React.useState(() => {\n if (typeof window === 'undefined') return ''\n return window.location.href\n })\n const locationKeyRef = React.useRef(locationKey)\n\n React.useEffect(() => {\n locationKeyRef.current = locationKey\n }, [locationKey])\n\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n\n let active = true\n const scheduleUpdate = (href: string) => {\n const run = () => {\n if (!active) return\n if (locationKeyRef.current === href) return\n locationKeyRef.current = href\n setLocationKey(href)\n }\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(run)\n } else {\n setTimeout(run, 0)\n }\n }\n const updateLocation = () => {\n if (!active) return\n const href = window.location.href\n if (href === locationKeyRef.current) return\n scheduleUpdate(href)\n }\n\n const deferredUpdateLocation = () => {\n setTimeout(updateLocation, 0)\n }\n\n const originalPush: HistoryMethod = window.history.pushState.bind(window.history)\n const originalReplace: HistoryMethod = window.history.replaceState.bind(window.history)\n\n const pushState: HistoryMethod = (...args) => {\n originalPush(...args)\n deferredUpdateLocation()\n }\n\n const replaceState: HistoryMethod = (...args) => {\n originalReplace(...args)\n deferredUpdateLocation()\n }\n\n window.history.pushState = pushState\n window.history.replaceState = replaceState\n window.addEventListener('popstate', updateLocation)\n window.addEventListener('hashchange', updateLocation)\n updateLocation()\n\n return () => {\n active = false\n window.history.pushState = originalPush\n window.history.replaceState = originalReplace\n window.removeEventListener('popstate', updateLocation)\n window.removeEventListener('hashchange', updateLocation)\n }\n }, [])\n\n return locationKey\n}\n\nfunction FlashMessagesInner() {\n const [msg, setMsg] = React.useState<string | null>(null)\n const [kind, setKind] = React.useState<FlashKind>('info')\n const locationKey = useLocationKey()\n const dismissTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const clearDismissTimer = React.useCallback(() => {\n if (dismissTimerRef.current) {\n clearTimeout(dismissTimerRef.current)\n dismissTimerRef.current = null\n }\n }, [])\n\n const showFlash = React.useCallback((message: string, type: FlashKind) => {\n clearDismissTimer()\n setMsg(message)\n setKind(type)\n dismissTimerRef.current = setTimeout(() => {\n dismissTimerRef.current = null\n setMsg(null)\n }, 3000)\n }, [clearDismissTimer])\n\n React.useEffect(() => {\n return () => {\n clearDismissTimer()\n }\n }, [clearDismissTimer])\n\n // Read flash from URL on any navigation change (client-side too)\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const url = new URL(window.location.href)\n const message = url.searchParams.get('flash')\n const type = (url.searchParams.get('type')
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Alert, type AlertStatus } from '../primitives/alert'\n\nexport type FlashKind = 'success' | 'error' | 'warning' | 'info'\n\nconst flashKindToAlertStatus: Record<FlashKind, AlertStatus> = {\n success: 'success',\n error: 'error',\n warning: 'warning',\n info: 'information',\n}\n\nfunction normalizeFlashKind(value: string | null | undefined): FlashKind {\n if (value && Object.prototype.hasOwnProperty.call(flashKindToAlertStatus, value)) {\n return value as FlashKind\n }\n return 'success'\n}\n\n// A URL-supplied flash is only trustworthy when the navigation that carried it\n// originated from the same origin (an in-app POST->GET redirect or client-side\n// navigation). Cross-origin links \u2014 the phishing/content-spoofing vector \u2014 must\n// not be allowed to render attacker-controlled copy inside an authoritative banner.\nfunction isSameOriginFlashNavigation(): boolean {\n if (typeof window === 'undefined') return false\n const referrer = document.referrer\n // No referrer (direct load, bookmark, or referrer stripped) is ambiguous, not a\n // cross-origin redirect we can attribute to an attacker page; allow it.\n if (!referrer) return true\n try {\n return new URL(referrer).origin === window.location.origin\n } catch {\n return false\n }\n}\n\n// Programmatic API to show a flash message without navigation.\n// Consumers can import { flash } and call flash('text', 'error').\nexport function flash(message: string, type: FlashKind = 'info') {\n if (typeof window === 'undefined') return\n const evt = new CustomEvent('flash', { detail: { message, type } })\n window.dispatchEvent(evt)\n}\n\ntype HistoryMethod = History['pushState']\n\nfunction useLocationKey() {\n const [locationKey, setLocationKey] = React.useState(() => {\n if (typeof window === 'undefined') return ''\n return window.location.href\n })\n const locationKeyRef = React.useRef(locationKey)\n\n React.useEffect(() => {\n locationKeyRef.current = locationKey\n }, [locationKey])\n\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n\n let active = true\n const scheduleUpdate = (href: string) => {\n const run = () => {\n if (!active) return\n if (locationKeyRef.current === href) return\n locationKeyRef.current = href\n setLocationKey(href)\n }\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(run)\n } else {\n setTimeout(run, 0)\n }\n }\n const updateLocation = () => {\n if (!active) return\n const href = window.location.href\n if (href === locationKeyRef.current) return\n scheduleUpdate(href)\n }\n\n const deferredUpdateLocation = () => {\n setTimeout(updateLocation, 0)\n }\n\n const originalPush: HistoryMethod = window.history.pushState.bind(window.history)\n const originalReplace: HistoryMethod = window.history.replaceState.bind(window.history)\n\n const pushState: HistoryMethod = (...args) => {\n originalPush(...args)\n deferredUpdateLocation()\n }\n\n const replaceState: HistoryMethod = (...args) => {\n originalReplace(...args)\n deferredUpdateLocation()\n }\n\n window.history.pushState = pushState\n window.history.replaceState = replaceState\n window.addEventListener('popstate', updateLocation)\n window.addEventListener('hashchange', updateLocation)\n updateLocation()\n\n return () => {\n active = false\n window.history.pushState = originalPush\n window.history.replaceState = originalReplace\n window.removeEventListener('popstate', updateLocation)\n window.removeEventListener('hashchange', updateLocation)\n }\n }, [])\n\n return locationKey\n}\n\nfunction FlashMessagesInner() {\n const [msg, setMsg] = React.useState<string | null>(null)\n const [kind, setKind] = React.useState<FlashKind>('info')\n const locationKey = useLocationKey()\n const dismissTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const clearDismissTimer = React.useCallback(() => {\n if (dismissTimerRef.current) {\n clearTimeout(dismissTimerRef.current)\n dismissTimerRef.current = null\n }\n }, [])\n\n const showFlash = React.useCallback((message: string, type: FlashKind) => {\n clearDismissTimer()\n setMsg(message)\n setKind(type)\n dismissTimerRef.current = setTimeout(() => {\n dismissTimerRef.current = null\n setMsg(null)\n }, 3000)\n }, [clearDismissTimer])\n\n React.useEffect(() => {\n return () => {\n clearDismissTimer()\n }\n }, [clearDismissTimer])\n\n // Read flash from URL on any navigation change (client-side too)\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const url = new URL(window.location.href)\n const message = url.searchParams.get('flash')\n const type = normalizeFlashKind(url.searchParams.get('type'))\n if (message) {\n // Always strip the params so a spoofed link does not linger in history,\n // but only render the banner for same-origin navigations.\n const trusted = isSameOriginFlashNavigation()\n url.searchParams.delete('flash')\n url.searchParams.delete('type')\n window.history.replaceState({}, '', url.toString())\n if (trusted) {\n showFlash(message, type)\n }\n }\n }, [locationKey, showFlash])\n\n // Listen for programmatic flash events\n React.useEffect(() => {\n const handler = (e: Event) => {\n const ce = e as CustomEvent<{ message?: string; type?: FlashKind }>\n const text = ce.detail?.message\n const t = ce.detail?.type || 'info'\n if (!text) return\n showFlash(text, t)\n }\n window.addEventListener('flash', handler as EventListener)\n return () => window.removeEventListener('flash', handler as EventListener)\n }, [showFlash])\n\n const handleDismiss = React.useCallback(() => {\n clearDismissTimer()\n setMsg(null)\n }, [clearDismissTimer])\n\n if (!msg) return null\n\n return (\n <div className=\"pointer-events-none fixed left-3 right-3 top-3 z-toast sm:left-auto sm:right-4 sm:w-[380px]\">\n <div className=\"pointer-events-auto\">\n <Alert\n status={flashKindToAlertStatus[kind]}\n size=\"sm\"\n dismissible\n onDismiss={handleDismiss}\n className=\"shadow-md\"\n >\n {msg}\n </Alert>\n </div>\n </div>\n )\n}\n\nexport function FlashMessages() {\n return (\n <React.Suspense fallback={null}>\n <FlashMessagesInner />\n </React.Suspense>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
SelectValue
|
|
14
14
|
} from "../primitives/select.js";
|
|
15
15
|
import { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from "lucide-react";
|
|
16
|
+
import { useConfirmDialog } from "./confirm-dialog/index.js";
|
|
16
17
|
function cn(...classes) {
|
|
17
18
|
return classes.filter(Boolean).join(" ");
|
|
18
19
|
}
|
|
@@ -21,6 +22,27 @@ function getJsonType(value) {
|
|
|
21
22
|
if (Array.isArray(value)) return "array";
|
|
22
23
|
return typeof value;
|
|
23
24
|
}
|
|
25
|
+
function defaultValueForType(type) {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case "string":
|
|
28
|
+
return "";
|
|
29
|
+
case "number":
|
|
30
|
+
return 0;
|
|
31
|
+
case "boolean":
|
|
32
|
+
return false;
|
|
33
|
+
case "object":
|
|
34
|
+
return {};
|
|
35
|
+
case "array":
|
|
36
|
+
return [];
|
|
37
|
+
case "null":
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function toRawString(value) {
|
|
42
|
+
if (value === null || value === void 0) return "{}";
|
|
43
|
+
if (typeof value === "object") return JSON.stringify(value, null, 2);
|
|
44
|
+
return String(value);
|
|
45
|
+
}
|
|
24
46
|
function JsonBuilder({
|
|
25
47
|
value,
|
|
26
48
|
onChange,
|
|
@@ -28,26 +50,24 @@ function JsonBuilder({
|
|
|
28
50
|
error
|
|
29
51
|
}) {
|
|
30
52
|
const [mode, setMode] = React.useState("raw");
|
|
31
|
-
const [rawString, setRawString] = React.useState(() =>
|
|
32
|
-
|
|
33
|
-
return typeof value === "object" ? JSON.stringify(value, null, 2) : String(value || "{}");
|
|
34
|
-
});
|
|
53
|
+
const [rawString, setRawString] = React.useState(() => toRawString(value));
|
|
54
|
+
const [rawDirty, setRawDirty] = React.useState(false);
|
|
35
55
|
const [parseError, setParseError] = React.useState(null);
|
|
56
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
36
57
|
React.useEffect(() => {
|
|
37
|
-
if (value === null) {
|
|
38
|
-
|
|
39
|
-
onChange({});
|
|
40
|
-
}
|
|
41
|
-
setRawString("{}");
|
|
42
|
-
setParseError(null);
|
|
43
|
-
return;
|
|
58
|
+
if (value === null && !disabled) {
|
|
59
|
+
onChange({});
|
|
44
60
|
}
|
|
45
|
-
|
|
61
|
+
}, [value, disabled, onChange]);
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
if (rawDirty) return;
|
|
64
|
+
if (value !== null && typeof value === "object") {
|
|
46
65
|
setRawString(JSON.stringify(value, null, 2));
|
|
47
66
|
setParseError(null);
|
|
48
67
|
}
|
|
49
|
-
}, [value,
|
|
68
|
+
}, [value, rawDirty]);
|
|
50
69
|
const handleRawChange = (str) => {
|
|
70
|
+
setRawDirty(true);
|
|
51
71
|
setRawString(str);
|
|
52
72
|
try {
|
|
53
73
|
if (str.trim() === "") {
|
|
@@ -63,6 +83,12 @@ function JsonBuilder({
|
|
|
63
83
|
setParseError("Invalid JSON");
|
|
64
84
|
}
|
|
65
85
|
};
|
|
86
|
+
const switchToRaw = () => {
|
|
87
|
+
setRawDirty(false);
|
|
88
|
+
setRawString(toRawString(value));
|
|
89
|
+
setParseError(null);
|
|
90
|
+
setMode("raw");
|
|
91
|
+
};
|
|
66
92
|
const switchToBuilder = () => {
|
|
67
93
|
try {
|
|
68
94
|
if (typeof value === "string") {
|
|
@@ -82,7 +108,7 @@ function JsonBuilder({
|
|
|
82
108
|
variant: "ghost",
|
|
83
109
|
size: "sm",
|
|
84
110
|
className: cn(mode === "raw" && "bg-muted text-foreground"),
|
|
85
|
-
onClick:
|
|
111
|
+
onClick: switchToRaw,
|
|
86
112
|
children: [
|
|
87
113
|
/* @__PURE__ */ jsx(Code, { className: "w-4 h-4" }),
|
|
88
114
|
"Raw JSON"
|
|
@@ -129,39 +155,32 @@ function JsonBuilder({
|
|
|
129
155
|
data: value,
|
|
130
156
|
onChange,
|
|
131
157
|
readOnly: disabled,
|
|
158
|
+
confirm,
|
|
132
159
|
isRoot: true
|
|
133
160
|
}
|
|
134
161
|
) : /* @__PURE__ */ jsx("div", { className: "text-muted-foreground italic p-4 text-center", children: "Value is not an object or array. Switch to Raw to edit." }) }),
|
|
135
|
-
error && /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600", children: error })
|
|
162
|
+
error && /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600", children: error }),
|
|
163
|
+
ConfirmDialogElement
|
|
136
164
|
] });
|
|
137
165
|
}
|
|
138
|
-
function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot }) {
|
|
166
|
+
function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }) {
|
|
139
167
|
const type = getJsonType(data);
|
|
140
168
|
const isContainer = type === "object" || type === "array";
|
|
141
169
|
const [collapsed, setCollapsed] = React.useState(false);
|
|
142
|
-
const handleTypeChange = (newType) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
case "object":
|
|
155
|
-
newVal = {};
|
|
156
|
-
break;
|
|
157
|
-
case "array":
|
|
158
|
-
newVal = [];
|
|
159
|
-
break;
|
|
160
|
-
case "null":
|
|
161
|
-
newVal = null;
|
|
162
|
-
break;
|
|
170
|
+
const handleTypeChange = async (newType) => {
|
|
171
|
+
if (newType === type) return;
|
|
172
|
+
const itemCount = type === "object" ? Object.keys(data).length : type === "array" ? data.length : 0;
|
|
173
|
+
if (itemCount > 0 && confirm) {
|
|
174
|
+
const noun = type === "object" ? `${itemCount} ${itemCount === 1 ? "property" : "properties"}` : `${itemCount} ${itemCount === 1 ? "item" : "items"}`;
|
|
175
|
+
const confirmed = await confirm({
|
|
176
|
+
title: "Change value type?",
|
|
177
|
+
text: `Changing the type from ${type} to ${newType} will discard the ${noun} currently configured. This cannot be undone.`,
|
|
178
|
+
confirmText: "Discard and change",
|
|
179
|
+
variant: "destructive"
|
|
180
|
+
});
|
|
181
|
+
if (!confirmed) return;
|
|
163
182
|
}
|
|
164
|
-
onChange(
|
|
183
|
+
onChange(defaultValueForType(newType));
|
|
165
184
|
};
|
|
166
185
|
const handleAddKey = () => {
|
|
167
186
|
if (type === "object") {
|
|
@@ -216,7 +235,9 @@ function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot }) {
|
|
|
216
235
|
Select,
|
|
217
236
|
{
|
|
218
237
|
value: type,
|
|
219
|
-
onValueChange: (next) =>
|
|
238
|
+
onValueChange: (next) => {
|
|
239
|
+
void handleTypeChange(next);
|
|
240
|
+
},
|
|
220
241
|
children: [
|
|
221
242
|
/* @__PURE__ */ jsx(SelectTrigger, { size: "sm", className: "w-auto min-w-[6rem]", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
222
243
|
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
@@ -300,7 +321,8 @@ function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot }) {
|
|
|
300
321
|
data: val,
|
|
301
322
|
onChange: (v) => handleChildChange(key, v),
|
|
302
323
|
onDelete: () => handleChildDelete(key),
|
|
303
|
-
readOnly
|
|
324
|
+
readOnly,
|
|
325
|
+
confirm
|
|
304
326
|
}
|
|
305
327
|
) })
|
|
306
328
|
] }, idx)),
|
|
@@ -311,7 +333,8 @@ function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot }) {
|
|
|
311
333
|
data: val,
|
|
312
334
|
onChange: (v) => handleChildChange(idx, v),
|
|
313
335
|
onDelete: () => handleChildDelete(idx),
|
|
314
|
-
readOnly
|
|
336
|
+
readOnly,
|
|
337
|
+
confirm
|
|
315
338
|
},
|
|
316
339
|
idx
|
|
317
340
|
)),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/backend/JsonBuilder.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Input } from '../primitives/input'\nimport { InlineInput } from '../primitives/inline-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from 'lucide-react'\n\nfunction cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(' ')\n}\n\nexport type JsonBuilderProps = {\n value: any\n onChange: (value: any) => void\n disabled?: boolean\n error?: string\n}\n\ntype JsonNodeType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'\n\nfunction getJsonType(value: any): JsonNodeType {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return typeof value as JsonNodeType\n}\n\nexport function JsonBuilder({\n value,\n onChange,\n disabled,\n error\n}: JsonBuilderProps) {\n const [mode, setMode] = React.useState<'raw' | 'builder'>('raw')\n const [rawString, setRawString] = React.useState(() => {\n if (value === null) return '{}'\n return typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value || '{}')\n })\n const [parseError, setParseError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (value === null) {\n if (!disabled) {\n onChange({})\n }\n setRawString('{}')\n setParseError(null)\n return\n }\n if (typeof value === 'object') {\n setRawString(JSON.stringify(value, null, 2))\n setParseError(null)\n }\n }, [value, disabled, onChange])\n\n const handleRawChange = (str: string) => {\n setRawString(str)\n try {\n if (str.trim() === '') {\n onChange({})\n setParseError(null)\n } else {\n const parsed = JSON.parse(str)\n onChange(parsed)\n setParseError(null)\n }\n } catch (e) {\n onChange(str)\n setParseError(\"Invalid JSON\")\n }\n }\n\n const switchToBuilder = () => {\n try {\n if (typeof value === 'string') {\n JSON.parse(value)\n }\n setMode('builder')\n } catch (e) {\n alert(\"Cannot switch to Builder mode: Invalid JSON\")\n }\n }\n\n return (\n <div className=\"space-y-4 border rounded-md p-4 bg-card\">\n <div className=\"flex items-center space-x-2 border-b pb-2 mb-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'raw' && \"bg-muted text-foreground\")}\n onClick={() => setMode('raw')}\n >\n <Code className=\"w-4 h-4\" />\n Raw JSON\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'builder' && \"bg-muted text-foreground\")}\n onClick={switchToBuilder}\n >\n <LayoutList className=\"w-4 h-4\" />\n Builder\n </Button>\n </div>\n\n {mode === 'raw' ? (\n <div className=\"space-y-2\">\n <textarea\n value={rawString}\n onChange={(e) => handleRawChange(e.target.value)}\n onBlur={() => {\n try {\n const parsed = JSON.parse(rawString)\n setRawString(JSON.stringify(parsed, null, 2))\n } catch { }\n }}\n placeholder='{\"key\": \"value\"}'\n className=\"w-full rounded border px-3 py-2 min-h-[300px] text-sm font-mono focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n disabled={disabled}\n />\n {parseError && (\n <div className=\"text-xs text-red-600\">Invalid JSON format</div>\n )}\n </div>\n ) : (\n <div className=\"min-h-[300px] text-sm overflow-x-auto\">\n {typeof value === 'object' && value !== null ? (\n <JsonNode\n data={value}\n onChange={onChange}\n readOnly={disabled}\n isRoot\n />\n ) : (\n <div className=\"text-muted-foreground italic p-4 text-center\">\n Value is not an object or array. Switch to Raw to edit.\n </div>\n )}\n </div>\n )}\n\n {error && <div className=\"text-xs text-red-600\">{error}</div>}\n </div>\n )\n}\n\ninterface JsonNodeProps {\n data: any\n onChange: (val: any) => void\n onDelete?: () => void\n readOnly?: boolean\n label?: string\n isRoot?: boolean\n}\n\nfunction JsonNode({ data, onChange, onDelete, readOnly, label, isRoot }: JsonNodeProps) {\n const type = getJsonType(data)\n const isContainer = type === 'object' || type === 'array'\n const [collapsed, setCollapsed] = React.useState(false)\n\n const handleTypeChange = (newType: JsonNodeType) => {\n let newVal: any = ''\n switch (newType) {\n case 'string': newVal = \"\"; break;\n case 'number': newVal = 0; break;\n case 'boolean': newVal = false; break;\n case 'object': newVal = {}; break;\n case 'array': newVal = []; break;\n case 'null': newVal = null; break;\n }\n onChange(newVal)\n }\n\n const handleAddKey = () => {\n if (type === 'object') {\n const newKey = `newKey_${Object.keys(data).length}`\n onChange({ ...data, [newKey]: \"\" })\n } else if (type === 'array') {\n onChange([...data, \"\"])\n }\n }\n\n const handleChildChange = (key: string | number, newVal: any) => {\n if (type === 'object') {\n onChange({ ...data, [key]: newVal })\n } else if (type === 'array') {\n const arr = [...data]\n arr[Number(key)] = newVal\n onChange(arr)\n }\n }\n\n const handleKeyRename = (oldKey: string, newKey: string) => {\n if (oldKey === newKey) return\n const keys = Object.keys(data)\n const newData: any = {}\n keys.forEach(k => {\n if (k === oldKey) {\n newData[newKey] = data[k]\n } else {\n newData[k] = data[k]\n }\n })\n onChange(newData)\n }\n\n const handleChildDelete = (key: string | number) => {\n if (type === 'object') {\n const newData = { ...data }\n delete newData[key as string]\n onChange(newData)\n } else if (type === 'array') {\n onChange(data.filter((_: any, i: number) => i !== key))\n }\n }\n\n return (\n <div className={cn(\"pl-0\", !isRoot && \"pl-4 border-l border-border ml-1\")}>\n <div className=\"flex items-start gap-2 py-1 group\">\n\n {isContainer && (\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" className=\"mt-1 text-muted-foreground hover:text-foreground\" onClick={() => setCollapsed(!collapsed)}>\n {collapsed ? <ChevronRight className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </IconButton>\n )}\n {!isContainer && !isRoot && <div className=\"w-3\" />} {/* Spacer */}\n\n {label !== undefined && !isRoot && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-muted-foreground font-mono\">\n {label}\n </span>\n <span className=\"text-muted-foreground text-xs\">:</span>\n </div>\n )}\n\n <div className=\"flex-1 flex gap-2 items-center flex-wrap\">\n\n {!readOnly && (\n <Select\n value={type}\n onValueChange={(next) => handleTypeChange(next as JsonNodeType)}\n >\n <SelectTrigger size=\"sm\" className=\"w-auto min-w-[6rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"string\">String</SelectItem>\n <SelectItem value=\"number\">Number</SelectItem>\n <SelectItem value=\"boolean\">Boolean</SelectItem>\n <SelectItem value=\"object\">Object</SelectItem>\n <SelectItem value=\"array\">Array</SelectItem>\n <SelectItem value=\"null\">Null</SelectItem>\n </SelectContent>\n </Select>\n )}\n\n {type === 'string' && (\n <Input\n size=\"sm\"\n className=\"flex-1 min-w-0 sm:min-w-[120px]\"\n value={data}\n onChange={e => onChange(e.target.value)}\n disabled={readOnly}\n />\n )}\n {type === 'number' && (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"flex-1 w-full sm:w-[100px]\"\n value={data}\n onChange={e => onChange(parseFloat(e.target.value) || 0)}\n disabled={readOnly}\n />\n )}\n {type === 'boolean' && (\n <Select\n value={String(data)}\n onValueChange={(next) => onChange(next === 'true')}\n disabled={readOnly}\n >\n <SelectTrigger size=\"sm\" className=\"flex-1 w-full sm:w-[100px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">true</SelectItem>\n <SelectItem value=\"false\">false</SelectItem>\n </SelectContent>\n </Select>\n )}\n {type === 'null' && <span className=\"text-xs text-muted-foreground\">null</span>}\n {isContainer && (\n <span className=\"text-xs text-muted-foreground\">\n {type === 'object' ? `{ ${Object.keys(data).length} items }` : `[ ${data.length} items ]`}\n </span>\n )}\n\n {onDelete && !readOnly && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"text-muted-foreground hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove item\"\n onClick={onDelete}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </IconButton>\n )}\n </div>\n </div>\n\n {isContainer && !collapsed && (\n <div className=\"flex flex-col gap-1 w-full pl-2\">\n {type === 'object' && Object.entries(data).map(([key, val], idx) => (\n <div key={idx} className=\"flex\">\n <div className=\"pt-2\">\n {/* Key Renamer */}\n <InlineInput\n className=\"w-full sm:w-[100px]\"\n inputClassName=\"text-right text-xs font-mono pr-1\"\n value={key}\n onChange={(e) => handleKeyRename(key, e.target.value)}\n disabled={readOnly}\n />\n </div>\n <div className=\"flex-1\">\n <JsonNode\n data={val}\n onChange={(v) => handleChildChange(key, v)}\n onDelete={() => handleChildDelete(key)}\n readOnly={readOnly}\n />\n </div>\n </div>\n ))}\n\n {type === 'array' && (data as any[]).map((val, idx) => (\n <JsonNode\n key={idx}\n label={String(idx)}\n data={val}\n onChange={(v) => handleChildChange(idx, v)}\n onDelete={() => handleChildDelete(idx)}\n readOnly={readOnly}\n />\n ))}\n\n {!readOnly && (\n <div className=\"pl-4 mt-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddKey}\n className=\"h-6 text-xs\"\n >\n <Plus className=\"w-3 h-3 mr-1\" />\n Add {type === 'object' ? 'Property' : 'Item'}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Input } from '../primitives/input'\nimport { InlineInput } from '../primitives/inline-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from 'lucide-react'\nimport { useConfirmDialog } from './confirm-dialog'\nimport type { ConfirmDialogOptions } from './confirm-dialog'\n\nfunction cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(' ')\n}\n\nexport type JsonBuilderProps = {\n value: any\n onChange: (value: any) => void\n disabled?: boolean\n error?: string\n}\n\ntype JsonNodeType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'\n\ntype ConfirmFn = (options?: ConfirmDialogOptions) => Promise<boolean>\n\nfunction getJsonType(value: any): JsonNodeType {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return typeof value as JsonNodeType\n}\n\nfunction defaultValueForType(type: JsonNodeType): any {\n switch (type) {\n case 'string': return \"\"\n case 'number': return 0\n case 'boolean': return false\n case 'object': return {}\n case 'array': return []\n case 'null': return null\n }\n}\n\nfunction toRawString(value: any): string {\n if (value === null || value === undefined) return '{}'\n if (typeof value === 'object') return JSON.stringify(value, null, 2)\n return String(value)\n}\n\nexport function JsonBuilder({\n value,\n onChange,\n disabled,\n error\n}: JsonBuilderProps) {\n const [mode, setMode] = React.useState<'raw' | 'builder'>('raw')\n const [rawString, setRawString] = React.useState(() => toRawString(value))\n const [rawDirty, setRawDirty] = React.useState(false)\n const [parseError, setParseError] = React.useState<string | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (value === null && !disabled) {\n onChange({})\n }\n }, [value, disabled, onChange])\n\n // Mirror external value changes (e.g. async record load, builder edits) into\n // the raw textarea only while the user has NOT started typing in it. Once the\n // textarea is dirty it becomes the source of truth, so re-deriving it from\n // `value` on every keystroke \u2014 the parent's onChange identity changes each\n // render \u2014 would clobber typing and make Raw JSON uneditable (issue #2817).\n React.useEffect(() => {\n if (rawDirty) return\n if (value !== null && typeof value === 'object') {\n setRawString(JSON.stringify(value, null, 2))\n setParseError(null)\n }\n }, [value, rawDirty])\n\n const handleRawChange = (str: string) => {\n setRawDirty(true)\n setRawString(str)\n try {\n if (str.trim() === '') {\n onChange({})\n setParseError(null)\n } else {\n const parsed = JSON.parse(str)\n onChange(parsed)\n setParseError(null)\n }\n } catch (e) {\n onChange(str)\n setParseError(\"Invalid JSON\")\n }\n }\n\n const switchToRaw = () => {\n // Re-sync the textarea from the current value and let it mirror external\n // changes again until the user edits it.\n setRawDirty(false)\n setRawString(toRawString(value))\n setParseError(null)\n setMode('raw')\n }\n\n const switchToBuilder = () => {\n try {\n if (typeof value === 'string') {\n JSON.parse(value)\n }\n setMode('builder')\n } catch (e) {\n alert(\"Cannot switch to Builder mode: Invalid JSON\")\n }\n }\n\n return (\n <div className=\"space-y-4 border rounded-md p-4 bg-card\">\n <div className=\"flex items-center space-x-2 border-b pb-2 mb-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'raw' && \"bg-muted text-foreground\")}\n onClick={switchToRaw}\n >\n <Code className=\"w-4 h-4\" />\n Raw JSON\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'builder' && \"bg-muted text-foreground\")}\n onClick={switchToBuilder}\n >\n <LayoutList className=\"w-4 h-4\" />\n Builder\n </Button>\n </div>\n\n {mode === 'raw' ? (\n <div className=\"space-y-2\">\n <textarea\n value={rawString}\n onChange={(e) => handleRawChange(e.target.value)}\n onBlur={() => {\n try {\n const parsed = JSON.parse(rawString)\n setRawString(JSON.stringify(parsed, null, 2))\n } catch { }\n }}\n placeholder='{\"key\": \"value\"}'\n className=\"w-full rounded border px-3 py-2 min-h-[300px] text-sm font-mono focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n disabled={disabled}\n />\n {parseError && (\n <div className=\"text-xs text-red-600\">Invalid JSON format</div>\n )}\n </div>\n ) : (\n <div className=\"min-h-[300px] text-sm overflow-x-auto\">\n {typeof value === 'object' && value !== null ? (\n <JsonNode\n data={value}\n onChange={onChange}\n readOnly={disabled}\n confirm={confirm}\n isRoot\n />\n ) : (\n <div className=\"text-muted-foreground italic p-4 text-center\">\n Value is not an object or array. Switch to Raw to edit.\n </div>\n )}\n </div>\n )}\n\n {error && <div className=\"text-xs text-red-600\">{error}</div>}\n {ConfirmDialogElement}\n </div>\n )\n}\n\ninterface JsonNodeProps {\n data: any\n onChange: (val: any) => void\n onDelete?: () => void\n readOnly?: boolean\n label?: string\n isRoot?: boolean\n confirm?: ConfirmFn\n}\n\nfunction JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }: JsonNodeProps) {\n const type = getJsonType(data)\n const isContainer = type === 'object' || type === 'array'\n const [collapsed, setCollapsed] = React.useState(false)\n\n const handleTypeChange = async (newType: JsonNodeType) => {\n if (newType === type) return\n // Switching away from a non-empty container discards its contents. Ask\n // for confirmation first so configured properties aren't lost silently\n // (issue #2817). The Select is controlled by the derived type, so when\n // the user cancels we simply leave the data untouched and the control\n // snaps back to its previous value.\n const itemCount = type === 'object'\n ? Object.keys(data).length\n : type === 'array'\n ? (data as any[]).length\n : 0\n if (itemCount > 0 && confirm) {\n const noun = type === 'object'\n ? `${itemCount} ${itemCount === 1 ? 'property' : 'properties'}`\n : `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`\n const confirmed = await confirm({\n title: 'Change value type?',\n text: `Changing the type from ${type} to ${newType} will discard the ${noun} currently configured. This cannot be undone.`,\n confirmText: 'Discard and change',\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n onChange(defaultValueForType(newType))\n }\n\n const handleAddKey = () => {\n if (type === 'object') {\n const newKey = `newKey_${Object.keys(data).length}`\n onChange({ ...data, [newKey]: \"\" })\n } else if (type === 'array') {\n onChange([...data, \"\"])\n }\n }\n\n const handleChildChange = (key: string | number, newVal: any) => {\n if (type === 'object') {\n onChange({ ...data, [key]: newVal })\n } else if (type === 'array') {\n const arr = [...data]\n arr[Number(key)] = newVal\n onChange(arr)\n }\n }\n\n const handleKeyRename = (oldKey: string, newKey: string) => {\n if (oldKey === newKey) return\n const keys = Object.keys(data)\n const newData: any = {}\n keys.forEach(k => {\n if (k === oldKey) {\n newData[newKey] = data[k]\n } else {\n newData[k] = data[k]\n }\n })\n onChange(newData)\n }\n\n const handleChildDelete = (key: string | number) => {\n if (type === 'object') {\n const newData = { ...data }\n delete newData[key as string]\n onChange(newData)\n } else if (type === 'array') {\n onChange(data.filter((_: any, i: number) => i !== key))\n }\n }\n\n return (\n <div className={cn(\"pl-0\", !isRoot && \"pl-4 border-l border-border ml-1\")}>\n <div className=\"flex items-start gap-2 py-1 group\">\n\n {isContainer && (\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" className=\"mt-1 text-muted-foreground hover:text-foreground\" onClick={() => setCollapsed(!collapsed)}>\n {collapsed ? <ChevronRight className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </IconButton>\n )}\n {!isContainer && !isRoot && <div className=\"w-3\" />} {/* Spacer */}\n\n {label !== undefined && !isRoot && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-muted-foreground font-mono\">\n {label}\n </span>\n <span className=\"text-muted-foreground text-xs\">:</span>\n </div>\n )}\n\n <div className=\"flex-1 flex gap-2 items-center flex-wrap\">\n\n {!readOnly && (\n <Select\n value={type}\n onValueChange={(next) => { void handleTypeChange(next as JsonNodeType) }}\n >\n <SelectTrigger size=\"sm\" className=\"w-auto min-w-[6rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"string\">String</SelectItem>\n <SelectItem value=\"number\">Number</SelectItem>\n <SelectItem value=\"boolean\">Boolean</SelectItem>\n <SelectItem value=\"object\">Object</SelectItem>\n <SelectItem value=\"array\">Array</SelectItem>\n <SelectItem value=\"null\">Null</SelectItem>\n </SelectContent>\n </Select>\n )}\n\n {type === 'string' && (\n <Input\n size=\"sm\"\n className=\"flex-1 min-w-0 sm:min-w-[120px]\"\n value={data}\n onChange={e => onChange(e.target.value)}\n disabled={readOnly}\n />\n )}\n {type === 'number' && (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"flex-1 w-full sm:w-[100px]\"\n value={data}\n onChange={e => onChange(parseFloat(e.target.value) || 0)}\n disabled={readOnly}\n />\n )}\n {type === 'boolean' && (\n <Select\n value={String(data)}\n onValueChange={(next) => onChange(next === 'true')}\n disabled={readOnly}\n >\n <SelectTrigger size=\"sm\" className=\"flex-1 w-full sm:w-[100px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">true</SelectItem>\n <SelectItem value=\"false\">false</SelectItem>\n </SelectContent>\n </Select>\n )}\n {type === 'null' && <span className=\"text-xs text-muted-foreground\">null</span>}\n {isContainer && (\n <span className=\"text-xs text-muted-foreground\">\n {type === 'object' ? `{ ${Object.keys(data).length} items }` : `[ ${data.length} items ]`}\n </span>\n )}\n\n {onDelete && !readOnly && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"text-muted-foreground hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove item\"\n onClick={onDelete}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </IconButton>\n )}\n </div>\n </div>\n\n {isContainer && !collapsed && (\n <div className=\"flex flex-col gap-1 w-full pl-2\">\n {type === 'object' && Object.entries(data).map(([key, val], idx) => (\n <div key={idx} className=\"flex\">\n <div className=\"pt-2\">\n {/* Key Renamer */}\n <InlineInput\n className=\"w-full sm:w-[100px]\"\n inputClassName=\"text-right text-xs font-mono pr-1\"\n value={key}\n onChange={(e) => handleKeyRename(key, e.target.value)}\n disabled={readOnly}\n />\n </div>\n <div className=\"flex-1\">\n <JsonNode\n data={val}\n onChange={(v) => handleChildChange(key, v)}\n onDelete={() => handleChildDelete(key)}\n readOnly={readOnly}\n confirm={confirm}\n />\n </div>\n </div>\n ))}\n\n {type === 'array' && (data as any[]).map((val, idx) => (\n <JsonNode\n key={idx}\n label={String(idx)}\n data={val}\n onChange={(v) => handleChildChange(idx, v)}\n onDelete={() => handleChildDelete(idx)}\n readOnly={readOnly}\n confirm={confirm}\n />\n ))}\n\n {!readOnly && (\n <div className=\"pl-4 mt-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddKey}\n className=\"h-6 text-xs\"\n >\n <Plus className=\"w-3 h-3 mr-1\" />\n Add {type === 'object' ? 'Property' : 'Item'}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,6 +8,7 @@ import { Checkbox } from "../primitives/checkbox.js";
|
|
|
8
8
|
import { Spinner } from "../primitives/spinner.js";
|
|
9
9
|
import { useConfirmDialog } from "./confirm-dialog/index.js";
|
|
10
10
|
import { flash } from "./FlashMessages.js";
|
|
11
|
+
import { surfaceRecordConflict } from "./conflicts/index.js";
|
|
11
12
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
12
13
|
import { ViewChip } from "./views/ViewChip.js";
|
|
13
14
|
import { NewViewForm } from "./views/NewViewForm.js";
|
|
@@ -92,7 +93,8 @@ function PerspectiveSidebar({
|
|
|
92
93
|
perspectiveId: targetId
|
|
93
94
|
});
|
|
94
95
|
flash(t("ui.perspectives.autosave.success", "View saved"), "success");
|
|
95
|
-
} catch {
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
96
98
|
flash(t("ui.perspectives.autosave.error", "Failed to save view"), "error");
|
|
97
99
|
}
|
|
98
100
|
}, 400);
|
|
@@ -152,6 +154,7 @@ function PerspectiveSidebar({
|
|
|
152
154
|
await onSave({ name: name.trim(), isDefault, applyToRoles: [], setRoleDefault: false, perspectiveId: null });
|
|
153
155
|
resetMode();
|
|
154
156
|
} catch (err) {
|
|
157
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
155
158
|
setError(err instanceof Error ? err.message : "Failed to save view");
|
|
156
159
|
}
|
|
157
160
|
};
|
|
@@ -187,6 +190,7 @@ function PerspectiveSidebar({
|
|
|
187
190
|
});
|
|
188
191
|
setRenamingId(null);
|
|
189
192
|
} catch (err) {
|
|
193
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
190
194
|
setError(err instanceof Error ? err.message : "Failed to rename view");
|
|
191
195
|
}
|
|
192
196
|
};
|
|
@@ -228,6 +232,7 @@ function PerspectiveSidebar({
|
|
|
228
232
|
const msg = errMessage.toLowerCase();
|
|
229
233
|
const isDuplicate = msg.includes("duplicate key") || msg.includes("unique constraint");
|
|
230
234
|
if (!isDuplicate) {
|
|
235
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
231
236
|
setError(errMessage || "Failed to clone view");
|
|
232
237
|
return;
|
|
233
238
|
}
|
|
@@ -235,6 +240,7 @@ function PerspectiveSidebar({
|
|
|
235
240
|
counter += 1;
|
|
236
241
|
}
|
|
237
242
|
}
|
|
243
|
+
if (surfaceRecordConflict(lastErr, t)) return;
|
|
238
244
|
setError(lastErr instanceof Error ? lastErr.message : "Failed to clone view");
|
|
239
245
|
};
|
|
240
246
|
React.useEffect(() => {
|
|
@@ -275,6 +281,7 @@ function PerspectiveSidebar({
|
|
|
275
281
|
setSharedIds((prev) => /* @__PURE__ */ new Set([...prev, mode.perspectiveId]));
|
|
276
282
|
resetMode();
|
|
277
283
|
} catch (err) {
|
|
284
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
278
285
|
setError(err instanceof Error ? err.message : "Failed to share view");
|
|
279
286
|
}
|
|
280
287
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/backend/PerspectiveSidebar.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ArrowLeft, Plus } from 'lucide-react'\nimport { Alert } from '../primitives/alert'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { Spinner } from '../primitives/spinner'\nimport { useConfirmDialog } from './confirm-dialog'\nimport { flash } from './FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n PerspectiveDto,\n PerspectiveSettings,\n RolePerspectiveDto,\n} from '@open-mercato/shared/modules/perspectives/types'\nimport { ViewChip } from './views/ViewChip'\nimport { NewViewForm } from './views/NewViewForm'\nimport { ShareForm } from './views/ShareForm'\nimport { type SidebarMode } from './views/types'\nimport { ColumnChooserSection, type ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type { ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type PerspectiveSidebarProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n loading: boolean\n perspectives: PerspectiveDto[]\n rolePerspectives: RolePerspectiveDto[]\n roles: Array<{ id: string; name: string; hasPerspective: boolean; hasDefault: boolean }>\n activePerspectiveId: string | null\n onActivatePerspective: (perspective: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void\n onDeletePerspective: (perspectiveId: string) => Promise<void>\n onClearRole: (roleId: string) => Promise<void>\n onSave: (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => Promise<void>\n canApplyToRoles: boolean\n availableColumns: ColumnChooserField[]\n visibleColumnKeys: string[]\n columnOrder: string[]\n onToggleColumn: (key: string) => void\n onReorderColumns: (orderedIds: string[]) => void\n saving: boolean\n deletingIds: string[]\n roleClearingIds: string[]\n apiWarning?: string | null\n}\n\nconst emptyArray: any[] = []\n\nexport function PerspectiveSidebar({\n open,\n onOpenChange,\n loading,\n perspectives,\n rolePerspectives,\n roles,\n activePerspectiveId,\n onActivatePerspective,\n onDeletePerspective,\n onClearRole,\n onSave,\n canApplyToRoles,\n availableColumns,\n visibleColumnKeys,\n columnOrder,\n onToggleColumn,\n onReorderColumns,\n saving,\n deletingIds,\n roleClearingIds,\n apiWarning,\n}: PerspectiveSidebarProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!open) return\n if (typeof document === 'undefined') return\n document.body.dataset.columnChooserOpen = 'true'\n return () => {\n delete document.body.dataset.columnChooserOpen\n }\n }, [open])\n\n function perspectiveLabel(p: PerspectiveDto | RolePerspectiveDto) {\n return p.name.trim().length ? p.name : t('ui.perspectives.untitled', 'Untitled view')\n }\n\n const [name, setName] = React.useState('')\n const [isDefault, setIsDefault] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mode, setMode] = React.useState<SidebarMode>({ type: 'idle' })\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [renameValue, setRenameValue] = React.useState('')\n const [shareRoles, setShareRoles] = React.useState<string[]>([])\n const [shareSetDefault, setShareSetDefault] = React.useState(false)\n const [sharedIds, setSharedIds] = React.useState<Set<string>>(new Set())\n const [pendingCloneBaselineIds, setPendingCloneBaselineIds] = React.useState<Set<string> | null>(null)\n\n const autosaveRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const isDefaultUserChangeRef = React.useRef(false)\n const isDefaultRef = React.useRef(isDefault)\n isDefaultRef.current = isDefault\n\n const onSaveRef = React.useRef(onSave)\n onSaveRef.current = onSave\n\n const perspectivesRef = React.useRef(perspectives)\n perspectivesRef.current = perspectives\n\n const flushAutosave = React.useCallback(() => {\n if (autosaveRef.current) {\n clearTimeout(autosaveRef.current)\n autosaveRef.current = null\n }\n }, [])\n\n const scheduleAutosave = React.useCallback(() => {\n if (!activePerspectiveId || mode.type === 'new') return\n const activePersonal = perspectivesRef.current.find((p) => p.id === activePerspectiveId)\n if (!activePersonal) return\n flushAutosave()\n const targetId = activePersonal.id\n const targetName = activePersonal.name\n autosaveRef.current = setTimeout(async () => {\n autosaveRef.current = null\n try {\n await onSaveRef.current({\n name: targetName,\n isDefault: isDefaultRef.current,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: targetId,\n })\n flash(t('ui.perspectives.autosave.success', 'View saved'), 'success')\n } catch {\n flash(t('ui.perspectives.autosave.error', 'Failed to save view'), 'error')\n }\n }, 400)\n }, [activePerspectiveId, mode.type, flushAutosave, t])\n\n React.useEffect(() => {\n return () => { flushAutosave() }\n }, [flushAutosave])\n\n const resetMode = () => {\n setMode({ type: 'idle' })\n setName('')\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startNewMode = () => {\n setMode({ type: 'new' })\n setName('')\n setIsDefault(false)\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startShareMode = (p: PerspectiveDto) => {\n setMode({ type: 'share', perspectiveId: p.id, perspectiveName: p.name, perspectiveIsDefault: p.isDefault })\n setName('')\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n React.useEffect(() => {\n if (!open) {\n flushAutosave()\n setError(null)\n resetMode()\n setRenamingId(null)\n setSharedIds(new Set())\n setPendingCloneBaselineIds(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const active = perspectives.find((p) => p.id === activePerspectiveId)\n ?? rolePerspectives.find((p) => p.id === activePerspectiveId)\n isDefaultUserChangeRef.current = false\n if (active) {\n setIsDefault(active.isDefault)\n } else {\n setIsDefault(false)\n }\n requestAnimationFrame(() => { isDefaultUserChangeRef.current = true })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, activePerspectiveId])\n\n const handleSaveNew = async () => {\n setError(null)\n try {\n await onSave({ name: name.trim(), isDefault, applyToRoles: [], setRoleDefault: false, perspectiveId: null })\n resetMode()\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to save view')\n }\n }\n\n const handleToggleColumnWithAutosave = React.useCallback((key: string) => {\n onToggleColumn(key)\n scheduleAutosave()\n }, [onToggleColumn, scheduleAutosave])\n\n const handleReorderColumnsWithAutosave = React.useCallback((orderedIds: string[]) => {\n onReorderColumns(orderedIds)\n scheduleAutosave()\n }, [onReorderColumns, scheduleAutosave])\n\n const handleRename = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const trimmed = renameValue.trim()\n if (!trimmed) {\n setRenamingId(null)\n return\n }\n const conflict = perspectivesRef.current.some(\n (item) => item.id !== p.id && item.name.trim() === trimmed,\n )\n if (conflict) {\n flash(t('ui.perspectives.error.nameExists', 'View with this name already exists'), 'error')\n return\n }\n setError(null)\n try {\n await onSave({\n name: trimmed,\n isDefault: p.isDefault,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: p.id,\n })\n setRenamingId(null)\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to rename view')\n }\n }\n\n const handleClone = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const originalName = perspectiveLabel(p)\n setError(null)\n const baseline = new Set(perspectivesRef.current.map((item) => item.id))\n const originalSettingsKey = JSON.stringify(p.settings ?? {})\n const sharedRoleIds = Array.from(\n new Set(\n rolePerspectives\n .filter((rp) => JSON.stringify(rp.settings ?? {}) === originalSettingsKey)\n .map((rp) => rp.roleId),\n ),\n )\n const buildCloneName = (n: number) =>\n n <= 1 ? `${originalName} (copy)` : `${originalName} (copy ${n})`\n const existingNames = new Set(\n perspectivesRef.current.map((item) => item.name.trim()),\n )\n let counter = 1\n while (existingNames.has(buildCloneName(counter))) {\n counter += 1\n }\n const clonedSettings = structuredClone(p.settings)\n const MAX_ATTEMPTS = 50\n let lastErr: unknown = null\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n try {\n await onSave({\n name: buildCloneName(counter),\n isDefault: false,\n applyToRoles: sharedRoleIds,\n setRoleDefault: false,\n perspectiveId: null,\n settings: clonedSettings,\n })\n setPendingCloneBaselineIds(baseline)\n return\n } catch (err: unknown) {\n const errMessage = err instanceof Error ? err.message : ''\n const msg = errMessage.toLowerCase()\n const isDuplicate = msg.includes('duplicate key') || msg.includes('unique constraint')\n if (!isDuplicate) {\n setError(errMessage || 'Failed to clone view')\n return\n }\n lastErr = err\n counter += 1\n }\n }\n setError(lastErr instanceof Error ? lastErr.message : 'Failed to clone view')\n }\n\n React.useEffect(() => {\n if (!pendingCloneBaselineIds) return\n const created = perspectives.find((item) => !pendingCloneBaselineIds.has(item.id))\n if (!created) return\n setRenamingId(created.id)\n setRenameValue(created.name)\n setPendingCloneBaselineIds(null)\n }, [perspectives, pendingCloneBaselineIds])\n\n const handleDelete = async (p: PerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.delete.text', 'This view will be removed for you and all shared roles. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onDeletePerspective(p.id)\n }\n }\n\n const handleDeleteRole = async (p: RolePerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.deleteRole.text', 'This shared view will be removed for the role. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onClearRole(p.roleId)\n }\n }\n\n const handleShareApply = async () => {\n if (mode.type !== 'share') return\n setError(null)\n try {\n await onSave({ name: mode.perspectiveName, isDefault: mode.perspectiveIsDefault, applyToRoles: shareRoles, setRoleDefault: shareSetDefault, perspectiveId: mode.perspectiveId })\n setSharedIds((prev) => new Set([...prev, mode.perspectiveId]))\n resetMode()\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to share view')\n }\n }\n\n const toggleShareRole = (roleId: string) => {\n setShareRoles((prev) => {\n const next = new Set(prev)\n if (next.has(roleId)) next.delete(roleId)\n else next.add(roleId)\n return Array.from(next)\n })\n }\n\n if (!open) return null\n\n const isNew = mode.type === 'new'\n const isShare = mode.type === 'share'\n\n return (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"fixed right-0 top-0 h-full w-full sm:w-80 bg-background shadow-xl border-l flex flex-col\">\n <div className=\"flex items-center p-4 border-b\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n className=\"h-auto -ml-2 gap-2 px-2 text-lg font-semibold hover:bg-transparent\"\n aria-label={t('ui.perspectives.close', 'Close')}\n >\n <ArrowLeft className=\"size-5\" />\n {t('ui.perspectives.title', 'Views')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n {/* Saved views as chips */}\n <section className=\"p-4 space-y-3\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{t('ui.perspectives.savedViews.title', 'Saved views')}</h3>\n {loading ? <Spinner size=\"sm\" /> : null}\n <div className=\"flex flex-wrap gap-1.5\">\n {/* + New chip */}\n <Button\n type=\"button\"\n variant={isNew ? 'secondary' : 'outline'}\n size=\"sm\"\n className={`h-8 px-3 py-1.5 text-sm ${!isNew ? 'border-dashed' : ''}`}\n onClick={startNewMode}\n >\n <Plus className=\"size-3 mr-1\" />\n {t('ui.perspectives.savedViews.new', 'New')}\n </Button>\n {(perspectives ?? emptyArray).map((p) => {\n const isActive = activePerspectiveId === p.id\n const deleting = deletingIds.includes(p.id)\n const isShared = sharedIds.has(p.id)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"personal\"\n isActive={isActive}\n disabled={deleting}\n isShared={isShared}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={deleting}\n onActivate={() => { onActivatePerspective(p, 'personal'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onRenameStart={() => { setRenamingId(p.id); setRenameValue(p.name) }}\n onClone={() => void handleClone(p)}\n onShareStart={() => startShareMode(p)}\n onDelete={() => void handleDelete(p)}\n />\n )\n })}\n {rolePerspectives\n .filter((rp) => {\n const rpName = rp.name.trim()\n return !perspectives.some((pp) => pp.name.trim() === rpName)\n })\n .map((p) => {\n const isActive = activePerspectiveId === p.id\n const clearing = roleClearingIds.includes(p.roleId)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"role\"\n isActive={isActive}\n disabled={clearing}\n isShared={false}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={clearing}\n onActivate={() => { onActivatePerspective(p, 'role'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onClone={() => void handleClone(p)}\n onDelete={() => void handleDeleteRole(p)}\n />\n )\n })}\n </div>\n\n {/* Inline form slot \u2014 shared between New and Share modes */}\n {isNew ? (\n <NewViewForm\n name={name}\n onNameChange={setName}\n onSubmit={() => void handleSaveNew()}\n onCancel={resetMode}\n saving={saving}\n />\n ) : null}\n\n {isShare ? (\n <ShareForm\n roles={roles}\n shareRoles={shareRoles}\n shareSetDefault={shareSetDefault}\n onToggleRole={toggleShareRole}\n onToggleSetDefault={setShareSetDefault}\n onApply={() => void handleShareApply()}\n onCancel={resetMode}\n />\n ) : null}\n\n {apiWarning ? (\n <Alert variant=\"warning\" className=\"text-xs\">{apiWarning}</Alert>\n ) : null}\n {error ? <div className=\"text-sm text-status-error-text\">{error}</div> : null}\n </section>\n\n {/* Set as default \u2014 separated from chips (border-t) and from columns (ColumnList owns border-t) */}\n <div className=\"flex items-center px-4 mt-2 pt-2 pb-2\">\n <label className=\"inline-flex items-center gap-2 text-sm leading-none\">\n <Checkbox\n checked={isDefault}\n onCheckedChange={(checked) => {\n setIsDefault(checked === true)\n if (isDefaultUserChangeRef.current) {\n scheduleAutosave()\n }\n }}\n />\n {t('ui.perspectives.form.makeDefault', 'Set as default')}\n </label>\n </div>\n\n <ColumnChooserSection\n availableColumns={availableColumns}\n visibleColumnKeys={visibleColumnKeys}\n columnOrder={columnOrder}\n onToggleColumn={handleToggleColumnWithAutosave}\n onReorderColumns={handleReorderColumnsWithAutosave}\n dndContextId=\"perspective-columns\"\n />\n </div>\n </div>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ArrowLeft, Plus } from 'lucide-react'\nimport { Alert } from '../primitives/alert'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { Spinner } from '../primitives/spinner'\nimport { useConfirmDialog } from './confirm-dialog'\nimport { flash } from './FlashMessages'\nimport { surfaceRecordConflict } from './conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n PerspectiveDto,\n PerspectiveSettings,\n RolePerspectiveDto,\n} from '@open-mercato/shared/modules/perspectives/types'\nimport { ViewChip } from './views/ViewChip'\nimport { NewViewForm } from './views/NewViewForm'\nimport { ShareForm } from './views/ShareForm'\nimport { type SidebarMode } from './views/types'\nimport { ColumnChooserSection, type ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type { ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type PerspectiveSidebarProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n loading: boolean\n perspectives: PerspectiveDto[]\n rolePerspectives: RolePerspectiveDto[]\n roles: Array<{ id: string; name: string; hasPerspective: boolean; hasDefault: boolean }>\n activePerspectiveId: string | null\n onActivatePerspective: (perspective: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void\n onDeletePerspective: (perspectiveId: string) => Promise<void>\n onClearRole: (roleId: string) => Promise<void>\n onSave: (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => Promise<void>\n canApplyToRoles: boolean\n availableColumns: ColumnChooserField[]\n visibleColumnKeys: string[]\n columnOrder: string[]\n onToggleColumn: (key: string) => void\n onReorderColumns: (orderedIds: string[]) => void\n saving: boolean\n deletingIds: string[]\n roleClearingIds: string[]\n apiWarning?: string | null\n}\n\nconst emptyArray: any[] = []\n\nexport function PerspectiveSidebar({\n open,\n onOpenChange,\n loading,\n perspectives,\n rolePerspectives,\n roles,\n activePerspectiveId,\n onActivatePerspective,\n onDeletePerspective,\n onClearRole,\n onSave,\n canApplyToRoles,\n availableColumns,\n visibleColumnKeys,\n columnOrder,\n onToggleColumn,\n onReorderColumns,\n saving,\n deletingIds,\n roleClearingIds,\n apiWarning,\n}: PerspectiveSidebarProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!open) return\n if (typeof document === 'undefined') return\n document.body.dataset.columnChooserOpen = 'true'\n return () => {\n delete document.body.dataset.columnChooserOpen\n }\n }, [open])\n\n function perspectiveLabel(p: PerspectiveDto | RolePerspectiveDto) {\n return p.name.trim().length ? p.name : t('ui.perspectives.untitled', 'Untitled view')\n }\n\n const [name, setName] = React.useState('')\n const [isDefault, setIsDefault] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mode, setMode] = React.useState<SidebarMode>({ type: 'idle' })\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [renameValue, setRenameValue] = React.useState('')\n const [shareRoles, setShareRoles] = React.useState<string[]>([])\n const [shareSetDefault, setShareSetDefault] = React.useState(false)\n const [sharedIds, setSharedIds] = React.useState<Set<string>>(new Set())\n const [pendingCloneBaselineIds, setPendingCloneBaselineIds] = React.useState<Set<string> | null>(null)\n\n const autosaveRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const isDefaultUserChangeRef = React.useRef(false)\n const isDefaultRef = React.useRef(isDefault)\n isDefaultRef.current = isDefault\n\n const onSaveRef = React.useRef(onSave)\n onSaveRef.current = onSave\n\n const perspectivesRef = React.useRef(perspectives)\n perspectivesRef.current = perspectives\n\n const flushAutosave = React.useCallback(() => {\n if (autosaveRef.current) {\n clearTimeout(autosaveRef.current)\n autosaveRef.current = null\n }\n }, [])\n\n const scheduleAutosave = React.useCallback(() => {\n if (!activePerspectiveId || mode.type === 'new') return\n const activePersonal = perspectivesRef.current.find((p) => p.id === activePerspectiveId)\n if (!activePersonal) return\n flushAutosave()\n const targetId = activePersonal.id\n const targetName = activePersonal.name\n autosaveRef.current = setTimeout(async () => {\n autosaveRef.current = null\n try {\n await onSaveRef.current({\n name: targetName,\n isDefault: isDefaultRef.current,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: targetId,\n })\n flash(t('ui.perspectives.autosave.success', 'View saved'), 'success')\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n flash(t('ui.perspectives.autosave.error', 'Failed to save view'), 'error')\n }\n }, 400)\n }, [activePerspectiveId, mode.type, flushAutosave, t])\n\n React.useEffect(() => {\n return () => { flushAutosave() }\n }, [flushAutosave])\n\n const resetMode = () => {\n setMode({ type: 'idle' })\n setName('')\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startNewMode = () => {\n setMode({ type: 'new' })\n setName('')\n setIsDefault(false)\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startShareMode = (p: PerspectiveDto) => {\n setMode({ type: 'share', perspectiveId: p.id, perspectiveName: p.name, perspectiveIsDefault: p.isDefault })\n setName('')\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n React.useEffect(() => {\n if (!open) {\n flushAutosave()\n setError(null)\n resetMode()\n setRenamingId(null)\n setSharedIds(new Set())\n setPendingCloneBaselineIds(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const active = perspectives.find((p) => p.id === activePerspectiveId)\n ?? rolePerspectives.find((p) => p.id === activePerspectiveId)\n isDefaultUserChangeRef.current = false\n if (active) {\n setIsDefault(active.isDefault)\n } else {\n setIsDefault(false)\n }\n requestAnimationFrame(() => { isDefaultUserChangeRef.current = true })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, activePerspectiveId])\n\n const handleSaveNew = async () => {\n setError(null)\n try {\n await onSave({ name: name.trim(), isDefault, applyToRoles: [], setRoleDefault: false, perspectiveId: null })\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to save view')\n }\n }\n\n const handleToggleColumnWithAutosave = React.useCallback((key: string) => {\n onToggleColumn(key)\n scheduleAutosave()\n }, [onToggleColumn, scheduleAutosave])\n\n const handleReorderColumnsWithAutosave = React.useCallback((orderedIds: string[]) => {\n onReorderColumns(orderedIds)\n scheduleAutosave()\n }, [onReorderColumns, scheduleAutosave])\n\n const handleRename = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const trimmed = renameValue.trim()\n if (!trimmed) {\n setRenamingId(null)\n return\n }\n const conflict = perspectivesRef.current.some(\n (item) => item.id !== p.id && item.name.trim() === trimmed,\n )\n if (conflict) {\n flash(t('ui.perspectives.error.nameExists', 'View with this name already exists'), 'error')\n return\n }\n setError(null)\n try {\n await onSave({\n name: trimmed,\n isDefault: p.isDefault,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: p.id,\n })\n setRenamingId(null)\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to rename view')\n }\n }\n\n const handleClone = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const originalName = perspectiveLabel(p)\n setError(null)\n const baseline = new Set(perspectivesRef.current.map((item) => item.id))\n const originalSettingsKey = JSON.stringify(p.settings ?? {})\n const sharedRoleIds = Array.from(\n new Set(\n rolePerspectives\n .filter((rp) => JSON.stringify(rp.settings ?? {}) === originalSettingsKey)\n .map((rp) => rp.roleId),\n ),\n )\n const buildCloneName = (n: number) =>\n n <= 1 ? `${originalName} (copy)` : `${originalName} (copy ${n})`\n const existingNames = new Set(\n perspectivesRef.current.map((item) => item.name.trim()),\n )\n let counter = 1\n while (existingNames.has(buildCloneName(counter))) {\n counter += 1\n }\n const clonedSettings = structuredClone(p.settings)\n const MAX_ATTEMPTS = 50\n let lastErr: unknown = null\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n try {\n await onSave({\n name: buildCloneName(counter),\n isDefault: false,\n applyToRoles: sharedRoleIds,\n setRoleDefault: false,\n perspectiveId: null,\n settings: clonedSettings,\n })\n setPendingCloneBaselineIds(baseline)\n return\n } catch (err: unknown) {\n const errMessage = err instanceof Error ? err.message : ''\n const msg = errMessage.toLowerCase()\n const isDuplicate = msg.includes('duplicate key') || msg.includes('unique constraint')\n if (!isDuplicate) {\n if (surfaceRecordConflict(err, t)) return\n setError(errMessage || 'Failed to clone view')\n return\n }\n lastErr = err\n counter += 1\n }\n }\n if (surfaceRecordConflict(lastErr, t)) return\n setError(lastErr instanceof Error ? lastErr.message : 'Failed to clone view')\n }\n\n React.useEffect(() => {\n if (!pendingCloneBaselineIds) return\n const created = perspectives.find((item) => !pendingCloneBaselineIds.has(item.id))\n if (!created) return\n setRenamingId(created.id)\n setRenameValue(created.name)\n setPendingCloneBaselineIds(null)\n }, [perspectives, pendingCloneBaselineIds])\n\n const handleDelete = async (p: PerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.delete.text', 'This view will be removed for you and all shared roles. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onDeletePerspective(p.id)\n }\n }\n\n const handleDeleteRole = async (p: RolePerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.deleteRole.text', 'This shared view will be removed for the role. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onClearRole(p.roleId)\n }\n }\n\n const handleShareApply = async () => {\n if (mode.type !== 'share') return\n setError(null)\n try {\n await onSave({ name: mode.perspectiveName, isDefault: mode.perspectiveIsDefault, applyToRoles: shareRoles, setRoleDefault: shareSetDefault, perspectiveId: mode.perspectiveId })\n setSharedIds((prev) => new Set([...prev, mode.perspectiveId]))\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to share view')\n }\n }\n\n const toggleShareRole = (roleId: string) => {\n setShareRoles((prev) => {\n const next = new Set(prev)\n if (next.has(roleId)) next.delete(roleId)\n else next.add(roleId)\n return Array.from(next)\n })\n }\n\n if (!open) return null\n\n const isNew = mode.type === 'new'\n const isShare = mode.type === 'share'\n\n return (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"fixed right-0 top-0 h-full w-full sm:w-80 bg-background shadow-xl border-l flex flex-col\">\n <div className=\"flex items-center p-4 border-b\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n className=\"h-auto -ml-2 gap-2 px-2 text-lg font-semibold hover:bg-transparent\"\n aria-label={t('ui.perspectives.close', 'Close')}\n >\n <ArrowLeft className=\"size-5\" />\n {t('ui.perspectives.title', 'Views')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n {/* Saved views as chips */}\n <section className=\"p-4 space-y-3\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{t('ui.perspectives.savedViews.title', 'Saved views')}</h3>\n {loading ? <Spinner size=\"sm\" /> : null}\n <div className=\"flex flex-wrap gap-1.5\">\n {/* + New chip */}\n <Button\n type=\"button\"\n variant={isNew ? 'secondary' : 'outline'}\n size=\"sm\"\n className={`h-8 px-3 py-1.5 text-sm ${!isNew ? 'border-dashed' : ''}`}\n onClick={startNewMode}\n >\n <Plus className=\"size-3 mr-1\" />\n {t('ui.perspectives.savedViews.new', 'New')}\n </Button>\n {(perspectives ?? emptyArray).map((p) => {\n const isActive = activePerspectiveId === p.id\n const deleting = deletingIds.includes(p.id)\n const isShared = sharedIds.has(p.id)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"personal\"\n isActive={isActive}\n disabled={deleting}\n isShared={isShared}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={deleting}\n onActivate={() => { onActivatePerspective(p, 'personal'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onRenameStart={() => { setRenamingId(p.id); setRenameValue(p.name) }}\n onClone={() => void handleClone(p)}\n onShareStart={() => startShareMode(p)}\n onDelete={() => void handleDelete(p)}\n />\n )\n })}\n {rolePerspectives\n .filter((rp) => {\n const rpName = rp.name.trim()\n return !perspectives.some((pp) => pp.name.trim() === rpName)\n })\n .map((p) => {\n const isActive = activePerspectiveId === p.id\n const clearing = roleClearingIds.includes(p.roleId)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"role\"\n isActive={isActive}\n disabled={clearing}\n isShared={false}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={clearing}\n onActivate={() => { onActivatePerspective(p, 'role'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onClone={() => void handleClone(p)}\n onDelete={() => void handleDeleteRole(p)}\n />\n )\n })}\n </div>\n\n {/* Inline form slot \u2014 shared between New and Share modes */}\n {isNew ? (\n <NewViewForm\n name={name}\n onNameChange={setName}\n onSubmit={() => void handleSaveNew()}\n onCancel={resetMode}\n saving={saving}\n />\n ) : null}\n\n {isShare ? (\n <ShareForm\n roles={roles}\n shareRoles={shareRoles}\n shareSetDefault={shareSetDefault}\n onToggleRole={toggleShareRole}\n onToggleSetDefault={setShareSetDefault}\n onApply={() => void handleShareApply()}\n onCancel={resetMode}\n />\n ) : null}\n\n {apiWarning ? (\n <Alert variant=\"warning\" className=\"text-xs\">{apiWarning}</Alert>\n ) : null}\n {error ? <div className=\"text-sm text-status-error-text\">{error}</div> : null}\n </section>\n\n {/* Set as default \u2014 separated from chips (border-t) and from columns (ColumnList owns border-t) */}\n <div className=\"flex items-center px-4 mt-2 pt-2 pb-2\">\n <label className=\"inline-flex items-center gap-2 text-sm leading-none\">\n <Checkbox\n checked={isDefault}\n onCheckedChange={(checked) => {\n setIsDefault(checked === true)\n if (isDefaultUserChangeRef.current) {\n scheduleAutosave()\n }\n }}\n />\n {t('ui.perspectives.form.makeDefault', 'Set as default')}\n </label>\n </div>\n\n <ColumnChooserSection\n availableColumns={availableColumns}\n visibleColumnKeys={visibleColumnKeys}\n columnOrder={columnOrder}\n onToggleColumn={handleToggleColumnWithAutosave}\n onReorderColumns={handleReorderColumnsWithAutosave}\n dndContextId=\"perspective-columns\"\n />\n </div>\n </div>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|