@open-mercato/enterprise 0.4.6-develop-02aac88968 → 0.4.6-develop-05b3bcb6f5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/record_locks/notifications.handlers.js +72 -0
- package/dist/modules/record_locks/notifications.handlers.js.map +7 -0
- package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js +24 -36
- package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js.map +2 -2
- package/package.json +4 -4
- package/src/modules/record_locks/notifications.handlers.ts +67 -0
- package/src/modules/record_locks/widgets/injection/record-locking/widget.client.tsx +38 -44
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const RECORD_LOCKS_LOCK_CONTENDED_EVENT = "om:record_locks:lock-contended";
|
|
2
|
+
const RECORD_LOCKS_RECORD_DELETED_EVENT = "om:record_locks:record-deleted";
|
|
3
|
+
const RECORD_LOCKS_INCOMING_CHANGES_EVENT = "om:record_locks:incoming-changes";
|
|
4
|
+
const RECORD_LOCKS_FORCE_RELEASED_EVENT = "om:record_locks:force-released";
|
|
5
|
+
const notificationHandlers = [
|
|
6
|
+
{
|
|
7
|
+
id: "record_locks.lock-contended-event",
|
|
8
|
+
notificationType: "record_locks.lock.contended",
|
|
9
|
+
features: ["record_locks.view"],
|
|
10
|
+
priority: 100,
|
|
11
|
+
handle(notification, context) {
|
|
12
|
+
context.emitEvent(RECORD_LOCKS_LOCK_CONTENDED_EVENT, {
|
|
13
|
+
notificationId: notification.id,
|
|
14
|
+
sourceEntityId: notification.sourceEntityId ?? null,
|
|
15
|
+
resourceKind: notification.bodyVariables?.resourceKind ?? null
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "record_locks.record-deleted-event",
|
|
21
|
+
notificationType: "record_locks.record.deleted",
|
|
22
|
+
features: ["record_locks.view"],
|
|
23
|
+
priority: 100,
|
|
24
|
+
handle(notification, context) {
|
|
25
|
+
context.emitEvent(RECORD_LOCKS_RECORD_DELETED_EVENT, {
|
|
26
|
+
notificationId: notification.id,
|
|
27
|
+
resourceId: notification.sourceEntityId ?? null,
|
|
28
|
+
resourceKind: notification.bodyVariables?.resourceKind ?? null
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "record_locks.incoming-changes-event",
|
|
34
|
+
notificationType: "record_locks.incoming_changes.available",
|
|
35
|
+
features: ["record_locks.view"],
|
|
36
|
+
priority: 90,
|
|
37
|
+
handle(notification, context) {
|
|
38
|
+
context.emitEvent(RECORD_LOCKS_INCOMING_CHANGES_EVENT, {
|
|
39
|
+
notificationId: notification.id,
|
|
40
|
+
sourceEntityId: notification.sourceEntityId ?? null,
|
|
41
|
+
resourceKind: notification.bodyVariables?.resourceKind ?? null
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "record_locks.force-released-toast",
|
|
47
|
+
notificationType: "record_locks.lock.force_released",
|
|
48
|
+
features: ["record_locks.view"],
|
|
49
|
+
priority: 110,
|
|
50
|
+
handle(notification, context) {
|
|
51
|
+
context.toast({
|
|
52
|
+
title: notification.title,
|
|
53
|
+
body: notification.body ?? void 0,
|
|
54
|
+
severity: "warning"
|
|
55
|
+
});
|
|
56
|
+
context.emitEvent(RECORD_LOCKS_FORCE_RELEASED_EVENT, {
|
|
57
|
+
notificationId: notification.id,
|
|
58
|
+
sourceEntityId: notification.sourceEntityId ?? null
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
var notifications_handlers_default = notificationHandlers;
|
|
64
|
+
export {
|
|
65
|
+
RECORD_LOCKS_FORCE_RELEASED_EVENT,
|
|
66
|
+
RECORD_LOCKS_INCOMING_CHANGES_EVENT,
|
|
67
|
+
RECORD_LOCKS_LOCK_CONTENDED_EVENT,
|
|
68
|
+
RECORD_LOCKS_RECORD_DELETED_EVENT,
|
|
69
|
+
notifications_handlers_default as default,
|
|
70
|
+
notificationHandlers
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=notifications.handlers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/record_locks/notifications.handlers.ts"],
|
|
4
|
+
"sourcesContent": ["import type { NotificationHandler } from '@open-mercato/shared/modules/notifications/handler'\n\nexport const RECORD_LOCKS_LOCK_CONTENDED_EVENT = 'om:record_locks:lock-contended'\nexport const RECORD_LOCKS_RECORD_DELETED_EVENT = 'om:record_locks:record-deleted'\nexport const RECORD_LOCKS_INCOMING_CHANGES_EVENT = 'om:record_locks:incoming-changes'\nexport const RECORD_LOCKS_FORCE_RELEASED_EVENT = 'om:record_locks:force-released'\n\nexport const notificationHandlers: NotificationHandler[] = [\n {\n id: 'record_locks.lock-contended-event',\n notificationType: 'record_locks.lock.contended',\n features: ['record_locks.view'],\n priority: 100,\n handle(notification, context) {\n context.emitEvent(RECORD_LOCKS_LOCK_CONTENDED_EVENT, {\n notificationId: notification.id,\n sourceEntityId: notification.sourceEntityId ?? null,\n resourceKind: notification.bodyVariables?.resourceKind ?? null,\n })\n },\n },\n {\n id: 'record_locks.record-deleted-event',\n notificationType: 'record_locks.record.deleted',\n features: ['record_locks.view'],\n priority: 100,\n handle(notification, context) {\n context.emitEvent(RECORD_LOCKS_RECORD_DELETED_EVENT, {\n notificationId: notification.id,\n resourceId: notification.sourceEntityId ?? null,\n resourceKind: notification.bodyVariables?.resourceKind ?? null,\n })\n },\n },\n {\n id: 'record_locks.incoming-changes-event',\n notificationType: 'record_locks.incoming_changes.available',\n features: ['record_locks.view'],\n priority: 90,\n handle(notification, context) {\n context.emitEvent(RECORD_LOCKS_INCOMING_CHANGES_EVENT, {\n notificationId: notification.id,\n sourceEntityId: notification.sourceEntityId ?? null,\n resourceKind: notification.bodyVariables?.resourceKind ?? null,\n })\n },\n },\n {\n id: 'record_locks.force-released-toast',\n notificationType: 'record_locks.lock.force_released',\n features: ['record_locks.view'],\n priority: 110,\n handle(notification, context) {\n context.toast({\n title: notification.title,\n body: notification.body ?? undefined,\n severity: 'warning',\n })\n context.emitEvent(RECORD_LOCKS_FORCE_RELEASED_EVENT, {\n notificationId: notification.id,\n sourceEntityId: notification.sourceEntityId ?? null,\n })\n },\n },\n]\n\nexport default notificationHandlers\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,oCAAoC;AAC1C,MAAM,oCAAoC;AAC1C,MAAM,sCAAsC;AAC5C,MAAM,oCAAoC;AAE1C,MAAM,uBAA8C;AAAA,EACzD;AAAA,IACE,IAAI;AAAA,IACJ,kBAAkB;AAAA,IAClB,UAAU,CAAC,mBAAmB;AAAA,IAC9B,UAAU;AAAA,IACV,OAAO,cAAc,SAAS;AAC5B,cAAQ,UAAU,mCAAmC;AAAA,QACnD,gBAAgB,aAAa;AAAA,QAC7B,gBAAgB,aAAa,kBAAkB;AAAA,QAC/C,cAAc,aAAa,eAAe,gBAAgB;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,kBAAkB;AAAA,IAClB,UAAU,CAAC,mBAAmB;AAAA,IAC9B,UAAU;AAAA,IACV,OAAO,cAAc,SAAS;AAC5B,cAAQ,UAAU,mCAAmC;AAAA,QACnD,gBAAgB,aAAa;AAAA,QAC7B,YAAY,aAAa,kBAAkB;AAAA,QAC3C,cAAc,aAAa,eAAe,gBAAgB;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,kBAAkB;AAAA,IAClB,UAAU,CAAC,mBAAmB;AAAA,IAC9B,UAAU;AAAA,IACV,OAAO,cAAc,SAAS;AAC5B,cAAQ,UAAU,qCAAqC;AAAA,QACrD,gBAAgB,aAAa;AAAA,QAC7B,gBAAgB,aAAa,kBAAkB;AAAA,QAC/C,cAAc,aAAa,eAAe,gBAAgB;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,kBAAkB;AAAA,IAClB,UAAU,CAAC,mBAAmB;AAAA,IAC9B,UAAU;AAAA,IACV,OAAO,cAAc,SAAS;AAC5B,cAAQ,MAAM;AAAA,QACZ,OAAO,aAAa;AAAA,QACpB,MAAM,aAAa,QAAQ;AAAA,QAC3B,UAAU;AAAA,MACZ,CAAC;AACD,cAAQ,UAAU,mCAAmC;AAAA,QACnD,gBAAgB,aAAa;AAAA,QAC7B,gBAAgB,aAAa,kBAAkB;AAAA,MACjD,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAO,iCAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -11,6 +11,10 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
|
11
11
|
import { BACKEND_MUTATION_ERROR_EVENT } from "@open-mercato/ui/backend/injection/mutationEvents";
|
|
12
12
|
import { useSearchParams } from "next/navigation";
|
|
13
13
|
import { Mail } from "lucide-react";
|
|
14
|
+
import {
|
|
15
|
+
RECORD_LOCKS_LOCK_CONTENDED_EVENT,
|
|
16
|
+
RECORD_LOCKS_RECORD_DELETED_EVENT
|
|
17
|
+
} from "@open-mercato/enterprise/modules/record_locks/notifications.handlers";
|
|
14
18
|
import {
|
|
15
19
|
ChangedFieldsTable
|
|
16
20
|
} from "@open-mercato/core/modules/audit_logs/lib/display-helpers";
|
|
@@ -32,6 +36,11 @@ function getRecordLockOwnerMap() {
|
|
|
32
36
|
function isObjectRecord(value) {
|
|
33
37
|
return Boolean(value) && typeof value === "object";
|
|
34
38
|
}
|
|
39
|
+
function readStringOrNull(value) {
|
|
40
|
+
if (typeof value !== "string") return null;
|
|
41
|
+
const trimmed = value.trim();
|
|
42
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
43
|
+
}
|
|
35
44
|
function isUuid(value) {
|
|
36
45
|
if (typeof value !== "string") return false;
|
|
37
46
|
const trimmed = value.trim();
|
|
@@ -495,44 +504,27 @@ function RecordLockingWidget({
|
|
|
495
504
|
React.useEffect(() => {
|
|
496
505
|
if (!isPrimaryInstance) return;
|
|
497
506
|
if (!mine || !state?.lock?.id) return;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
);
|
|
503
|
-
if (cancelled) return;
|
|
504
|
-
const items = Array.isArray(call.result?.items) ? call.result.items : [];
|
|
505
|
-
const hasUnreadContention = items.some((item) => item.sourceEntityId === state.lock?.id);
|
|
506
|
-
if (hasUnreadContention) {
|
|
507
|
-
setShowLockContentionBanner(true);
|
|
508
|
-
}
|
|
507
|
+
const onContention = (event) => {
|
|
508
|
+
const detail = isObjectRecord(event.detail) ? event.detail : null;
|
|
509
|
+
if (!detail) return;
|
|
510
|
+
if (detail.sourceEntityId !== state.lock?.id) return;
|
|
511
|
+
setShowLockContentionBanner(true);
|
|
509
512
|
};
|
|
510
|
-
|
|
511
|
-
const interval = window.setInterval(() => {
|
|
512
|
-
void syncContentionBanner();
|
|
513
|
-
}, 5e3);
|
|
513
|
+
window.addEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention);
|
|
514
514
|
return () => {
|
|
515
|
-
|
|
516
|
-
window.clearInterval(interval);
|
|
515
|
+
window.removeEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention);
|
|
517
516
|
};
|
|
518
517
|
}, [isPrimaryInstance, mine, state?.lock?.id]);
|
|
519
518
|
React.useEffect(() => {
|
|
520
519
|
if (!isPrimaryInstance) return;
|
|
521
520
|
if (!state?.resourceKind || !state?.resourceId) return;
|
|
522
521
|
if (state.recordDeleted === true) return;
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
const matchesResourceId = item.sourceEntityId === state.resourceId;
|
|
530
|
-
if (!matchesResourceId) return false;
|
|
531
|
-
const kindFromBody = typeof item.bodyVariables?.resourceKind === "string" ? item.bodyVariables.resourceKind.trim() : "";
|
|
532
|
-
if (!kindFromBody) return true;
|
|
533
|
-
return kindFromBody === state.resourceKind;
|
|
534
|
-
});
|
|
535
|
-
if (!hasUnreadRecordDeleted) return;
|
|
522
|
+
const onRecordDeleted = (event) => {
|
|
523
|
+
const detail = isObjectRecord(event.detail) ? event.detail : null;
|
|
524
|
+
if (!detail) return;
|
|
525
|
+
if (detail.resourceId !== state.resourceId) return;
|
|
526
|
+
const kind = readStringOrNull(detail.resourceKind);
|
|
527
|
+
if (kind && kind !== state.resourceKind) return;
|
|
536
528
|
setIsConflictDialogOpen(true);
|
|
537
529
|
setRecordLockFormState(formId, {
|
|
538
530
|
recordDeleted: true,
|
|
@@ -544,13 +536,9 @@ function RecordLockingWidget({
|
|
|
544
536
|
pendingResolutionArmed: false
|
|
545
537
|
});
|
|
546
538
|
};
|
|
547
|
-
|
|
548
|
-
const interval = window.setInterval(() => {
|
|
549
|
-
void syncRecordDeletedState();
|
|
550
|
-
}, 5e3);
|
|
539
|
+
window.addEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted);
|
|
551
540
|
return () => {
|
|
552
|
-
|
|
553
|
-
window.clearInterval(interval);
|
|
541
|
+
window.removeEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted);
|
|
554
542
|
};
|
|
555
543
|
}, [formId, isPrimaryInstance, state?.recordDeleted, state?.resourceId, state?.resourceKind]);
|
|
556
544
|
React.useEffect(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/record_locks/widgets/injection/record-locking/widget.client.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Notice } from '@open-mercato/ui/primitives/Notice'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { BACKEND_MUTATION_ERROR_EVENT } from '@open-mercato/ui/backend/injection/mutationEvents'\nimport { useSearchParams } from 'next/navigation'\nimport { Mail } from 'lucide-react'\nimport {\n ChangedFieldsTable,\n type ChangeRow,\n} from '@open-mercato/core/modules/audit_logs/lib/display-helpers'\nimport {\n clearRecordLockFormState,\n getRecordLockFormState,\n setRecordLockFormState,\n subscribeRecordLockFormState,\n type RecordLockFormState,\n type RecordLockUiConflict,\n type RecordLockUiView,\n} from '@open-mercato/enterprise/modules/record_locks/lib/clientLockStore'\n\ntype CrudInjectionContext = {\n formId?: string\n entityId?: string\n resourceKind?: string\n resourceId?: string\n recordId?: string\n path?: string\n query?: string\n kind?: string\n personId?: string\n companyId?: string\n dealId?: string\n retryLastMutation?: () => Promise<boolean | void> | boolean | void\n}\n\ntype RecordLockWidgetOwner = {\n instanceId: string\n priority: number\n}\n\nconst GLOBAL_RECORD_LOCK_OWNERS_KEY = '__openMercatoRecordLockWidgetOwners__'\n\nfunction getRecordLockOwnerMap(): Map<string, RecordLockWidgetOwner> {\n const store = globalThis as Record<string, unknown>\n const existing = store[GLOBAL_RECORD_LOCK_OWNERS_KEY]\n if (existing instanceof Map) return existing as Map<string, RecordLockWidgetOwner>\n const next = new Map<string, RecordLockWidgetOwner>()\n store[GLOBAL_RECORD_LOCK_OWNERS_KEY] = next\n return next\n}\n\ntype AcquireResponse = {\n ok?: boolean\n acquired?: boolean\n allowForceUnlock?: boolean\n heartbeatSeconds?: number\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n currentUserId?: string\n error?: string\n code?: string\n}\n\ntype ValidateResponse = {\n ok: boolean\n status?: number\n code?: string\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n conflict?: RecordLockUiConflict | null\n}\n\ntype CrudSaveErrorEventDetail = {\n contextId?: string\n formId?: string\n error?: unknown\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object'\n}\n\nfunction isUuid(value: string | null | undefined): value is string {\n if (typeof value !== 'string') return false\n const trimmed = value.trim()\n if (!trimmed) return false\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)\n}\n\nfunction extractErrorStatus(error: unknown): number | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = current.status\n if (typeof status === 'number' && Number.isFinite(status)) return status\n if (typeof status === 'string') {\n const parsed = Number(status)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const statusCode = current.statusCode\n if (typeof statusCode === 'number' && Number.isFinite(statusCode)) return statusCode\n if (typeof statusCode === 'string') {\n const parsed = Number(statusCode)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return null\n}\n\nfunction extractRecordLockConflictPayload(error: unknown): {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n} | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const nested = ['body', 'response', 'data', 'details', 'error']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n\n const code = typeof current.code === 'string' ? current.code : null\n const status = extractErrorStatus(current)\n const hasLockMarkers = (\n isObjectRecord(current.lock)\n || isObjectRecord(current.conflict)\n || typeof current.conflictId === 'string'\n || typeof current.resourceKind === 'string'\n || typeof current.resourceId === 'string'\n || Array.isArray(current.resolutionOptions)\n || typeof current.allowIncomingOverride === 'boolean'\n || typeof current.canOverrideIncoming === 'boolean'\n )\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n const looksLikeLockConflictMessage = (\n message.includes('record conflict')\n || message.includes('record_lock_conflict')\n || message.includes('conflict detected')\n )\n const isRecordLockConflict = (\n code === 'record_lock_conflict'\n || (status === 409 && (hasLockMarkers || looksLikeLockConflictMessage))\n )\n if (!isRecordLockConflict) continue\n if (!isObjectRecord(current.conflict)) {\n const lock = isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined\n const fallbackConflictId = typeof current.conflictId === 'string' && isUuid(current.conflictId)\n ? current.conflictId\n : 'unresolved'\n const fallbackConflict: RecordLockUiConflict = {\n id: fallbackConflictId,\n resourceKind:\n (typeof current.resourceKind === 'string' && current.resourceKind.trim().length > 0\n ? current.resourceKind\n : lock?.resourceKind) ?? '',\n resourceId:\n (typeof current.resourceId === 'string' && current.resourceId.trim().length > 0\n ? current.resourceId\n : lock?.resourceId) ?? '',\n baseActionLogId:\n typeof current.baseActionLogId === 'string' || current.baseActionLogId === null\n ? current.baseActionLogId\n : lock?.baseActionLogId ?? null,\n incomingActionLogId:\n typeof current.incomingActionLogId === 'string' || current.incomingActionLogId === null\n ? current.incomingActionLogId\n : null,\n allowIncomingOverride: Boolean(current.allowIncomingOverride),\n canOverrideIncoming: Boolean(current.canOverrideIncoming),\n resolutionOptions: Array.isArray(current.resolutionOptions) && current.resolutionOptions.includes('accept_mine')\n ? ['accept_mine']\n : [],\n changes: [],\n }\n return {\n conflict: fallbackConflict,\n lock,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n return {\n conflict: current.conflict as RecordLockUiConflict,\n lock: isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n\n return null\n}\n\nfunction isRecordDeletedError(error: unknown): boolean {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = extractErrorStatus(current)\n const code = typeof current.code === 'string' ? current.code.toLowerCase() : ''\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n\n const matchesCode = (\n code === 'record_not_found'\n || code === 'not_found'\n || code === 'record_deleted'\n )\n const matchesMessage = (\n message.includes('not found')\n || message.includes('was deleted')\n || message.includes('record deleted')\n )\n\n if (status === 404 || matchesCode || matchesMessage) {\n return true\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return false\n}\n\nfunction clearIncomingChangesQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showIncomingChanges') !== '1') return\n url.searchParams.delete('showIncomingChanges')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction clearLockContentionQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showLockContention') !== '1') return\n url.searchParams.delete('showLockContention')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction submitCrudForm(formId: string): boolean {\n if (typeof document === 'undefined') return false\n const form = document.getElementById(formId)\n if (!(form instanceof HTMLFormElement)) return false\n form.requestSubmit()\n return true\n}\n\nfunction resolveResourceKind(context: CrudInjectionContext): string | null {\n if (context.resourceKind && context.resourceKind.trim()) return context.resourceKind\n if (context.kind === 'order') return 'sales.order'\n if (context.kind === 'quote') return 'sales.quote'\n if (context.personId) return 'customers.person'\n if (context.companyId) return 'customers.company'\n if (context.dealId) return 'customers.deal'\n const entityId = context.entityId\n if (entityId && entityId.includes(':')) {\n const [moduleId, rawEntity] = entityId.split(':')\n const entity = rawEntity ?? ''\n const normalizedModuleId = moduleId.trim()\n const normalizedEntity = entity.trim()\n if (normalizedModuleId && normalizedEntity) {\n const singularModuleId = normalizedModuleId.endsWith('s')\n ? normalizedModuleId.slice(0, -1)\n : normalizedModuleId\n\n const stripPrefixes = [\n `${normalizedModuleId}_`,\n `${singularModuleId}_`,\n ]\n\n let finalEntity = normalizedEntity\n for (const prefix of stripPrefixes) {\n if (finalEntity.startsWith(prefix)) {\n finalEntity = finalEntity.slice(prefix.length)\n break\n }\n }\n\n if (finalEntity) return `${normalizedModuleId}.${finalEntity}`\n }\n }\n\n const path = context.path ?? ''\n if (path.startsWith('/backend/customers/people/')) return 'customers.person'\n if (path.startsWith('/backend/customers/companies/')) return 'customers.company'\n if (path.startsWith('/backend/customers/deals/')) return 'customers.deal'\n if (path.startsWith('/backend/sales/orders/')) return 'sales.order'\n if (path.startsWith('/backend/sales/quotes/')) return 'sales.quote'\n if (path.startsWith('/backend/sales/documents/')) {\n const query = context.query ?? ''\n const params = new URLSearchParams(query)\n const kind = params.get('kind')\n if (kind === 'order') return 'sales.order'\n if (kind === 'quote') return 'sales.quote'\n }\n\n return null\n}\n\nfunction resolveResourceId(context: CrudInjectionContext, data: unknown): string | null {\n if (context.resourceId && context.resourceId.trim()) return context.resourceId\n if (context.recordId && context.recordId.trim()) return context.recordId\n if (context.personId && context.personId.trim()) return context.personId\n if (context.companyId && context.companyId.trim()) return context.companyId\n if (context.dealId && context.dealId.trim()) return context.dealId\n if (data && typeof data === 'object' && 'id' in data) {\n const id = (data as { id?: unknown }).id\n if (typeof id === 'string' && id.trim()) return id\n }\n if (data && typeof data === 'object') {\n const nestedPersonId = (data as { person?: { id?: unknown } }).person?.id\n if (typeof nestedPersonId === 'string' && nestedPersonId.trim()) return nestedPersonId\n const nestedCompanyId = (data as { company?: { id?: unknown } }).company?.id\n if (typeof nestedCompanyId === 'string' && nestedCompanyId.trim()) return nestedCompanyId\n const nestedDealId = (data as { deal?: { id?: unknown } }).deal?.id\n if (typeof nestedDealId === 'string' && nestedDealId.trim()) return nestedDealId\n }\n const path = context.path ?? ''\n const parts = path.split('/').filter((part) => part.length > 0)\n const candidates = [\n ['backend', 'customers', 'people'],\n ['backend', 'customers', 'companies'],\n ['backend', 'customers', 'deals'],\n ['backend', 'sales', 'orders'],\n ['backend', 'sales', 'quotes'],\n ['backend', 'sales', 'documents'],\n ] as const\n for (const prefix of candidates) {\n const matchesPrefix = prefix.every((segment, index) => parts[index] === segment)\n if (!matchesPrefix || parts.length <= prefix.length) continue\n const rawId = parts[prefix.length] ?? ''\n if (!rawId) continue\n try {\n const decoded = decodeURIComponent(rawId).trim()\n if (decoded.length > 0) return decoded\n } catch {\n const trimmed = rawId.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n}\n\nfunction resolveFormId(\n context: CrudInjectionContext,\n resourceKind: string | null,\n resourceId: string | null,\n): string {\n if (context.formId && context.formId.trim().length > 0) return context.formId\n if (resourceKind && resourceId) return `record-lock:${resourceKind}:${resourceId}`\n if (context.path && context.path.trim().length > 0) {\n const query = context.query?.trim()\n return `record-lock:${context.path}${query ? `?${query}` : ''}`\n }\n return 'record-lock:global'\n}\n\nasync function releaseLock(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n await apiCall('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'cancelled',\n }),\n })\n}\n\nfunction releaseLockWithKeepalive(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n const payload = JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'unmount',\n })\n\n try {\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([payload], { type: 'application/json' })\n const sent = navigator.sendBeacon('/api/record_locks/release', blob)\n if (sent) return\n }\n } catch {\n // ignore and fallback to fetch\n }\n\n void fetch('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: payload,\n keepalive: true,\n credentials: 'include',\n }).catch((error) => {\n console.warn('[RecordLockingWidget] Failed to release lock with keepalive fallback', error)\n })\n}\n\nexport default function RecordLockingWidget({\n context,\n data,\n}: InjectionWidgetComponentProps<CrudInjectionContext, Record<string, unknown>>) {\n const t = useT()\n const searchParams = useSearchParams()\n const resourceKind = React.useMemo(() => resolveResourceKind(context), [context])\n const resourceId = React.useMemo(() => resolveResourceId(context, data), [context, data])\n const formId = React.useMemo(\n () => resolveFormId(context, resourceKind, resourceId),\n [context, resourceId, resourceKind],\n )\n const [, forceRender] = React.useReducer((value) => value + 1, 0)\n const state = getRecordLockFormState(formId)\n const [mounted, setMounted] = React.useState(false)\n const [showIncomingChangesRequested, setShowIncomingChangesRequested] = React.useState(false)\n const [showLockContentionBanner, setShowLockContentionBanner] = React.useState(false)\n const [isConflictDialogOpen, setIsConflictDialogOpen] = React.useState(false)\n const instanceId = React.useMemo(\n () =>\n `record-lock-widget:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`,\n [],\n )\n const ownerPriority = context.formId ? 2 : 1\n const ownerKey = resourceKind && resourceId ? `${resourceKind}:${resourceId}` : null\n const [isPrimaryInstance, setIsPrimaryInstance] = React.useState(true)\n const releasePayloadRef = React.useRef<{\n resourceKind: string\n resourceId: string\n token: string\n } | null>(null)\n const keepMineRetryVersionRef = React.useRef(0)\n\n React.useEffect(() => {\n if (!ownerKey) {\n setIsPrimaryInstance(true)\n return\n }\n\n const owners = getRecordLockOwnerMap()\n const notifyOwnersChanged = () => {\n if (typeof window === 'undefined') return\n window.dispatchEvent(\n new CustomEvent('om:record-lock-owner-changed', {\n detail: { ownerKey },\n }),\n )\n }\n\n const claimOwnership = () => {\n const current = owners.get(ownerKey)\n if (!current) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n if (current.instanceId === instanceId) {\n setIsPrimaryInstance(true)\n return\n }\n if (ownerPriority > current.priority) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n setIsPrimaryInstance(false)\n }\n\n claimOwnership()\n const onOwnersChanged = () => claimOwnership()\n if (typeof window !== 'undefined') {\n window.addEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n\n return () => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n const current = owners.get(ownerKey)\n if (current?.instanceId === instanceId) {\n owners.delete(ownerKey)\n notifyOwnersChanged()\n }\n }\n }, [instanceId, ownerKey, ownerPriority])\n\n React.useEffect(() => {\n if (isPrimaryInstance) return\n const current = getRecordLockFormState(formId)\n if (!current?.lock?.token || !current.resourceKind || !current.resourceId) {\n clearRecordLockFormState(formId)\n return\n }\n void releaseLock({\n resourceKind: current.resourceKind,\n resourceId: current.resourceId,\n token: current.lock.token,\n reason: 'cancelled',\n }).catch((error) => {\n console.warn('[RecordLockingWidget] Failed to release lock while demoting owner', error)\n })\n clearRecordLockFormState(formId)\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n React.useEffect(() => {\n const showIncomingChanges = searchParams?.get('showIncomingChanges') === '1'\n const showLockContention = searchParams?.get('showLockContention') === '1'\n\n if (showIncomingChanges) {\n setShowIncomingChangesRequested(true)\n clearIncomingChangesQueryFlag()\n }\n\n if (showLockContention) {\n setShowLockContentionBanner(true)\n clearLockContentionQueryFlag()\n }\n }, [searchParams])\n\n React.useEffect(() => subscribeRecordLockFormState(formId, () => forceRender()), [formId])\n\n React.useEffect(() => {\n if (!state?.conflict) {\n setIsConflictDialogOpen(false)\n return\n }\n setIsConflictDialogOpen(true)\n }, [\n state?.conflict?.id,\n state?.conflict?.incomingActionLogId,\n state?.conflict?.baseActionLogId,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n setRecordLockFormState(formId, { formId, resourceKind, resourceId })\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n let active = true\n const acquire = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind, resourceId }),\n })\n const payload = call.result ?? {}\n if (!active) return\n if (!call.ok) {\n const defaultMessage = call.status === 403\n ? t('api.errors.forbidden', 'Forbidden')\n : t('record_locks.errors.acquire_failed', 'Failed to load record lock status.')\n const message = typeof payload.error === 'string' && payload.error.trim().length\n ? payload.error\n : defaultMessage\n flash(message, 'error')\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n void acquire()\n return () => {\n active = false\n }\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n const mine = Boolean(state?.lock?.token)\n const participants = React.useMemo(() => {\n if (!state?.lock) return []\n const fromPayload = Array.isArray(state.lock.participants) ? state.lock.participants : []\n if (fromPayload.length) return fromPayload\n return [{\n userId: state.lock.lockedByUserId,\n lockedByName: state.lock.lockedByName,\n lockedByEmail: state.lock.lockedByEmail,\n lockedByIp: state.lock.lockedByIp,\n lockedAt: state.lock.lockedAt,\n lastHeartbeatAt: state.lock.lastHeartbeatAt,\n expiresAt: state.lock.expiresAt,\n }]\n }, [state?.lock])\n const activeParticipantCount = state?.lock?.activeParticipantCount ?? participants.length\n const otherParticipants = React.useMemo(() => {\n if (!state?.currentUserId) return participants\n return participants.filter((participant) => participant.userId !== state.currentUserId)\n }, [participants, state?.currentUserId])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!mine || !state?.lock?.id) return\n let cancelled = false\n\n const syncContentionBanner = async () => {\n const call = await apiCall<{ items?: Array<{ sourceEntityId?: string | null; type?: string }> }>(\n '/api/notifications?status=unread&type=record_locks.lock.contended&pageSize=20'\n )\n if (cancelled) return\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const hasUnreadContention = items.some((item) => item.sourceEntityId === state.lock?.id)\n if (hasUnreadContention) {\n setShowLockContentionBanner(true)\n }\n }\n\n void syncContentionBanner()\n const interval = window.setInterval(() => {\n void syncContentionBanner()\n }, 5000)\n\n return () => {\n cancelled = true\n window.clearInterval(interval)\n }\n }, [isPrimaryInstance, mine, state?.lock?.id])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.resourceKind || !state?.resourceId) return\n if (state.recordDeleted === true) return\n let cancelled = false\n\n const syncRecordDeletedState = async () => {\n const call = await apiCall<{\n items?: Array<{\n sourceEntityId?: string | null\n bodyVariables?: Record<string, string> | null\n }>\n }>('/api/notifications?status=unread&type=record_locks.record.deleted&pageSize=20')\n if (cancelled) return\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const hasUnreadRecordDeleted = items.some((item) => {\n const matchesResourceId = item.sourceEntityId === state.resourceId\n if (!matchesResourceId) return false\n const kindFromBody = typeof item.bodyVariables?.resourceKind === 'string'\n ? item.bodyVariables.resourceKind.trim()\n : ''\n if (!kindFromBody) return true\n return kindFromBody === state.resourceKind\n })\n if (!hasUnreadRecordDeleted) return\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }\n\n void syncRecordDeletedState()\n const interval = window.setInterval(() => {\n void syncRecordDeletedState()\n }, 5000)\n\n return () => {\n cancelled = true\n window.clearInterval(interval)\n }\n }, [formId, isPrimaryInstance, state?.recordDeleted, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.lock?.token || !state.resourceKind || !state.resourceId) return\n const heartbeatSeconds = (\n typeof state.heartbeatSeconds === 'number'\n && Number.isFinite(state.heartbeatSeconds)\n && state.heartbeatSeconds > 0\n )\n ? state.heartbeatSeconds\n : 10\n const intervalMs = Math.max(1_000, Math.round(heartbeatSeconds * 1000))\n const interval = window.setInterval(() => {\n void apiCall('/api/record_locks/heartbeat', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock?.token,\n }),\n })\n }, intervalMs)\n return () => window.clearInterval(interval)\n }, [isPrimaryInstance, state?.heartbeatSeconds, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const hasUnresolvedConflict = Boolean(state?.conflict)\n && !(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal'\n )\n if (hasUnresolvedConflict) return\n if (!state?.resourceKind || !state?.resourceId) return\n let cancelled = false\n const refreshPresence = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n const payload = call.result ?? {}\n if (cancelled) return\n if (!call.ok) {\n const currentState = getRecordLockFormState(formId)\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? currentState?.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? currentState?.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? currentState?.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n const currentState = getRecordLockFormState(formId)\n const previousToken = currentState?.lock?.token ?? null\n const nextToken = payload.lock?.token ?? null\n const isSameSession = Boolean(previousToken && nextToken && previousToken === nextToken)\n const nextLatestActionLogId = isSameSession\n ? (currentState?.latestActionLogId ?? null)\n : (payload.latestActionLogId ?? currentState?.latestActionLogId ?? null)\n\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: nextLatestActionLogId,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n\n const interval = window.setInterval(() => {\n void refreshPresence()\n }, 4000)\n\n return () => {\n cancelled = true\n window.clearInterval(interval)\n }\n }, [\n formId,\n isPrimaryInstance,\n state?.conflict,\n state?.pendingResolution,\n state?.pendingResolutionArmed,\n state?.resourceId,\n state?.resourceKind,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!showIncomingChangesRequested) return\n if (!state?.resourceKind || !state?.resourceId || !state?.lock) return\n let cancelled = false\n const openIncomingChangesDialog = async () => {\n clearIncomingChangesQueryFlag()\n setShowIncomingChangesRequested(false)\n await validateBeforeSave({}, context)\n if (cancelled) return\n }\n void openIncomingChangesDialog()\n return () => {\n cancelled = true\n }\n }, [context, isPrimaryInstance, showIncomingChangesRequested, state?.lock, state?.resourceId, state?.resourceKind, t])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (\n state?.resourceKind\n && state?.resourceId\n && typeof state.lock?.token === 'string'\n && state.lock.token.trim().length > 0\n ) {\n releasePayloadRef.current = {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock.token,\n }\n return\n }\n releasePayloadRef.current = null\n }, [isPrimaryInstance, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onPageHide = () => {\n const payload = releasePayloadRef.current\n if (!payload) return\n releaseLockWithKeepalive({\n ...payload,\n reason: 'unmount',\n })\n }\n window.addEventListener('pagehide', onPageHide)\n return () => {\n window.removeEventListener('pagehide', onPageHide)\n }\n }, [isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n return () => {\n const payload = releasePayloadRef.current\n if (payload) {\n void releaseLock({\n ...payload,\n reason: 'unmount',\n })\n }\n clearRecordLockFormState(formId)\n }\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onCrudSaveError = (event: Event) => {\n const applyConflictPayload = (payload: {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n }) => {\n setIsConflictDialogOpen(true)\n const nextPatch: Partial<RecordLockFormState> = {\n conflict: payload.conflict,\n pendingConflictId: payload.conflict.id,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n }\n if (payload.lock !== undefined) {\n nextPatch.lock = payload.lock\n }\n if (payload.latestActionLogId !== undefined) {\n nextPatch.latestActionLogId = payload.latestActionLogId\n }\n setRecordLockFormState(formId, {\n ...nextPatch,\n })\n }\n\n const buildFallbackConflict = (currentState: RecordLockFormState) => {\n const preferredConflictId = isUuid(currentState.pendingConflictId)\n ? currentState.pendingConflictId\n : isUuid(currentState.conflict?.id)\n ? currentState.conflict.id\n : 'unresolved'\n return ({\n conflict: {\n id: preferredConflictId,\n resourceKind: currentState.resourceKind ?? '',\n resourceId: currentState.resourceId ?? '',\n baseActionLogId: currentState.latestActionLogId ?? null,\n incomingActionLogId: null,\n allowIncomingOverride: false,\n canOverrideIncoming: false,\n resolutionOptions: [],\n changes: [],\n } as RecordLockUiConflict,\n lock: currentState.lock ?? undefined,\n latestActionLogId: currentState.latestActionLogId ?? null,\n })\n }\n\n const detail = (event as CustomEvent<CrudSaveErrorEventDetail>).detail\n if (!detail) return\n const eventContextId = detail.contextId ?? detail.formId\n let payload = extractRecordLockConflictPayload(detail.error)\n const currentState = getRecordLockFormState(formId)\n const eventTargetsCurrentForm = !eventContextId || eventContextId === formId\n if (!eventTargetsCurrentForm) {\n if (!payload || !currentState?.resourceKind || !currentState?.resourceId) return\n const payloadResourceKind = payload.conflict.resourceKind?.trim() ?? ''\n const payloadResourceId = payload.conflict.resourceId?.trim() ?? ''\n if (!payloadResourceKind || !payloadResourceId) return\n if (payloadResourceKind !== currentState.resourceKind || payloadResourceId !== currentState.resourceId) return\n }\n if (!payload) {\n if (!currentState?.resourceKind || !currentState?.resourceId) return\n if (isRecordDeletedError(detail.error)) {\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n return\n }\n if (extractErrorStatus(detail.error) === 409) {\n applyConflictPayload(buildFallbackConflict(currentState))\n }\n return\n }\n\n applyConflictPayload(payload)\n }\n\n window.addEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.addEventListener('om:crud-save-error', onCrudSaveError)\n return () => {\n window.removeEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.removeEventListener('om:crud-save-error', onCrudSaveError)\n }\n }, [formId, isPrimaryInstance])\n\n const handleTakeOver = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n if (!state?.resourceKind || !state?.resourceId) return\n const call = await apiCall<AcquireResponse>('/api/record_locks/force-release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n if (!call.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const acquire = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind: state.resourceKind, resourceId: state.resourceId }),\n })\n if (!acquire.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const payload = acquire.result ?? {}\n setRecordLockFormState(formId, {\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n latestActionLogId: payload.latestActionLogId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }, [formId, state?.resourceId, state?.resourceKind, t])\n\n const handleAcceptIncoming = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n if (!state?.conflict || !state?.resourceKind || !state?.resourceId) return\n let conflictId: string | undefined = isUuid(state.conflict.id) ? state.conflict.id : undefined\n if (!conflictId) {\n const validation = await validateBeforeSave({}, context)\n conflictId = isUuid(validation.conflict?.id) ? validation.conflict.id : undefined\n if (!conflictId) {\n flash(\n t(\n 'record_locks.conflict.refresh_required',\n 'Could not confirm conflict details. Save again to refresh conflict data.',\n ),\n 'error',\n )\n return\n }\n }\n await apiCallOrThrow('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock?.token ?? undefined,\n reason: 'conflict_resolved',\n conflictId,\n resolution: 'accept_incoming',\n }),\n })\n setRecordLockFormState(formId, {\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n window.location.reload()\n }, [context, formId, state?.conflict, state?.lock?.token, state?.resourceId, state?.resourceKind, t])\n\n const handleKeepMine = React.useCallback(() => {\n if (!state?.conflict) return\n const applyAcceptMine = async () => {\n const keepMineRetryVersion = keepMineRetryVersionRef.current + 1\n keepMineRetryVersionRef.current = keepMineRetryVersion\n let conflictId: string | null = isUuid(state.conflict?.id) ? state.conflict.id : null\n if (!conflictId) {\n const validation = await validateBeforeSave({}, context)\n conflictId = isUuid(validation.conflict?.id) ? validation.conflict.id : null\n }\n if (!conflictId) {\n flash(\n t(\n 'record_locks.conflict.refresh_required',\n 'Could not confirm conflict details. Save again to refresh conflict data.',\n ),\n 'error',\n )\n return\n }\n setRecordLockFormState(formId, {\n pendingResolution: 'accept_mine',\n pendingConflictId: conflictId,\n pendingResolutionArmed: true,\n })\n window.setTimeout(async () => {\n if (keepMineRetryVersionRef.current !== keepMineRetryVersion) return\n const currentState = getRecordLockFormState(formId)\n if (currentState?.pendingResolution !== 'accept_mine') return\n const submitted = submitCrudForm(formId)\n if (submitted) return\n const retried = await Promise.resolve(context.retryLastMutation?.()).catch(() => false)\n if (!retried) {\n flash(\n t(\n 'record_locks.conflict.retry_save_after_accept_mine',\n 'Click save again to apply your changes.',\n ),\n 'info',\n )\n }\n }, 0)\n }\n void applyAcceptMine()\n }, [context, context.retryLastMutation, formId, state?.conflict, t])\n\n const handleKeepEditing = React.useCallback(() => {\n keepMineRetryVersionRef.current += 1\n setIsConflictDialogOpen(false)\n }, [])\n\n const noneLabel = t('audit_logs.common.none')\n const conflictChangeRows = React.useMemo<ChangeRow[]>(\n () => (state?.conflict?.changes ?? []).map((change) => ({\n field: change.field,\n from: change.incomingValue,\n to: change.mineValue,\n })),\n [state?.conflict?.changes],\n )\n const canKeepMyChanges = Boolean(\n state?.conflict?.allowIncomingOverride\n && state?.conflict?.canOverrideIncoming === true\n && state?.conflict?.resolutionOptions?.includes('accept_mine'),\n )\n const isRecordDeleted = state?.recordDeleted === true\n const showOverrideBlockedNotice = Boolean(\n state?.conflict?.allowIncomingOverride\n && !state?.conflict?.canOverrideIncoming,\n )\n const conflictDialog = (\n <Dialog open={Boolean(state?.conflict || isRecordDeleted) && isConflictDialogOpen} onOpenChange={(open) => {\n if (open) {\n setIsConflictDialogOpen(true)\n return\n }\n if (isRecordDeleted) {\n setIsConflictDialogOpen(true)\n return\n }\n setIsConflictDialogOpen(false)\n }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {isRecordDeleted\n ? t('record_locks.conflict.record_deleted_title', 'Record was deleted')\n : t('record_locks.conflict.title', 'Conflict detected')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-3 text-sm\">\n <p className=\"text-muted-foreground\">\n {isRecordDeleted\n ? t(\n 'record_locks.conflict.record_deleted_description',\n 'This record was deleted by another user while you were editing. Saving is blocked to avoid unexpected results.',\n )\n : t('record_locks.conflict.description', 'The record was changed by another user after you started editing.')}\n </p>\n {!isRecordDeleted ? (\n <ChangedFieldsTable\n changeRows={conflictChangeRows}\n noneLabel={noneLabel}\n t={t}\n beforeLabel={t('record_locks.conflict.incoming_label', 'Incoming')}\n afterLabel={t('record_locks.conflict.current_label', 'Current')}\n />\n ) : null}\n {(state?.conflict?.changes?.length ?? 0) === 0 ? (\n !isRecordDeleted ? (\n <Notice compact variant=\"info\">\n {t(\n 'record_locks.conflict.no_field_details',\n 'Field-level conflict details are unavailable for this record. Choose a resolution to continue.'\n )}\n </Notice>\n ) : null\n ) : null}\n {showOverrideBlockedNotice ? (\n <Notice compact variant=\"warning\">\n {t(\n 'record_locks.conflict.override_blocked_notice',\n 'You cannot keep your version because you do not have permission to override incoming changes.',\n )}\n </Notice>\n ) : null}\n <div className=\"-mx-6 -mb-6 mt-4 border-t bg-background/95 px-6 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80\">\n <div className=\"flex flex-wrap justify-end gap-2\">\n {isRecordDeleted ? null : (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleAcceptIncoming()\n }}\n >\n {t('record_locks.conflict.accept_incoming', 'Accept incoming')}\n </Button>\n {canKeepMyChanges ? (\n <Button\n type=\"button\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleKeepMine()\n }}\n >\n {t('record_locks.conflict.accept_mine', 'Keep my changes')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n handleKeepEditing()\n }}\n >\n {t('record_locks.conflict.keep_editing', 'Keep editing')}\n </Button>\n </>\n )}\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n\n const participantEmails = React.useMemo(() => {\n return otherParticipants\n .map((participant) => participant.lockedByEmail?.trim() ?? '')\n .filter((email, index, all) => email.length > 0 && all.indexOf(email) === index)\n .slice(0, 4)\n }, [otherParticipants])\n\n React.useEffect(() => {\n if (!showLockContentionBanner) return\n if (!mine) return\n if (activeParticipantCount > 1) return\n setShowLockContentionBanner(false)\n }, [activeParticipantCount, mine, showLockContentionBanner])\n\n const topBannerTarget = mounted ? document.getElementById('om-top-banners') : null\n\n if (!isPrimaryInstance) return null\n if (!state?.lock) return conflictDialog\n\n const defaultPresenceMessage = activeParticipantCount > 1\n ? `${activeParticipantCount} users are currently on this record.`\n : 'This record is currently locked by another user.'\n const bannerMessageRaw = !mine\n ? t('record_locks.banner.locked_by_other', 'This record is currently locked by another user.')\n : showLockContentionBanner\n ? t('record_locks.banner.contention_notice', 'Another user opened this record while you are editing it. Conflicts may occur on save.')\n : t('record_locks.banner.participants_notice', 'Multiple users are currently on this record.')\n const bannerMessage = typeof bannerMessageRaw === 'string' && bannerMessageRaw.trim().length > 0\n ? bannerMessageRaw\n : defaultPresenceMessage\n const shouldShowPresenceBanner = Boolean(\n showLockContentionBanner\n || activeParticipantCount > 1\n || !mine,\n )\n\n const lockBanner = shouldShowPresenceBanner ? (\n <div className=\"rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900 shadow-sm\">\n <div className=\"font-medium\">\n {bannerMessage}\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-amber-900/90\">\n <span>{`${t('record_locks.banner.active_users_label', 'Active users')}: ${activeParticipantCount}`}</span>\n {participantEmails.map((email) => (\n <span\n key={email}\n className=\"inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-100 px-2 py-0.5 text-[11px] font-medium text-amber-900\"\n >\n <Mail className=\"h-3 w-3\" />\n <span>{email}</span>\n </span>\n ))}\n </div>\n <div className=\"mt-2 flex gap-2\">\n {state.allowForceUnlock && !mine ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={handleTakeOver}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('record_locks.banner.take_over', 'Take over editing')}\n </Button>\n ) : null}\n {showLockContentionBanner ? (\n <div className=\"mt-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowLockContentionBanner(false)}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('common.close', 'Close')}\n </Button>\n </div>\n ) : null}\n </div>\n </div>\n ) : null\n\n return (\n <>\n {lockBanner ? (topBannerTarget ? createPortal(lockBanner, topBannerTarget) : lockBanner) : null}\n {conflictDialog}\n </>\n )\n}\n\nexport async function validateBeforeSave(\n data: Record<string, unknown>,\n context: CrudInjectionContext,\n): Promise<ValidateResponse> {\n const contextResourceKind = resolveResourceKind(context)\n const contextResourceId = resolveResourceId(context, data)\n const formId = resolveFormId(context, contextResourceKind, contextResourceId)\n const state = getRecordLockFormState(formId)\n const resourceKind = state?.resourceKind || contextResourceKind\n const resourceId = state?.resourceId || contextResourceId\n if (!resourceKind || !resourceId) {\n return { ok: true }\n }\n const hasSelectedConflictResolution = Boolean(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal',\n )\n if (state?.conflict && !hasSelectedConflictResolution) {\n return {\n ok: false,\n status: 409,\n code: 'record_lock_conflict',\n lock: state.lock ?? null,\n conflict: state.conflict,\n latestActionLogId: state.latestActionLogId ?? null,\n }\n }\n const hasResolvableConflict = Boolean(state?.conflict?.id && isUuid(state.conflict.id))\n const requestedResolution = state?.pendingResolution ?? 'normal'\n const resolution = requestedResolution !== 'normal' && !hasResolvableConflict\n ? 'normal'\n : requestedResolution\n const rawConflictId = resolution === 'normal' || !hasResolvableConflict\n ? undefined\n : (state?.pendingConflictId ?? state?.conflict?.id ?? undefined)\n const conflictId = isUuid(rawConflictId) ? rawConflictId : undefined\n const call = await apiCall<ValidateResponse>('/api/record_locks/validate', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind,\n resourceId,\n method: 'PUT',\n token: state?.lock?.token ?? undefined,\n baseLogId: state?.latestActionLogId ?? state?.lock?.baseActionLogId ?? undefined,\n conflictId,\n resolution,\n mutationPayload: data,\n }),\n })\n const payload = call.result ?? { ok: false }\n if (payload.ok) {\n const nextResolution = resolution === 'normal' ? 'normal' : resolution\n const preserveConflictUntilSuccessfulSave = nextResolution !== 'normal'\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n latestActionLogId: payload.latestActionLogId ?? state?.latestActionLogId ?? null,\n lock: payload.lock ?? state?.lock ?? null,\n conflict: preserveConflictUntilSuccessfulSave ? (state?.conflict ?? null) : null,\n pendingConflictId: nextResolution === 'normal' ? null : (conflictId ?? state?.pendingConflictId ?? null),\n pendingResolution: nextResolution,\n pendingResolutionArmed: nextResolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n }\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n lock: payload.lock ?? state?.lock ?? null,\n conflict: payload.conflict ?? state?.conflict ?? null,\n pendingConflictId: payload.conflict?.id ?? conflictId ?? state?.pendingConflictId ?? null,\n pendingResolution: resolution === 'normal' ? 'normal' : resolution,\n pendingResolutionArmed: resolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n}\n"],
|
|
5
|
-
"mappings": ";AA4pCU,SA6CI,UA7CJ,KA6CI,YA7CJ;AA1pCV,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAC7B,SAAS,SAAS,sBAAsB;AACxC,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,QAAQ,eAAe,cAAc,mBAAmB;AACjE,SAAS,cAAc;AACvB,SAAS,YAAY;AAErB,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAChC,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAsBP,MAAM,gCAAgC;AAEtC,SAAS,wBAA4D;AACnE,QAAM,QAAQ;AACd,QAAM,WAAW,MAAM,6BAA6B;AACpD,MAAI,oBAAoB,IAAK,QAAO;AACpC,QAAM,OAAO,oBAAI,IAAmC;AACpD,QAAM,6BAA6B,IAAI;AACvC,SAAO;AACT;AA6BA,SAAS,eAAe,OAAkD;AACxE,SAAO,QAAQ,KAAK,KAAK,OAAO,UAAU;AAC5C;AAEA,SAAS,OAAO,OAAmD;AACjE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,6EAA6E,KAAK,OAAO;AAClG;AAEA,SAAS,mBAAmB,OAA+B;AACzD,QAAM,QAAmB,CAAC,KAAK;AAC/B,QAAM,UAAU,oBAAI,IAAa;AAEjC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,WAAW,QAAQ,IAAI,OAAO,EAAG;AACtC,YAAQ,IAAI,OAAO;AACnB,QAAI,CAAC,eAAe,OAAO,EAAG;AAE9B,UAAM,SAAS,QAAQ;AACvB,QAAI,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM,EAAG,QAAO;AAClE,QAAI,OAAO,WAAW,UAAU;AAC9B,YAAM,SAAS,OAAO,MAAM;AAC5B,UAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,IACtC;AAEA,UAAM,aAAa,QAAQ;AAC3B,QAAI,OAAO,eAAe,YAAY,OAAO,SAAS,UAAU,EAAG,QAAO;AAC1E,QAAI,OAAO,eAAe,UAAU;AAClC,YAAM,SAAS,OAAO,UAAU;AAChC,UAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,IACtC;AAEA,UAAM,SAAS,CAAC,QAAQ,YAAY,QAAQ,WAAW,SAAS,OAAO;AACvE,eAAW,OAAO,QAAQ;AACxB,YAAM,OAAO,QAAQ,GAAG;AACxB,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IACjD;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iCAAiC,OAIjC;AACP,QAAM,QAAmB,CAAC,KAAK;AAC/B,QAAM,UAAU,oBAAI,IAAa;AAEjC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,WAAW,QAAQ,IAAI,OAAO,EAAG;AACtC,YAAQ,IAAI,OAAO;AACnB,QAAI,CAAC,eAAe,OAAO,EAAG;AAE9B,UAAM,SAAS,CAAC,QAAQ,YAAY,QAAQ,WAAW,OAAO;AAC9D,eAAW,OAAO,QAAQ;AACxB,YAAM,OAAO,QAAQ,GAAG;AACxB,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IACjD;AAEA,UAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAC/D,UAAM,SAAS,mBAAmB,OAAO;AACzC,UAAM,iBACJ,eAAe,QAAQ,IAAI,KACxB,eAAe,QAAQ,QAAQ,KAC/B,OAAO,QAAQ,eAAe,YAC9B,OAAO,QAAQ,iBAAiB,YAChC,OAAO,QAAQ,eAAe,YAC9B,MAAM,QAAQ,QAAQ,iBAAiB,KACvC,OAAO,QAAQ,0BAA0B,aACzC,OAAO,QAAQ,wBAAwB;AAE5C,UAAM,UAAU,OAAO,QAAQ,YAAY,WACvC,QAAQ,QAAQ,YAAY,IAC5B,OAAO,QAAQ,UAAU,WACvB,QAAQ,MAAM,YAAY,IAC1B;AACN,UAAM,+BACJ,QAAQ,SAAS,iBAAiB,KAC/B,QAAQ,SAAS,sBAAsB,KACvC,QAAQ,SAAS,mBAAmB;AAEzC,UAAM,uBACJ,SAAS,0BACL,WAAW,QAAQ,kBAAkB;AAE3C,QAAI,CAAC,qBAAsB;AAC3B,QAAI,CAAC,eAAe,QAAQ,QAAQ,GAAG;AACrC,YAAM,OAAO,eAAe,QAAQ,IAAI,IAAK,QAAQ,OAA4B;AACjF,YAAM,qBAAqB,OAAO,QAAQ,eAAe,YAAY,OAAO,QAAQ,UAAU,IAC1F,QAAQ,aACR;AACJ,YAAM,mBAAyC;AAAA,QAC7C,IAAI;AAAA,QACJ,eACG,OAAO,QAAQ,iBAAiB,YAAY,QAAQ,aAAa,KAAK,EAAE,SAAS,IAC9E,QAAQ,eACR,MAAM,iBAAiB;AAAA,QAC7B,aACG,OAAO,QAAQ,eAAe,YAAY,QAAQ,WAAW,KAAK,EAAE,SAAS,IAC1E,QAAQ,aACR,MAAM,eAAe;AAAA,QAC3B,iBACE,OAAO,QAAQ,oBAAoB,YAAY,QAAQ,oBAAoB,OACvE,QAAQ,kBACR,MAAM,mBAAmB;AAAA,QAC/B,qBACE,OAAO,QAAQ,wBAAwB,YAAY,QAAQ,wBAAwB,OAC/E,QAAQ,sBACR;AAAA,QACN,uBAAuB,QAAQ,QAAQ,qBAAqB;AAAA,QAC5D,qBAAqB,QAAQ,QAAQ,mBAAmB;AAAA,QACxD,mBAAmB,MAAM,QAAQ,QAAQ,iBAAiB,KAAK,QAAQ,kBAAkB,SAAS,aAAa,IAC3G,CAAC,aAAa,IACd,CAAC;AAAA,QACL,SAAS,CAAC;AAAA,MACZ;AACA,aAAO;AAAA,QACL,UAAU;AAAA,QACV;AAAA,QACA,mBAAmB,OAAO,QAAQ,sBAAsB,YAAY,QAAQ,sBAAsB,OAC9F,QAAQ,oBACR;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,MACL,UAAU,QAAQ;AAAA,MAClB,MAAM,eAAe,QAAQ,IAAI,IAAK,QAAQ,OAA4B;AAAA,MAC1E,mBAAmB,OAAO,QAAQ,sBAAsB,YAAY,QAAQ,sBAAsB,OAC9F,QAAQ,oBACR;AAAA,IACN;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAAyB;AACrD,QAAM,QAAmB,CAAC,KAAK;AAC/B,QAAM,UAAU,oBAAI,IAAa;AAEjC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,WAAW,QAAQ,IAAI,OAAO,EAAG;AACtC,YAAQ,IAAI,OAAO;AACnB,QAAI,CAAC,eAAe,OAAO,EAAG;AAE9B,UAAM,SAAS,mBAAmB,OAAO;AACzC,UAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,QAAQ,KAAK,YAAY,IAAI;AAC7E,UAAM,UAAU,OAAO,QAAQ,YAAY,WACvC,QAAQ,QAAQ,YAAY,IAC5B,OAAO,QAAQ,UAAU,WACvB,QAAQ,MAAM,YAAY,IAC1B;AAEN,UAAM,cACJ,SAAS,sBACN,SAAS,eACT,SAAS;AAEd,UAAM,iBACJ,QAAQ,SAAS,WAAW,KACzB,QAAQ,SAAS,aAAa,KAC9B,QAAQ,SAAS,gBAAgB;AAGtC,QAAI,WAAW,OAAO,eAAe,gBAAgB;AACnD,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,CAAC,QAAQ,YAAY,QAAQ,WAAW,SAAS,OAAO;AACvE,eAAW,OAAO,QAAQ;AACxB,YAAM,OAAO,QAAQ,GAAG;AACxB,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IACjD;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,gCAAgC;AACvC,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,QAAI,IAAI,aAAa,IAAI,qBAAqB,MAAM,IAAK;AACzD,QAAI,aAAa,OAAO,qBAAqB;AAC7C,UAAM,UAAU,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI;AACvD,WAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,OAAO;AAAA,EAC/D,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,+BAA+B;AACtC,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,QAAI,IAAI,aAAa,IAAI,oBAAoB,MAAM,IAAK;AACxD,QAAI,aAAa,OAAO,oBAAoB;AAC5C,UAAM,UAAU,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI;AACvD,WAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,OAAO;AAAA,EAC/D,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,eAAe,QAAyB;AAC/C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,OAAO,SAAS,eAAe,MAAM;AAC3C,MAAI,EAAE,gBAAgB,iBAAkB,QAAO;AAC/C,OAAK,cAAc;AACnB,SAAO;AACT;AAEA,SAAS,oBAAoB,SAA8C;AACzE,MAAI,QAAQ,gBAAgB,QAAQ,aAAa,KAAK,EAAG,QAAO,QAAQ;AACxE,MAAI,QAAQ,SAAS,QAAS,QAAO;AACrC,MAAI,QAAQ,SAAS,QAAS,QAAO;AACrC,MAAI,QAAQ,SAAU,QAAO;AAC7B,MAAI,QAAQ,UAAW,QAAO;AAC9B,MAAI,QAAQ,OAAQ,QAAO;AAC3B,QAAM,WAAW,QAAQ;AACzB,MAAI,YAAY,SAAS,SAAS,GAAG,GAAG;AACtC,UAAM,CAAC,UAAU,SAAS,IAAI,SAAS,MAAM,GAAG;AAChD,UAAM,SAAS,aAAa;AAC5B,UAAM,qBAAqB,SAAS,KAAK;AACzC,UAAM,mBAAmB,OAAO,KAAK;AACrC,QAAI,sBAAsB,kBAAkB;AAC1C,YAAM,mBAAmB,mBAAmB,SAAS,GAAG,IACpD,mBAAmB,MAAM,GAAG,EAAE,IAC9B;AAEJ,YAAM,gBAAgB;AAAA,QACpB,GAAG,kBAAkB;AAAA,QACrB,GAAG,gBAAgB;AAAA,MACrB;AAEA,UAAI,cAAc;AAClB,iBAAW,UAAU,eAAe;AAClC,YAAI,YAAY,WAAW,MAAM,GAAG;AAClC,wBAAc,YAAY,MAAM,OAAO,MAAM;AAC7C;AAAA,QACF;AAAA,MACF;AAEA,UAAI,YAAa,QAAO,GAAG,kBAAkB,IAAI,WAAW;AAAA,IAC9D;AAAA,EACF;AAEA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,MAAI,KAAK,WAAW,4BAA4B,EAAG,QAAO;AAC1D,MAAI,KAAK,WAAW,+BAA+B,EAAG,QAAO;AAC7D,MAAI,KAAK,WAAW,2BAA2B,EAAG,QAAO;AACzD,MAAI,KAAK,WAAW,wBAAwB,EAAG,QAAO;AACtD,MAAI,KAAK,WAAW,wBAAwB,EAAG,QAAO;AACtD,MAAI,KAAK,WAAW,2BAA2B,GAAG;AAChD,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,SAAS,IAAI,gBAAgB,KAAK;AACxC,UAAM,OAAO,OAAO,IAAI,MAAM;AAC9B,QAAI,SAAS,QAAS,QAAO;AAC7B,QAAI,SAAS,QAAS,QAAO;AAAA,EAC/B;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAA+B,MAA8B;AACtF,MAAI,QAAQ,cAAc,QAAQ,WAAW,KAAK,EAAG,QAAO,QAAQ;AACpE,MAAI,QAAQ,YAAY,QAAQ,SAAS,KAAK,EAAG,QAAO,QAAQ;AAChE,MAAI,QAAQ,YAAY,QAAQ,SAAS,KAAK,EAAG,QAAO,QAAQ;AAChE,MAAI,QAAQ,aAAa,QAAQ,UAAU,KAAK,EAAG,QAAO,QAAQ;AAClE,MAAI,QAAQ,UAAU,QAAQ,OAAO,KAAK,EAAG,QAAO,QAAQ;AAC5D,MAAI,QAAQ,OAAO,SAAS,YAAY,QAAQ,MAAM;AACpD,UAAM,KAAM,KAA0B;AACtC,QAAI,OAAO,OAAO,YAAY,GAAG,KAAK,EAAG,QAAO;AAAA,EAClD;AACA,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,UAAM,iBAAkB,KAAuC,QAAQ;AACvE,QAAI,OAAO,mBAAmB,YAAY,eAAe,KAAK,EAAG,QAAO;AACxE,UAAM,kBAAmB,KAAwC,SAAS;AAC1E,QAAI,OAAO,oBAAoB,YAAY,gBAAgB,KAAK,EAAG,QAAO;AAC1E,UAAM,eAAgB,KAAqC,MAAM;AACjE,QAAI,OAAO,iBAAiB,YAAY,aAAa,KAAK,EAAG,QAAO;AAAA,EACtE;AACA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AAC9D,QAAM,aAAa;AAAA,IACjB,CAAC,WAAW,aAAa,QAAQ;AAAA,IACjC,CAAC,WAAW,aAAa,WAAW;AAAA,IACpC,CAAC,WAAW,aAAa,OAAO;AAAA,IAChC,CAAC,WAAW,SAAS,QAAQ;AAAA,IAC7B,CAAC,WAAW,SAAS,QAAQ;AAAA,IAC7B,CAAC,WAAW,SAAS,WAAW;AAAA,EAClC;AACA,aAAW,UAAU,YAAY;AAC/B,UAAM,gBAAgB,OAAO,MAAM,CAAC,SAAS,UAAU,MAAM,KAAK,MAAM,OAAO;AAC/E,QAAI,CAAC,iBAAiB,MAAM,UAAU,OAAO,OAAQ;AACrD,UAAM,QAAQ,MAAM,OAAO,MAAM,KAAK;AACtC,QAAI,CAAC,MAAO;AACZ,QAAI;AACF,YAAM,UAAU,mBAAmB,KAAK,EAAE,KAAK;AAC/C,UAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,IACjC,QAAQ;AACN,YAAM,UAAU,MAAM,KAAK;AAC3B,UAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cACP,SACA,cACA,YACQ;AACR,MAAI,QAAQ,UAAU,QAAQ,OAAO,KAAK,EAAE,SAAS,EAAG,QAAO,QAAQ;AACvE,MAAI,gBAAgB,WAAY,QAAO,eAAe,YAAY,IAAI,UAAU;AAChF,MAAI,QAAQ,QAAQ,QAAQ,KAAK,KAAK,EAAE,SAAS,GAAG;AAClD,UAAM,QAAQ,QAAQ,OAAO,KAAK;AAClC,WAAO,eAAe,QAAQ,IAAI,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,EAC/D;AACA,SAAO;AACT;AAEA,eAAe,YAAY,OAKxB;AACD,QAAM,QAAQ,6BAA6B;AAAA,IACzC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,cAAc,MAAM;AAAA,MACpB,YAAY,MAAM;AAAA,MAClB,OAAO,MAAM,SAAS;AAAA,MACtB,QAAQ,MAAM,UAAU;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,yBAAyB,OAK/B;AACD,QAAM,UAAU,KAAK,UAAU;AAAA,IAC7B,cAAc,MAAM;AAAA,IACpB,YAAY,MAAM;AAAA,IAClB,OAAO,MAAM,SAAS;AAAA,IACtB,QAAQ,MAAM,UAAU;AAAA,EAC1B,CAAC;AAED,MAAI;AACF,QAAI,OAAO,cAAc,eAAe,OAAO,UAAU,eAAe,YAAY;AAClF,YAAM,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC7D,YAAM,OAAO,UAAU,WAAW,6BAA6B,IAAI;AACnE,UAAI,KAAM;AAAA,IACZ;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,OAAK,MAAM,6BAA6B;AAAA,IACtC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,EACf,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,YAAQ,KAAK,wEAAwE,KAAK;AAAA,EAC5F,CAAC;AACH;AAEe,SAAR,oBAAqC;AAAA,EAC1C;AAAA,EACA;AACF,GAAiF;AAC/E,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,gBAAgB;AACrC,QAAM,eAAe,MAAM,QAAQ,MAAM,oBAAoB,OAAO,GAAG,CAAC,OAAO,CAAC;AAChF,QAAM,aAAa,MAAM,QAAQ,MAAM,kBAAkB,SAAS,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC;AACxF,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM,cAAc,SAAS,cAAc,UAAU;AAAA,IACrD,CAAC,SAAS,YAAY,YAAY;AAAA,EACpC;AACA,QAAM,CAAC,EAAE,WAAW,IAAI,MAAM,WAAW,CAAC,UAAU,QAAQ,GAAG,CAAC;AAChE,QAAM,QAAQ,uBAAuB,MAAM;AAC3C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,8BAA8B,+BAA+B,IAAI,MAAM,SAAS,KAAK;AAC5F,QAAM,CAAC,0BAA0B,2BAA2B,IAAI,MAAM,SAAS,KAAK;AACpF,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,MAAM,SAAS,KAAK;AAC5E,QAAM,aAAa,MAAM;AAAA,IACvB,MACE,sBAAsB,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IAC1F,CAAC;AAAA,EACH;AACA,QAAM,gBAAgB,QAAQ,SAAS,IAAI;AAC3C,QAAM,WAAW,gBAAgB,aAAa,GAAG,YAAY,IAAI,UAAU,KAAK;AAChF,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,IAAI;AACrE,QAAM,oBAAoB,MAAM,OAItB,IAAI;AACd,QAAM,0BAA0B,MAAM,OAAO,CAAC;AAE9C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,UAAU;AACb,2BAAqB,IAAI;AACzB;AAAA,IACF;AAEA,UAAM,SAAS,sBAAsB;AACrC,UAAM,sBAAsB,MAAM;AAChC,UAAI,OAAO,WAAW,YAAa;AACnC,aAAO;AAAA,QACL,IAAI,YAAY,gCAAgC;AAAA,UAC9C,QAAQ,EAAE,SAAS;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,iBAAiB,MAAM;AAC3B,YAAM,UAAU,OAAO,IAAI,QAAQ;AACnC,UAAI,CAAC,SAAS;AACZ,eAAO,IAAI,UAAU,EAAE,YAAY,UAAU,cAAc,CAAC;AAC5D,6BAAqB,IAAI;AACzB,4BAAoB;AACpB;AAAA,MACF;AACA,UAAI,QAAQ,eAAe,YAAY;AACrC,6BAAqB,IAAI;AACzB;AAAA,MACF;AACA,UAAI,gBAAgB,QAAQ,UAAU;AACpC,eAAO,IAAI,UAAU,EAAE,YAAY,UAAU,cAAc,CAAC;AAC5D,6BAAqB,IAAI;AACzB,4BAAoB;AACpB;AAAA,MACF;AACA,2BAAqB,KAAK;AAAA,IAC5B;AAEA,mBAAe;AACf,UAAM,kBAAkB,MAAM,eAAe;AAC7C,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,iBAAiB,gCAAgC,eAAe;AAAA,IACzE;AAEA,WAAO,MAAM;AACX,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,oBAAoB,gCAAgC,eAAe;AAAA,MAC5E;AACA,YAAM,UAAU,OAAO,IAAI,QAAQ;AACnC,UAAI,SAAS,eAAe,YAAY;AACtC,eAAO,OAAO,QAAQ;AACtB,4BAAoB;AAAA,MACtB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,UAAU,aAAa,CAAC;AAExC,QAAM,UAAU,MAAM;AACpB,QAAI,kBAAmB;AACvB,UAAM,UAAU,uBAAuB,MAAM;AAC7C,QAAI,CAAC,SAAS,MAAM,SAAS,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,YAAY;AACzE,+BAAyB,MAAM;AAC/B;AAAA,IACF;AACA,SAAK,YAAY;AAAA,MACf,cAAc,QAAQ;AAAA,MACtB,YAAY,QAAQ;AAAA,MACpB,OAAO,QAAQ,KAAK;AAAA,MACpB,QAAQ;AAAA,IACV,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,cAAQ,KAAK,qEAAqE,KAAK;AAAA,IACzF,CAAC;AACD,6BAAyB,MAAM;AAAA,EACjC,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAE9B,QAAM,UAAU,MAAM;AACpB,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,UAAM,sBAAsB,cAAc,IAAI,qBAAqB,MAAM;AACzE,UAAM,qBAAqB,cAAc,IAAI,oBAAoB,MAAM;AAEvE,QAAI,qBAAqB;AACvB,sCAAgC,IAAI;AACpC,oCAA8B;AAAA,IAChC;AAEA,QAAI,oBAAoB;AACtB,kCAA4B,IAAI;AAChC,mCAA6B;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,UAAU,MAAM,6BAA6B,QAAQ,MAAM,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;AAEzF,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,OAAO,UAAU;AACpB,8BAAwB,KAAK;AAC7B;AAAA,IACF;AACA,4BAAwB,IAAI;AAAA,EAC9B,GAAG;AAAA,IACD,OAAO,UAAU;AAAA,IACjB,OAAO,UAAU;AAAA,IACjB,OAAO,UAAU;AAAA,EACnB,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,gBAAgB,CAAC,WAAY;AAClC,2BAAuB,QAAQ,EAAE,QAAQ,cAAc,WAAW,CAAC;AAAA,EACrE,GAAG,CAAC,QAAQ,mBAAmB,YAAY,YAAY,CAAC;AAExD,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,gBAAgB,CAAC,WAAY;AAClC,QAAI,SAAS;AACb,UAAM,UAAU,YAAY;AAC1B,YAAM,OAAO,MAAM,QAAyB,6BAA6B;AAAA,QACvE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,cAAc,WAAW,CAAC;AAAA,MACnD,CAAC;AACD,YAAM,UAAU,KAAK,UAAU,CAAC;AAChC,UAAI,CAAC,OAAQ;AACb,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,iBAAiB,KAAK,WAAW,MACnC,EAAE,wBAAwB,WAAW,IACrC,EAAE,sCAAsC,oCAAoC;AAChF,cAAM,UAAU,OAAO,QAAQ,UAAU,YAAY,QAAQ,MAAM,KAAK,EAAE,SACtE,QAAQ,QACR;AACJ,cAAM,SAAS,OAAO;AACtB,+BAAuB,QAAQ;AAAA,UAC7B;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV,MAAM,QAAQ,QAAQ;AAAA,UACtB,eAAe,QAAQ,iBAAiB;AAAA,UACxC,kBAAkB,QAAQ,oBAAoB;AAAA,UAC9C,mBAAmB,QAAQ,qBAAqB;AAAA,UAChD,kBAAkB,QAAQ,oBAAoB;AAAA,QAChD,CAAC;AACD;AAAA,MACF;AACA,6BAAuB,QAAQ;AAAA,QAC7B;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,QAAQ,YAAY;AAAA,QAC9B,MAAM,QAAQ,QAAQ;AAAA,QACtB,eAAe,QAAQ,iBAAiB;AAAA,QACxC,kBAAkB,QAAQ,oBAAoB;AAAA,QAC9C,mBAAmB,QAAQ,qBAAqB;AAAA,QAChD,kBAAkB,QAAQ,oBAAoB;AAAA,MAChD,CAAC;AAAA,IACH;AACA,SAAK,QAAQ;AACb,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,QAAQ,mBAAmB,YAAY,YAAY,CAAC;AAExD,QAAM,OAAO,QAAQ,OAAO,MAAM,KAAK;AACvC,QAAM,eAAe,MAAM,QAAQ,MAAM;AACvC,QAAI,CAAC,OAAO,KAAM,QAAO,CAAC;AAC1B,UAAM,cAAc,MAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,eAAe,CAAC;AACxF,QAAI,YAAY,OAAQ,QAAO;AAC/B,WAAO,CAAC;AAAA,MACN,QAAQ,MAAM,KAAK;AAAA,MACnB,cAAc,MAAM,KAAK;AAAA,MACzB,eAAe,MAAM,KAAK;AAAA,MAC1B,YAAY,MAAM,KAAK;AAAA,MACvB,UAAU,MAAM,KAAK;AAAA,MACrB,iBAAiB,MAAM,KAAK;AAAA,MAC5B,WAAW,MAAM,KAAK;AAAA,IACxB,CAAC;AAAA,EACH,GAAG,CAAC,OAAO,IAAI,CAAC;AAChB,QAAM,yBAAyB,OAAO,MAAM,0BAA0B,aAAa;AACnF,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,QAAI,CAAC,OAAO,cAAe,QAAO;AAClC,WAAO,aAAa,OAAO,CAAC,gBAAgB,YAAY,WAAW,MAAM,aAAa;AAAA,EACxF,GAAG,CAAC,cAAc,OAAO,aAAa,CAAC;AAEvC,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,QAAQ,CAAC,OAAO,MAAM,GAAI;AAC/B,QAAI,YAAY;AAEhB,UAAM,uBAAuB,YAAY;AACvC,YAAM,OAAO,MAAM;AAAA,QACjB;AAAA,MACF;AACA,UAAI,UAAW;AACf,YAAM,QAAQ,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,OAAO,QAAQ,CAAC;AACvE,YAAM,sBAAsB,MAAM,KAAK,CAAC,SAAS,KAAK,mBAAmB,MAAM,MAAM,EAAE;AACvF,UAAI,qBAAqB;AACvB,oCAA4B,IAAI;AAAA,MAClC;AAAA,IACF;AAEA,SAAK,qBAAqB;AAC1B,UAAM,WAAW,OAAO,YAAY,MAAM;AACxC,WAAK,qBAAqB;AAAA,IAC5B,GAAG,GAAI;AAEP,WAAO,MAAM;AACX,kBAAY;AACZ,aAAO,cAAc,QAAQ;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,mBAAmB,MAAM,OAAO,MAAM,EAAE,CAAC;AAE7C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAY;AAChD,QAAI,MAAM,kBAAkB,KAAM;AAClC,QAAI,YAAY;AAEhB,UAAM,yBAAyB,YAAY;AACzC,YAAM,OAAO,MAAM,QAKhB,+EAA+E;AAClF,UAAI,UAAW;AACf,YAAM,QAAQ,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,OAAO,QAAQ,CAAC;AACvE,YAAM,yBAAyB,MAAM,KAAK,CAAC,SAAS;AAClD,cAAM,oBAAoB,KAAK,mBAAmB,MAAM;AACxD,YAAI,CAAC,kBAAmB,QAAO;AAC/B,cAAM,eAAe,OAAO,KAAK,eAAe,iBAAiB,WAC7D,KAAK,cAAc,aAAa,KAAK,IACrC;AACJ,YAAI,CAAC,aAAc,QAAO;AAC1B,eAAO,iBAAiB,MAAM;AAAA,MAChC,CAAC;AACD,UAAI,CAAC,uBAAwB;AAC7B,8BAAwB,IAAI;AAC5B,6BAAuB,QAAQ;AAAA,QAC7B,eAAe;AAAA,QACf,UAAU;AAAA,QACV,MAAM;AAAA,QACN,UAAU;AAAA,QACV,mBAAmB;AAAA,QACnB,mBAAmB;AAAA,QACnB,wBAAwB;AAAA,MAC1B,CAAC;AAAA,IACH;AAEA,SAAK,uBAAuB;AAC5B,UAAM,WAAW,OAAO,YAAY,MAAM;AACxC,WAAK,uBAAuB;AAAA,IAC9B,GAAG,GAAI;AAEP,WAAO,MAAM;AACX,kBAAY;AACZ,aAAO,cAAc,QAAQ;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,QAAQ,mBAAmB,OAAO,eAAe,OAAO,YAAY,OAAO,YAAY,CAAC;AAE5F,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,OAAO,MAAM,SAAS,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAY;AACrE,UAAM,mBACJ,OAAO,MAAM,qBAAqB,YAC/B,OAAO,SAAS,MAAM,gBAAgB,KACtC,MAAM,mBAAmB,IAE1B,MAAM,mBACN;AACJ,UAAM,aAAa,KAAK,IAAI,KAAO,KAAK,MAAM,mBAAmB,GAAI,CAAC;AACtE,UAAM,WAAW,OAAO,YAAY,MAAM;AACxC,WAAK,QAAQ,+BAA+B;AAAA,QAC1C,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,cAAc,MAAM;AAAA,UACpB,YAAY,MAAM;AAAA,UAClB,OAAO,MAAM,MAAM;AAAA,QACrB,CAAC;AAAA,MACH,CAAC;AAAA,IACH,GAAG,UAAU;AACb,WAAO,MAAM,OAAO,cAAc,QAAQ;AAAA,EAC5C,GAAG,CAAC,mBAAmB,OAAO,kBAAkB,OAAO,MAAM,OAAO,OAAO,YAAY,OAAO,YAAY,CAAC;AAE3G,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,UAAM,wBAAwB,QAAQ,OAAO,QAAQ,KAChD,EACD,OAAO,2BAA2B,QAC/B,OAAO,OAAO,sBAAsB,YACpC,MAAM,sBAAsB;AAEnC,QAAI,sBAAuB;AAC3B,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAY;AAChD,QAAI,YAAY;AAChB,UAAM,kBAAkB,YAAY;AAClC,YAAM,OAAO,MAAM,QAAyB,6BAA6B;AAAA,QACvE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,cAAc,MAAM;AAAA,UACpB,YAAY,MAAM;AAAA,QACpB,CAAC;AAAA,MACH,CAAC;AACD,YAAM,UAAU,KAAK,UAAU,CAAC;AAChC,UAAI,UAAW;AACf,UAAI,CAAC,KAAK,IAAI;AACZ,cAAMA,gBAAe,uBAAuB,MAAM;AAClD,+BAAuB,QAAQ;AAAA,UAC7B,cAAc,MAAM;AAAA,UACpB,YAAY,MAAM;AAAA,UAClB,UAAU;AAAA,UACV,MAAM,QAAQ,QAAQ;AAAA,UACtB,eAAe,QAAQ,iBAAiBA,eAAc,iBAAiB;AAAA,UACvE,kBAAkB,QAAQ,oBAAoBA,eAAc,oBAAoB;AAAA,UAChF,mBAAmB,QAAQ,qBAAqBA,eAAc,qBAAqB;AAAA,UACnF,kBAAkB,QAAQ,oBAAoB;AAAA,QAChD,CAAC;AACD;AAAA,MACF;AACA,YAAM,eAAe,uBAAuB,MAAM;AAClD,YAAM,gBAAgB,cAAc,MAAM,SAAS;AACnD,YAAM,YAAY,QAAQ,MAAM,SAAS;AACzC,YAAM,gBAAgB,QAAQ,iBAAiB,aAAa,kBAAkB,SAAS;AACvF,YAAM,wBAAwB,gBACzB,cAAc,qBAAqB,OACnC,QAAQ,qBAAqB,cAAc,qBAAqB;AAErE,6BAAuB,QAAQ;AAAA,QAC7B,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,QAClB,UAAU,QAAQ,YAAY;AAAA,QAC9B,MAAM,QAAQ,QAAQ;AAAA,QACtB,eAAe,QAAQ,iBAAiB;AAAA,QACxC,kBAAkB,QAAQ,oBAAoB;AAAA,QAC9C,mBAAmB;AAAA,QACnB,kBAAkB,QAAQ,oBAAoB;AAAA,MAChD,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,OAAO,YAAY,MAAM;AACxC,WAAK,gBAAgB;AAAA,IACvB,GAAG,GAAI;AAEP,WAAO,MAAM;AACX,kBAAY;AACZ,aAAO,cAAc,QAAQ;AAAA,IAC/B;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,6BAA8B;AACnC,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,cAAc,CAAC,OAAO,KAAM;AAChE,QAAI,YAAY;AAChB,UAAM,4BAA4B,YAAY;AAC5C,oCAA8B;AAC9B,sCAAgC,KAAK;AACrC,YAAM,mBAAmB,CAAC,GAAG,OAAO;AACpC,UAAI,UAAW;AAAA,IACjB;AACA,SAAK,0BAA0B;AAC/B,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,SAAS,mBAAmB,8BAA8B,OAAO,MAAM,OAAO,YAAY,OAAO,cAAc,CAAC,CAAC;AAErH,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QACE,OAAO,gBACJ,OAAO,cACP,OAAO,MAAM,MAAM,UAAU,YAC7B,MAAM,KAAK,MAAM,KAAK,EAAE,SAAS,GACpC;AACA,wBAAkB,UAAU;AAAA,QAC1B,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,QAClB,OAAO,MAAM,KAAK;AAAA,MACpB;AACA;AAAA,IACF;AACA,sBAAkB,UAAU;AAAA,EAC9B,GAAG,CAAC,mBAAmB,OAAO,MAAM,OAAO,OAAO,YAAY,OAAO,YAAY,CAAC;AAElF,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,UAAM,aAAa,MAAM;AACvB,YAAM,UAAU,kBAAkB;AAClC,UAAI,CAAC,QAAS;AACd,+BAAyB;AAAA,QACvB,GAAG;AAAA,QACH,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,WAAO,iBAAiB,YAAY,UAAU;AAC9C,WAAO,MAAM;AACX,aAAO,oBAAoB,YAAY,UAAU;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,iBAAiB,CAAC;AAEtB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,WAAO,MAAM;AACX,YAAM,UAAU,kBAAkB;AAClC,UAAI,SAAS;AACX,aAAK,YAAY;AAAA,UACf,GAAG;AAAA,UACH,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AACA,+BAAyB,MAAM;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAE9B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,UAAM,kBAAkB,CAAC,UAAiB;AACxC,YAAM,uBAAuB,CAACC,aAIxB;AACJ,gCAAwB,IAAI;AAC5B,cAAM,YAA0C;AAAA,UAC9C,UAAUA,SAAQ;AAAA,UAClB,mBAAmBA,SAAQ,SAAS;AAAA,UACpC,mBAAmB;AAAA,UACnB,wBAAwB;AAAA,QAC1B;AACA,YAAIA,SAAQ,SAAS,QAAW;AAC9B,oBAAU,OAAOA,SAAQ;AAAA,QAC3B;AACA,YAAIA,SAAQ,sBAAsB,QAAW;AAC3C,oBAAU,oBAAoBA,SAAQ;AAAA,QACxC;AACA,+BAAuB,QAAQ;AAAA,UAC7B,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAEA,YAAM,wBAAwB,CAACD,kBAAsC;AACnE,cAAM,sBAAsB,OAAOA,cAAa,iBAAiB,IAC7DA,cAAa,oBACb,OAAOA,cAAa,UAAU,EAAE,IAC9BA,cAAa,SAAS,KACtB;AACN,eAAQ;AAAA,UACN,UAAU;AAAA,YACR,IAAI;AAAA,YACJ,cAAcA,cAAa,gBAAgB;AAAA,YAC3C,YAAYA,cAAa,cAAc;AAAA,YACzC,iBAAiBA,cAAa,qBAAqB;AAAA,YACnD,qBAAqB;AAAA,YACrB,uBAAuB;AAAA,YACvB,qBAAqB;AAAA,YACrB,mBAAmB,CAAC;AAAA,YACpB,SAAS,CAAC;AAAA,UACZ;AAAA,UACA,MAAMA,cAAa,QAAQ;AAAA,UAC3B,mBAAmBA,cAAa,qBAAqB;AAAA,QACvD;AAAA,MACA;AAEA,YAAM,SAAU,MAAgD;AAChE,UAAI,CAAC,OAAQ;AACb,YAAM,iBAAiB,OAAO,aAAa,OAAO;AAClD,UAAI,UAAU,iCAAiC,OAAO,KAAK;AAC3D,YAAM,eAAe,uBAAuB,MAAM;AAClD,YAAM,0BAA0B,CAAC,kBAAkB,mBAAmB;AACtE,UAAI,CAAC,yBAAyB;AAC5B,YAAI,CAAC,WAAW,CAAC,cAAc,gBAAgB,CAAC,cAAc,WAAY;AAC1E,cAAM,sBAAsB,QAAQ,SAAS,cAAc,KAAK,KAAK;AACrE,cAAM,oBAAoB,QAAQ,SAAS,YAAY,KAAK,KAAK;AACjE,YAAI,CAAC,uBAAuB,CAAC,kBAAmB;AAChD,YAAI,wBAAwB,aAAa,gBAAgB,sBAAsB,aAAa,WAAY;AAAA,MAC1G;AACE,UAAI,CAAC,SAAS;AACd,YAAI,CAAC,cAAc,gBAAgB,CAAC,cAAc,WAAY;AAC9D,YAAI,qBAAqB,OAAO,KAAK,GAAG;AACtC,kCAAwB,IAAI;AAC5B,iCAAuB,QAAQ;AAAA,YAC7B,eAAe;AAAA,YACf,UAAU;AAAA,YACV,MAAM;AAAA,YACN,UAAU;AAAA,YACV,mBAAmB;AAAA,YACnB,mBAAmB;AAAA,YACnB,wBAAwB;AAAA,UAC1B,CAAC;AACD;AAAA,QACF;AACA,YAAI,mBAAmB,OAAO,KAAK,MAAM,KAAK;AAC5C,+BAAqB,sBAAsB,YAAY,CAAC;AAAA,QAC1D;AACA;AAAA,MACF;AAEA,2BAAqB,OAAO;AAAA,IAC9B;AAEA,WAAO,iBAAiB,8BAA8B,eAAe;AACrE,WAAO,iBAAiB,sBAAsB,eAAe;AAC7D,WAAO,MAAM;AACX,aAAO,oBAAoB,8BAA8B,eAAe;AACxE,aAAO,oBAAoB,sBAAsB,eAAe;AAAA,IAClE;AAAA,EACF,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAE9B,QAAM,iBAAiB,MAAM,YAAY,YAAY;AACnD,4BAAwB,WAAW;AACnC,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAY;AAChD,UAAM,OAAO,MAAM,QAAyB,mCAAmC;AAAA,MAC7E,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,MACpB,CAAC;AAAA,IACH,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,EAAE,4CAA4C,8BAA8B,GAAG,OAAO;AAC5F;AAAA,IACF;AACA,UAAM,UAAU,MAAM,QAAyB,6BAA6B;AAAA,MAC1E,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,cAAc,MAAM,cAAc,YAAY,MAAM,WAAW,CAAC;AAAA,IACzF,CAAC;AACD,QAAI,CAAC,QAAQ,IAAI;AACf,YAAM,EAAE,4CAA4C,8BAA8B,GAAG,OAAO;AAC5F;AAAA,IACF;AACA,UAAM,UAAU,QAAQ,UAAU,CAAC;AACnC,2BAAuB,QAAQ;AAAA,MAC7B,UAAU,QAAQ,YAAY;AAAA,MAC9B,MAAM,QAAQ,QAAQ;AAAA,MACtB,eAAe,QAAQ,iBAAiB;AAAA,MACxC,kBAAkB,QAAQ,oBAAoB;AAAA,MAC9C,mBAAmB,QAAQ,qBAAqB;AAAA,MAChD,kBAAkB,QAAQ,oBAAoB;AAAA,MAC9C,UAAU;AAAA,MACV,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,MACnB,wBAAwB;AAAA,IAC1B,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,OAAO,YAAY,OAAO,cAAc,CAAC,CAAC;AAEtD,QAAM,uBAAuB,MAAM,YAAY,YAAY;AACzD,4BAAwB,WAAW;AACnC,QAAI,CAAC,OAAO,YAAY,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAY;AACpE,QAAI,aAAiC,OAAO,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS,KAAK;AACrF,QAAI,CAAC,YAAY;AACf,YAAM,aAAa,MAAM,mBAAmB,CAAC,GAAG,OAAO;AACvD,mBAAa,OAAO,WAAW,UAAU,EAAE,IAAI,WAAW,SAAS,KAAK;AACxE,UAAI,CAAC,YAAY;AACf;AAAA,UACE;AAAA,YACE;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,eAAe,6BAA6B;AAAA,MAChD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,QAClB,OAAO,MAAM,MAAM,SAAS;AAAA,QAC5B,QAAQ;AAAA,QACR;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AACD,2BAAuB,QAAQ;AAAA,MAC7B,UAAU;AAAA,MACV,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,MACnB,wBAAwB;AAAA,IAC1B,CAAC;AACD,WAAO,SAAS,OAAO;AAAA,EACzB,GAAG,CAAC,SAAS,QAAQ,OAAO,UAAU,OAAO,MAAM,OAAO,OAAO,YAAY,OAAO,cAAc,CAAC,CAAC;AAEpG,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,QAAI,CAAC,OAAO,SAAU;AACtB,UAAM,kBAAkB,YAAY;AAClC,YAAM,uBAAuB,wBAAwB,UAAU;AAC/D,8BAAwB,UAAU;AAClC,UAAI,aAA4B,OAAO,MAAM,UAAU,EAAE,IAAI,MAAM,SAAS,KAAK;AACjF,UAAI,CAAC,YAAY;AACf,cAAM,aAAa,MAAM,mBAAmB,CAAC,GAAG,OAAO;AACvD,qBAAa,OAAO,WAAW,UAAU,EAAE,IAAI,WAAW,SAAS,KAAK;AAAA,MAC1E;AACA,UAAI,CAAC,YAAY;AACf;AAAA,UACE;AAAA,YACE;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AACA,6BAAuB,QAAQ;AAAA,QAC7B,mBAAmB;AAAA,QACnB,mBAAmB;AAAA,QACnB,wBAAwB;AAAA,MAC1B,CAAC;AACD,aAAO,WAAW,YAAY;AAC5B,YAAI,wBAAwB,YAAY,qBAAsB;AAC9D,cAAM,eAAe,uBAAuB,MAAM;AAClD,YAAI,cAAc,sBAAsB,cAAe;AACvD,cAAM,YAAY,eAAe,MAAM;AACvC,YAAI,UAAW;AACf,cAAM,UAAU,MAAM,QAAQ,QAAQ,QAAQ,oBAAoB,CAAC,EAAE,MAAM,MAAM,KAAK;AACtF,YAAI,CAAC,SAAS;AACZ;AAAA,YACE;AAAA,cACE;AAAA,cACA;AAAA,YACF;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,GAAG,CAAC;AAAA,IACN;AACA,SAAK,gBAAgB;AAAA,EACvB,GAAG,CAAC,SAAS,QAAQ,mBAAmB,QAAQ,OAAO,UAAU,CAAC,CAAC;AAEnE,QAAM,oBAAoB,MAAM,YAAY,MAAM;AAChD,4BAAwB,WAAW;AACnC,4BAAwB,KAAK;AAAA,EAC/B,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,EAAE,wBAAwB;AAC5C,QAAM,qBAAqB,MAAM;AAAA,IAC/B,OAAO,OAAO,UAAU,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,MACtD,OAAO,OAAO;AAAA,MACd,MAAM,OAAO;AAAA,MACb,IAAI,OAAO;AAAA,IACb,EAAE;AAAA,IACF,CAAC,OAAO,UAAU,OAAO;AAAA,EAC3B;AACA,QAAM,mBAAmB;AAAA,IACvB,OAAO,UAAU,yBACd,OAAO,UAAU,wBAAwB,QACzC,OAAO,UAAU,mBAAmB,SAAS,aAAa;AAAA,EAC/D;AACA,QAAM,kBAAkB,OAAO,kBAAkB;AACjD,QAAM,4BAA4B;AAAA,IAChC,OAAO,UAAU,yBACd,CAAC,OAAO,UAAU;AAAA,EACvB;AACA,QAAM,iBACJ,oBAAC,UAAO,MAAM,QAAQ,OAAO,YAAY,eAAe,KAAK,sBAAsB,cAAc,CAAC,SAAS;AACzG,QAAI,MAAM;AACR,8BAAwB,IAAI;AAC5B;AAAA,IACF;AACA,QAAI,iBAAiB;AACnB,8BAAwB,IAAI;AAC5B;AAAA,IACF;AACA,4BAAwB,KAAK;AAAA,EAC/B,GACE,+BAAC,iBACC;AAAA,wBAAC,gBACC,8BAAC,eACE,4BACG,EAAE,8CAA8C,oBAAoB,IACpE,EAAE,+BAA+B,mBAAmB,GAC1D,GACF;AAAA,IACA,qBAAC,SAAI,WAAU,qBACb;AAAA,0BAAC,OAAE,WAAU,yBACV,4BACG;AAAA,QACA;AAAA,QACA;AAAA,MACF,IACE,EAAE,qCAAqC,mEAAmE,GAChH;AAAA,MACC,CAAC,kBACF;AAAA,QAAC;AAAA;AAAA,UACC,YAAY;AAAA,UACZ;AAAA,UACA;AAAA,UACA,aAAa,EAAE,wCAAwC,UAAU;AAAA,UACjE,YAAY,EAAE,uCAAuC,SAAS;AAAA;AAAA,MAChE,IACI;AAAA,OACF,OAAO,UAAU,SAAS,UAAU,OAAO,IAC3C,CAAC,kBACD,oBAAC,UAAO,SAAO,MAAC,SAAQ,QACrB;AAAA,QACC;AAAA,QACA;AAAA,MACF,GACF,IACI,OACF;AAAA,MACH,4BACC,oBAAC,UAAO,SAAO,MAAC,SAAQ,WACrB;AAAA,QACC;AAAA,QACA;AAAA,MACF,GACF,IACE;AAAA,MACJ,oBAAC,SAAI,WAAU,kHACb,8BAAC,SAAI,WAAU,oCACd,4BAAkB,OACjB,iCACF;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,CAAC,UAAU;AAClB,oBAAM,eAAe;AACrB,oBAAM,gBAAgB;AACtB,mBAAK,qBAAqB;AAAA,YAC5B;AAAA,YAEC,YAAE,yCAAyC,iBAAiB;AAAA;AAAA,QAC/D;AAAA,QACC,mBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,CAAC,UAAU;AAClB,oBAAM,eAAe;AACrB,oBAAM,gBAAgB;AACtB,mBAAK,eAAe;AAAA,YACtB;AAAA,YAEC,YAAE,qCAAqC,iBAAiB;AAAA;AAAA,QAC3D,IACE;AAAA,QACJ;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,CAAC,UAAU;AAClB,oBAAM,eAAe;AACrB,oBAAM,gBAAgB;AACtB,gCAAkB;AAAA,YACpB;AAAA,YAEC,YAAE,sCAAsC,cAAc;AAAA;AAAA,QACzD;AAAA,SACE,GAEF,GACF;AAAA,OACF;AAAA,KACF,GACF;AAGF,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,WAAO,kBACJ,IAAI,CAAC,gBAAgB,YAAY,eAAe,KAAK,KAAK,EAAE,EAC5D,OAAO,CAAC,OAAO,OAAO,QAAQ,MAAM,SAAS,KAAK,IAAI,QAAQ,KAAK,MAAM,KAAK,EAC9E,MAAM,GAAG,CAAC;AAAA,EACf,GAAG,CAAC,iBAAiB,CAAC;AAEtB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,yBAA0B;AAC/B,QAAI,CAAC,KAAM;AACX,QAAI,yBAAyB,EAAG;AAChC,gCAA4B,KAAK;AAAA,EACnC,GAAG,CAAC,wBAAwB,MAAM,wBAAwB,CAAC;AAE3D,QAAM,kBAAkB,UAAU,SAAS,eAAe,gBAAgB,IAAI;AAE9E,MAAI,CAAC,kBAAmB,QAAO;AAC/B,MAAI,CAAC,OAAO,KAAM,QAAO;AAEzB,QAAM,yBAAyB,yBAAyB,IACpD,GAAG,sBAAsB,yCACzB;AACJ,QAAM,mBAAmB,CAAC,OACtB,EAAE,uCAAuC,kDAAkD,IAC3F,2BACE,EAAE,yCAAyC,wFAAwF,IACnI,EAAE,2CAA2C,8CAA8C;AACjG,QAAM,gBAAgB,OAAO,qBAAqB,YAAY,iBAAiB,KAAK,EAAE,SAAS,IAC3F,mBACA;AACJ,QAAM,2BAA2B;AAAA,IAC/B,4BACG,yBAAyB,KACzB,CAAC;AAAA,EACN;AAEA,QAAM,aAAa,2BACjB,qBAAC,SAAI,WAAU,6FACb;AAAA,wBAAC,SAAI,WAAU,eACZ,yBACH;AAAA,IACA,qBAAC,SAAI,WAAU,oEACb;AAAA,0BAAC,UAAM,aAAG,EAAE,0CAA0C,cAAc,CAAC,KAAK,sBAAsB,IAAG;AAAA,MAClG,kBAAkB,IAAI,CAAC,UACtB;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,gCAAC,QAAK,WAAU,WAAU;AAAA,YAC1B,oBAAC,UAAM,iBAAM;AAAA;AAAA;AAAA,QAJR;AAAA,MAKP,CACD;AAAA,OACH;AAAA,IACA,qBAAC,SAAI,WAAU,mBACd;AAAA,YAAM,oBAAoB,CAAC,OAC1B;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS;AAAA,UACT,WAAU;AAAA,UAET,YAAE,iCAAiC,mBAAmB;AAAA;AAAA,MACzD,IACE;AAAA,MACH,2BACC,oBAAC,SAAI,WAAU,QACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,4BAA4B,KAAK;AAAA,UAChD,WAAU;AAAA,UAET,YAAE,gBAAgB,OAAO;AAAA;AAAA,MAC5B,GACF,IACE;AAAA,OACJ;AAAA,KACF,IACE;AAEJ,SACE,iCACG;AAAA,iBAAc,kBAAkB,aAAa,YAAY,eAAe,IAAI,aAAc;AAAA,IAC1F;AAAA,KACH;AAEJ;AAEA,eAAsB,mBACpB,MACA,SAC2B;AAC3B,QAAM,sBAAsB,oBAAoB,OAAO;AACvD,QAAM,oBAAoB,kBAAkB,SAAS,IAAI;AACzD,QAAM,SAAS,cAAc,SAAS,qBAAqB,iBAAiB;AAC5E,QAAM,QAAQ,uBAAuB,MAAM;AAC3C,QAAM,eAAe,OAAO,gBAAgB;AAC5C,QAAM,aAAa,OAAO,cAAc;AACxC,MAAI,CAAC,gBAAgB,CAAC,YAAY;AAChC,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AACA,QAAM,gCAAgC;AAAA,IACpC,OAAO,2BAA2B,QAC/B,OAAO,OAAO,sBAAsB,YACpC,MAAM,sBAAsB;AAAA,EACjC;AACA,MAAI,OAAO,YAAY,CAAC,+BAA+B;AACrD,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM,MAAM,QAAQ;AAAA,MACpB,UAAU,MAAM;AAAA,MAChB,mBAAmB,MAAM,qBAAqB;AAAA,IAChD;AAAA,EACF;AACA,QAAM,wBAAwB,QAAQ,OAAO,UAAU,MAAM,OAAO,MAAM,SAAS,EAAE,CAAC;AACtF,QAAM,sBAAsB,OAAO,qBAAqB;AACxD,QAAM,aAAa,wBAAwB,YAAY,CAAC,wBACpD,WACA;AACJ,QAAM,gBAAgB,eAAe,YAAY,CAAC,wBAC9C,SACC,OAAO,qBAAqB,OAAO,UAAU,MAAM;AACxD,QAAM,aAAa,OAAO,aAAa,IAAI,gBAAgB;AAC3D,QAAM,OAAO,MAAM,QAA0B,8BAA8B;AAAA,IACzE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,OAAO,MAAM,SAAS;AAAA,MAC7B,WAAW,OAAO,qBAAqB,OAAO,MAAM,mBAAmB;AAAA,MACvE;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,IACnB,CAAC;AAAA,EACH,CAAC;AACD,QAAM,UAAU,KAAK,UAAU,EAAE,IAAI,MAAM;AAC3C,MAAI,QAAQ,IAAI;AACd,UAAM,iBAAiB,eAAe,WAAW,WAAW;AAC5D,UAAM,sCAAsC,mBAAmB;AAC/D,2BAAuB,QAAQ;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,mBAAmB,QAAQ,qBAAqB,OAAO,qBAAqB;AAAA,MAC5E,MAAM,QAAQ,QAAQ,OAAO,QAAQ;AAAA,MACrC,UAAU,sCAAuC,OAAO,YAAY,OAAQ;AAAA,MAC5E,mBAAmB,mBAAmB,WAAW,OAAQ,cAAc,OAAO,qBAAqB;AAAA,MACnG,mBAAmB;AAAA,MACnB,wBAAwB,mBAAmB,WAAW,QAAQ,QAAQ,OAAO,sBAAsB;AAAA,IACrG,CAAC;AACD,WAAO;AAAA,EACT;AACA,yBAAuB,QAAQ;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,MAAM,QAAQ,QAAQ,OAAO,QAAQ;AAAA,IACrC,UAAU,QAAQ,YAAY,OAAO,YAAY;AAAA,IACjD,mBAAmB,QAAQ,UAAU,MAAM,cAAc,OAAO,qBAAqB;AAAA,IACrF,mBAAmB,eAAe,WAAW,WAAW;AAAA,IACxD,wBAAwB,eAAe,WAAW,QAAQ,QAAQ,OAAO,sBAAsB;AAAA,EACjG,CAAC;AACD,SAAO;AACT;",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Notice } from '@open-mercato/ui/primitives/Notice'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { BACKEND_MUTATION_ERROR_EVENT } from '@open-mercato/ui/backend/injection/mutationEvents'\nimport { useSearchParams } from 'next/navigation'\nimport { Mail } from 'lucide-react'\nimport {\n RECORD_LOCKS_LOCK_CONTENDED_EVENT,\n RECORD_LOCKS_RECORD_DELETED_EVENT,\n} from '@open-mercato/enterprise/modules/record_locks/notifications.handlers'\nimport {\n ChangedFieldsTable,\n type ChangeRow,\n} from '@open-mercato/core/modules/audit_logs/lib/display-helpers'\nimport {\n clearRecordLockFormState,\n getRecordLockFormState,\n setRecordLockFormState,\n subscribeRecordLockFormState,\n type RecordLockFormState,\n type RecordLockUiConflict,\n type RecordLockUiView,\n} from '@open-mercato/enterprise/modules/record_locks/lib/clientLockStore'\n\ntype CrudInjectionContext = {\n formId?: string\n entityId?: string\n resourceKind?: string\n resourceId?: string\n recordId?: string\n path?: string\n query?: string\n kind?: string\n personId?: string\n companyId?: string\n dealId?: string\n retryLastMutation?: () => Promise<boolean | void> | boolean | void\n}\n\ntype RecordLockWidgetOwner = {\n instanceId: string\n priority: number\n}\n\nconst GLOBAL_RECORD_LOCK_OWNERS_KEY = '__openMercatoRecordLockWidgetOwners__'\n\nfunction getRecordLockOwnerMap(): Map<string, RecordLockWidgetOwner> {\n const store = globalThis as Record<string, unknown>\n const existing = store[GLOBAL_RECORD_LOCK_OWNERS_KEY]\n if (existing instanceof Map) return existing as Map<string, RecordLockWidgetOwner>\n const next = new Map<string, RecordLockWidgetOwner>()\n store[GLOBAL_RECORD_LOCK_OWNERS_KEY] = next\n return next\n}\n\ntype AcquireResponse = {\n ok?: boolean\n acquired?: boolean\n allowForceUnlock?: boolean\n heartbeatSeconds?: number\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n currentUserId?: string\n error?: string\n code?: string\n}\n\ntype ValidateResponse = {\n ok: boolean\n status?: number\n code?: string\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n conflict?: RecordLockUiConflict | null\n}\n\ntype CrudSaveErrorEventDetail = {\n contextId?: string\n formId?: string\n error?: unknown\n}\n\ntype RecordLockContendedEventDetail = {\n sourceEntityId?: string | null\n}\n\ntype RecordDeletedEventDetail = {\n resourceId?: string | null\n resourceKind?: string | null\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object'\n}\n\nfunction readStringOrNull(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction isUuid(value: string | null | undefined): value is string {\n if (typeof value !== 'string') return false\n const trimmed = value.trim()\n if (!trimmed) return false\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)\n}\n\nfunction extractErrorStatus(error: unknown): number | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = current.status\n if (typeof status === 'number' && Number.isFinite(status)) return status\n if (typeof status === 'string') {\n const parsed = Number(status)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const statusCode = current.statusCode\n if (typeof statusCode === 'number' && Number.isFinite(statusCode)) return statusCode\n if (typeof statusCode === 'string') {\n const parsed = Number(statusCode)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return null\n}\n\nfunction extractRecordLockConflictPayload(error: unknown): {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n} | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const nested = ['body', 'response', 'data', 'details', 'error']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n\n const code = typeof current.code === 'string' ? current.code : null\n const status = extractErrorStatus(current)\n const hasLockMarkers = (\n isObjectRecord(current.lock)\n || isObjectRecord(current.conflict)\n || typeof current.conflictId === 'string'\n || typeof current.resourceKind === 'string'\n || typeof current.resourceId === 'string'\n || Array.isArray(current.resolutionOptions)\n || typeof current.allowIncomingOverride === 'boolean'\n || typeof current.canOverrideIncoming === 'boolean'\n )\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n const looksLikeLockConflictMessage = (\n message.includes('record conflict')\n || message.includes('record_lock_conflict')\n || message.includes('conflict detected')\n )\n const isRecordLockConflict = (\n code === 'record_lock_conflict'\n || (status === 409 && (hasLockMarkers || looksLikeLockConflictMessage))\n )\n if (!isRecordLockConflict) continue\n if (!isObjectRecord(current.conflict)) {\n const lock = isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined\n const fallbackConflictId = typeof current.conflictId === 'string' && isUuid(current.conflictId)\n ? current.conflictId\n : 'unresolved'\n const fallbackConflict: RecordLockUiConflict = {\n id: fallbackConflictId,\n resourceKind:\n (typeof current.resourceKind === 'string' && current.resourceKind.trim().length > 0\n ? current.resourceKind\n : lock?.resourceKind) ?? '',\n resourceId:\n (typeof current.resourceId === 'string' && current.resourceId.trim().length > 0\n ? current.resourceId\n : lock?.resourceId) ?? '',\n baseActionLogId:\n typeof current.baseActionLogId === 'string' || current.baseActionLogId === null\n ? current.baseActionLogId\n : lock?.baseActionLogId ?? null,\n incomingActionLogId:\n typeof current.incomingActionLogId === 'string' || current.incomingActionLogId === null\n ? current.incomingActionLogId\n : null,\n allowIncomingOverride: Boolean(current.allowIncomingOverride),\n canOverrideIncoming: Boolean(current.canOverrideIncoming),\n resolutionOptions: Array.isArray(current.resolutionOptions) && current.resolutionOptions.includes('accept_mine')\n ? ['accept_mine']\n : [],\n changes: [],\n }\n return {\n conflict: fallbackConflict,\n lock,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n return {\n conflict: current.conflict as RecordLockUiConflict,\n lock: isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n\n return null\n}\n\nfunction isRecordDeletedError(error: unknown): boolean {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = extractErrorStatus(current)\n const code = typeof current.code === 'string' ? current.code.toLowerCase() : ''\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n\n const matchesCode = (\n code === 'record_not_found'\n || code === 'not_found'\n || code === 'record_deleted'\n )\n const matchesMessage = (\n message.includes('not found')\n || message.includes('was deleted')\n || message.includes('record deleted')\n )\n\n if (status === 404 || matchesCode || matchesMessage) {\n return true\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return false\n}\n\nfunction clearIncomingChangesQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showIncomingChanges') !== '1') return\n url.searchParams.delete('showIncomingChanges')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction clearLockContentionQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showLockContention') !== '1') return\n url.searchParams.delete('showLockContention')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction submitCrudForm(formId: string): boolean {\n if (typeof document === 'undefined') return false\n const form = document.getElementById(formId)\n if (!(form instanceof HTMLFormElement)) return false\n form.requestSubmit()\n return true\n}\n\nfunction resolveResourceKind(context: CrudInjectionContext): string | null {\n if (context.resourceKind && context.resourceKind.trim()) return context.resourceKind\n if (context.kind === 'order') return 'sales.order'\n if (context.kind === 'quote') return 'sales.quote'\n if (context.personId) return 'customers.person'\n if (context.companyId) return 'customers.company'\n if (context.dealId) return 'customers.deal'\n const entityId = context.entityId\n if (entityId && entityId.includes(':')) {\n const [moduleId, rawEntity] = entityId.split(':')\n const entity = rawEntity ?? ''\n const normalizedModuleId = moduleId.trim()\n const normalizedEntity = entity.trim()\n if (normalizedModuleId && normalizedEntity) {\n const singularModuleId = normalizedModuleId.endsWith('s')\n ? normalizedModuleId.slice(0, -1)\n : normalizedModuleId\n\n const stripPrefixes = [\n `${normalizedModuleId}_`,\n `${singularModuleId}_`,\n ]\n\n let finalEntity = normalizedEntity\n for (const prefix of stripPrefixes) {\n if (finalEntity.startsWith(prefix)) {\n finalEntity = finalEntity.slice(prefix.length)\n break\n }\n }\n\n if (finalEntity) return `${normalizedModuleId}.${finalEntity}`\n }\n }\n\n const path = context.path ?? ''\n if (path.startsWith('/backend/customers/people/')) return 'customers.person'\n if (path.startsWith('/backend/customers/companies/')) return 'customers.company'\n if (path.startsWith('/backend/customers/deals/')) return 'customers.deal'\n if (path.startsWith('/backend/sales/orders/')) return 'sales.order'\n if (path.startsWith('/backend/sales/quotes/')) return 'sales.quote'\n if (path.startsWith('/backend/sales/documents/')) {\n const query = context.query ?? ''\n const params = new URLSearchParams(query)\n const kind = params.get('kind')\n if (kind === 'order') return 'sales.order'\n if (kind === 'quote') return 'sales.quote'\n }\n\n return null\n}\n\nfunction resolveResourceId(context: CrudInjectionContext, data: unknown): string | null {\n if (context.resourceId && context.resourceId.trim()) return context.resourceId\n if (context.recordId && context.recordId.trim()) return context.recordId\n if (context.personId && context.personId.trim()) return context.personId\n if (context.companyId && context.companyId.trim()) return context.companyId\n if (context.dealId && context.dealId.trim()) return context.dealId\n if (data && typeof data === 'object' && 'id' in data) {\n const id = (data as { id?: unknown }).id\n if (typeof id === 'string' && id.trim()) return id\n }\n if (data && typeof data === 'object') {\n const nestedPersonId = (data as { person?: { id?: unknown } }).person?.id\n if (typeof nestedPersonId === 'string' && nestedPersonId.trim()) return nestedPersonId\n const nestedCompanyId = (data as { company?: { id?: unknown } }).company?.id\n if (typeof nestedCompanyId === 'string' && nestedCompanyId.trim()) return nestedCompanyId\n const nestedDealId = (data as { deal?: { id?: unknown } }).deal?.id\n if (typeof nestedDealId === 'string' && nestedDealId.trim()) return nestedDealId\n }\n const path = context.path ?? ''\n const parts = path.split('/').filter((part) => part.length > 0)\n const candidates = [\n ['backend', 'customers', 'people'],\n ['backend', 'customers', 'companies'],\n ['backend', 'customers', 'deals'],\n ['backend', 'sales', 'orders'],\n ['backend', 'sales', 'quotes'],\n ['backend', 'sales', 'documents'],\n ] as const\n for (const prefix of candidates) {\n const matchesPrefix = prefix.every((segment, index) => parts[index] === segment)\n if (!matchesPrefix || parts.length <= prefix.length) continue\n const rawId = parts[prefix.length] ?? ''\n if (!rawId) continue\n try {\n const decoded = decodeURIComponent(rawId).trim()\n if (decoded.length > 0) return decoded\n } catch {\n const trimmed = rawId.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n}\n\nfunction resolveFormId(\n context: CrudInjectionContext,\n resourceKind: string | null,\n resourceId: string | null,\n): string {\n if (context.formId && context.formId.trim().length > 0) return context.formId\n if (resourceKind && resourceId) return `record-lock:${resourceKind}:${resourceId}`\n if (context.path && context.path.trim().length > 0) {\n const query = context.query?.trim()\n return `record-lock:${context.path}${query ? `?${query}` : ''}`\n }\n return 'record-lock:global'\n}\n\nasync function releaseLock(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n await apiCall('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'cancelled',\n }),\n })\n}\n\nfunction releaseLockWithKeepalive(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n const payload = JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'unmount',\n })\n\n try {\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([payload], { type: 'application/json' })\n const sent = navigator.sendBeacon('/api/record_locks/release', blob)\n if (sent) return\n }\n } catch {\n // ignore and fallback to fetch\n }\n\n void fetch('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: payload,\n keepalive: true,\n credentials: 'include',\n }).catch((error) => {\n console.warn('[RecordLockingWidget] Failed to release lock with keepalive fallback', error)\n })\n}\n\nexport default function RecordLockingWidget({\n context,\n data,\n}: InjectionWidgetComponentProps<CrudInjectionContext, Record<string, unknown>>) {\n const t = useT()\n const searchParams = useSearchParams()\n const resourceKind = React.useMemo(() => resolveResourceKind(context), [context])\n const resourceId = React.useMemo(() => resolveResourceId(context, data), [context, data])\n const formId = React.useMemo(\n () => resolveFormId(context, resourceKind, resourceId),\n [context, resourceId, resourceKind],\n )\n const [, forceRender] = React.useReducer((value) => value + 1, 0)\n const state = getRecordLockFormState(formId)\n const [mounted, setMounted] = React.useState(false)\n const [showIncomingChangesRequested, setShowIncomingChangesRequested] = React.useState(false)\n const [showLockContentionBanner, setShowLockContentionBanner] = React.useState(false)\n const [isConflictDialogOpen, setIsConflictDialogOpen] = React.useState(false)\n const instanceId = React.useMemo(\n () =>\n `record-lock-widget:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`,\n [],\n )\n const ownerPriority = context.formId ? 2 : 1\n const ownerKey = resourceKind && resourceId ? `${resourceKind}:${resourceId}` : null\n const [isPrimaryInstance, setIsPrimaryInstance] = React.useState(true)\n const releasePayloadRef = React.useRef<{\n resourceKind: string\n resourceId: string\n token: string\n } | null>(null)\n const keepMineRetryVersionRef = React.useRef(0)\n\n React.useEffect(() => {\n if (!ownerKey) {\n setIsPrimaryInstance(true)\n return\n }\n\n const owners = getRecordLockOwnerMap()\n const notifyOwnersChanged = () => {\n if (typeof window === 'undefined') return\n window.dispatchEvent(\n new CustomEvent('om:record-lock-owner-changed', {\n detail: { ownerKey },\n }),\n )\n }\n\n const claimOwnership = () => {\n const current = owners.get(ownerKey)\n if (!current) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n if (current.instanceId === instanceId) {\n setIsPrimaryInstance(true)\n return\n }\n if (ownerPriority > current.priority) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n setIsPrimaryInstance(false)\n }\n\n claimOwnership()\n const onOwnersChanged = () => claimOwnership()\n if (typeof window !== 'undefined') {\n window.addEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n\n return () => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n const current = owners.get(ownerKey)\n if (current?.instanceId === instanceId) {\n owners.delete(ownerKey)\n notifyOwnersChanged()\n }\n }\n }, [instanceId, ownerKey, ownerPriority])\n\n React.useEffect(() => {\n if (isPrimaryInstance) return\n const current = getRecordLockFormState(formId)\n if (!current?.lock?.token || !current.resourceKind || !current.resourceId) {\n clearRecordLockFormState(formId)\n return\n }\n void releaseLock({\n resourceKind: current.resourceKind,\n resourceId: current.resourceId,\n token: current.lock.token,\n reason: 'cancelled',\n }).catch((error) => {\n console.warn('[RecordLockingWidget] Failed to release lock while demoting owner', error)\n })\n clearRecordLockFormState(formId)\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n React.useEffect(() => {\n const showIncomingChanges = searchParams?.get('showIncomingChanges') === '1'\n const showLockContention = searchParams?.get('showLockContention') === '1'\n\n if (showIncomingChanges) {\n setShowIncomingChangesRequested(true)\n clearIncomingChangesQueryFlag()\n }\n\n if (showLockContention) {\n setShowLockContentionBanner(true)\n clearLockContentionQueryFlag()\n }\n }, [searchParams])\n\n React.useEffect(() => subscribeRecordLockFormState(formId, () => forceRender()), [formId])\n\n React.useEffect(() => {\n if (!state?.conflict) {\n setIsConflictDialogOpen(false)\n return\n }\n setIsConflictDialogOpen(true)\n }, [\n state?.conflict?.id,\n state?.conflict?.incomingActionLogId,\n state?.conflict?.baseActionLogId,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n setRecordLockFormState(formId, { formId, resourceKind, resourceId })\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n let active = true\n const acquire = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind, resourceId }),\n })\n const payload = call.result ?? {}\n if (!active) return\n if (!call.ok) {\n const defaultMessage = call.status === 403\n ? t('api.errors.forbidden', 'Forbidden')\n : t('record_locks.errors.acquire_failed', 'Failed to load record lock status.')\n const message = typeof payload.error === 'string' && payload.error.trim().length\n ? payload.error\n : defaultMessage\n flash(message, 'error')\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n void acquire()\n return () => {\n active = false\n }\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n const mine = Boolean(state?.lock?.token)\n const participants = React.useMemo(() => {\n if (!state?.lock) return []\n const fromPayload = Array.isArray(state.lock.participants) ? state.lock.participants : []\n if (fromPayload.length) return fromPayload\n return [{\n userId: state.lock.lockedByUserId,\n lockedByName: state.lock.lockedByName,\n lockedByEmail: state.lock.lockedByEmail,\n lockedByIp: state.lock.lockedByIp,\n lockedAt: state.lock.lockedAt,\n lastHeartbeatAt: state.lock.lastHeartbeatAt,\n expiresAt: state.lock.expiresAt,\n }]\n }, [state?.lock])\n const activeParticipantCount = state?.lock?.activeParticipantCount ?? participants.length\n const otherParticipants = React.useMemo(() => {\n if (!state?.currentUserId) return participants\n return participants.filter((participant) => participant.userId !== state.currentUserId)\n }, [participants, state?.currentUserId])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!mine || !state?.lock?.id) return\n\n const onContention = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordLockContendedEventDetail)\n : null\n if (!detail) return\n if (detail.sourceEntityId !== state.lock?.id) return\n setShowLockContentionBanner(true)\n }\n\n window.addEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)\n }\n }, [isPrimaryInstance, mine, state?.lock?.id])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.resourceKind || !state?.resourceId) return\n if (state.recordDeleted === true) return\n const onRecordDeleted = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordDeletedEventDetail)\n : null\n if (!detail) return\n if (detail.resourceId !== state.resourceId) return\n const kind = readStringOrNull(detail.resourceKind)\n if (kind && kind !== state.resourceKind) return\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }\n\n window.addEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)\n }\n }, [formId, isPrimaryInstance, state?.recordDeleted, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.lock?.token || !state.resourceKind || !state.resourceId) return\n const heartbeatSeconds = (\n typeof state.heartbeatSeconds === 'number'\n && Number.isFinite(state.heartbeatSeconds)\n && state.heartbeatSeconds > 0\n )\n ? state.heartbeatSeconds\n : 10\n const intervalMs = Math.max(1_000, Math.round(heartbeatSeconds * 1000))\n const interval = window.setInterval(() => {\n void apiCall('/api/record_locks/heartbeat', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock?.token,\n }),\n })\n }, intervalMs)\n return () => window.clearInterval(interval)\n }, [isPrimaryInstance, state?.heartbeatSeconds, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const hasUnresolvedConflict = Boolean(state?.conflict)\n && !(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal'\n )\n if (hasUnresolvedConflict) return\n if (!state?.resourceKind || !state?.resourceId) return\n let cancelled = false\n const refreshPresence = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n const payload = call.result ?? {}\n if (cancelled) return\n if (!call.ok) {\n const currentState = getRecordLockFormState(formId)\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? currentState?.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? currentState?.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? currentState?.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n const currentState = getRecordLockFormState(formId)\n const previousToken = currentState?.lock?.token ?? null\n const nextToken = payload.lock?.token ?? null\n const isSameSession = Boolean(previousToken && nextToken && previousToken === nextToken)\n const nextLatestActionLogId = isSameSession\n ? (currentState?.latestActionLogId ?? null)\n : (payload.latestActionLogId ?? currentState?.latestActionLogId ?? null)\n\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: nextLatestActionLogId,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n\n const interval = window.setInterval(() => {\n void refreshPresence()\n }, 4000)\n\n return () => {\n cancelled = true\n window.clearInterval(interval)\n }\n }, [\n formId,\n isPrimaryInstance,\n state?.conflict,\n state?.pendingResolution,\n state?.pendingResolutionArmed,\n state?.resourceId,\n state?.resourceKind,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!showIncomingChangesRequested) return\n if (!state?.resourceKind || !state?.resourceId || !state?.lock) return\n let cancelled = false\n const openIncomingChangesDialog = async () => {\n clearIncomingChangesQueryFlag()\n setShowIncomingChangesRequested(false)\n await validateBeforeSave({}, context)\n if (cancelled) return\n }\n void openIncomingChangesDialog()\n return () => {\n cancelled = true\n }\n }, [context, isPrimaryInstance, showIncomingChangesRequested, state?.lock, state?.resourceId, state?.resourceKind, t])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (\n state?.resourceKind\n && state?.resourceId\n && typeof state.lock?.token === 'string'\n && state.lock.token.trim().length > 0\n ) {\n releasePayloadRef.current = {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock.token,\n }\n return\n }\n releasePayloadRef.current = null\n }, [isPrimaryInstance, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onPageHide = () => {\n const payload = releasePayloadRef.current\n if (!payload) return\n releaseLockWithKeepalive({\n ...payload,\n reason: 'unmount',\n })\n }\n window.addEventListener('pagehide', onPageHide)\n return () => {\n window.removeEventListener('pagehide', onPageHide)\n }\n }, [isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n return () => {\n const payload = releasePayloadRef.current\n if (payload) {\n void releaseLock({\n ...payload,\n reason: 'unmount',\n })\n }\n clearRecordLockFormState(formId)\n }\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onCrudSaveError = (event: Event) => {\n const applyConflictPayload = (payload: {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n }) => {\n setIsConflictDialogOpen(true)\n const nextPatch: Partial<RecordLockFormState> = {\n conflict: payload.conflict,\n pendingConflictId: payload.conflict.id,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n }\n if (payload.lock !== undefined) {\n nextPatch.lock = payload.lock\n }\n if (payload.latestActionLogId !== undefined) {\n nextPatch.latestActionLogId = payload.latestActionLogId\n }\n setRecordLockFormState(formId, {\n ...nextPatch,\n })\n }\n\n const buildFallbackConflict = (currentState: RecordLockFormState) => {\n const preferredConflictId = isUuid(currentState.pendingConflictId)\n ? currentState.pendingConflictId\n : isUuid(currentState.conflict?.id)\n ? currentState.conflict.id\n : 'unresolved'\n return ({\n conflict: {\n id: preferredConflictId,\n resourceKind: currentState.resourceKind ?? '',\n resourceId: currentState.resourceId ?? '',\n baseActionLogId: currentState.latestActionLogId ?? null,\n incomingActionLogId: null,\n allowIncomingOverride: false,\n canOverrideIncoming: false,\n resolutionOptions: [],\n changes: [],\n } as RecordLockUiConflict,\n lock: currentState.lock ?? undefined,\n latestActionLogId: currentState.latestActionLogId ?? null,\n })\n }\n\n const detail = (event as CustomEvent<CrudSaveErrorEventDetail>).detail\n if (!detail) return\n const eventContextId = detail.contextId ?? detail.formId\n let payload = extractRecordLockConflictPayload(detail.error)\n const currentState = getRecordLockFormState(formId)\n const eventTargetsCurrentForm = !eventContextId || eventContextId === formId\n if (!eventTargetsCurrentForm) {\n if (!payload || !currentState?.resourceKind || !currentState?.resourceId) return\n const payloadResourceKind = payload.conflict.resourceKind?.trim() ?? ''\n const payloadResourceId = payload.conflict.resourceId?.trim() ?? ''\n if (!payloadResourceKind || !payloadResourceId) return\n if (payloadResourceKind !== currentState.resourceKind || payloadResourceId !== currentState.resourceId) return\n }\n if (!payload) {\n if (!currentState?.resourceKind || !currentState?.resourceId) return\n if (isRecordDeletedError(detail.error)) {\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n return\n }\n if (extractErrorStatus(detail.error) === 409) {\n applyConflictPayload(buildFallbackConflict(currentState))\n }\n return\n }\n\n applyConflictPayload(payload)\n }\n\n window.addEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.addEventListener('om:crud-save-error', onCrudSaveError)\n return () => {\n window.removeEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.removeEventListener('om:crud-save-error', onCrudSaveError)\n }\n }, [formId, isPrimaryInstance])\n\n const handleTakeOver = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n if (!state?.resourceKind || !state?.resourceId) return\n const call = await apiCall<AcquireResponse>('/api/record_locks/force-release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n if (!call.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const acquire = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind: state.resourceKind, resourceId: state.resourceId }),\n })\n if (!acquire.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const payload = acquire.result ?? {}\n setRecordLockFormState(formId, {\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n latestActionLogId: payload.latestActionLogId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }, [formId, state?.resourceId, state?.resourceKind, t])\n\n const handleAcceptIncoming = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n if (!state?.conflict || !state?.resourceKind || !state?.resourceId) return\n let conflictId: string | undefined = isUuid(state.conflict.id) ? state.conflict.id : undefined\n if (!conflictId) {\n const validation = await validateBeforeSave({}, context)\n conflictId = isUuid(validation.conflict?.id) ? validation.conflict.id : undefined\n if (!conflictId) {\n flash(\n t(\n 'record_locks.conflict.refresh_required',\n 'Could not confirm conflict details. Save again to refresh conflict data.',\n ),\n 'error',\n )\n return\n }\n }\n await apiCallOrThrow('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock?.token ?? undefined,\n reason: 'conflict_resolved',\n conflictId,\n resolution: 'accept_incoming',\n }),\n })\n setRecordLockFormState(formId, {\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n window.location.reload()\n }, [context, formId, state?.conflict, state?.lock?.token, state?.resourceId, state?.resourceKind, t])\n\n const handleKeepMine = React.useCallback(() => {\n if (!state?.conflict) return\n const applyAcceptMine = async () => {\n const keepMineRetryVersion = keepMineRetryVersionRef.current + 1\n keepMineRetryVersionRef.current = keepMineRetryVersion\n let conflictId: string | null = isUuid(state.conflict?.id) ? state.conflict.id : null\n if (!conflictId) {\n const validation = await validateBeforeSave({}, context)\n conflictId = isUuid(validation.conflict?.id) ? validation.conflict.id : null\n }\n if (!conflictId) {\n flash(\n t(\n 'record_locks.conflict.refresh_required',\n 'Could not confirm conflict details. Save again to refresh conflict data.',\n ),\n 'error',\n )\n return\n }\n setRecordLockFormState(formId, {\n pendingResolution: 'accept_mine',\n pendingConflictId: conflictId,\n pendingResolutionArmed: true,\n })\n window.setTimeout(async () => {\n if (keepMineRetryVersionRef.current !== keepMineRetryVersion) return\n const currentState = getRecordLockFormState(formId)\n if (currentState?.pendingResolution !== 'accept_mine') return\n const submitted = submitCrudForm(formId)\n if (submitted) return\n const retried = await Promise.resolve(context.retryLastMutation?.()).catch(() => false)\n if (!retried) {\n flash(\n t(\n 'record_locks.conflict.retry_save_after_accept_mine',\n 'Click save again to apply your changes.',\n ),\n 'info',\n )\n }\n }, 0)\n }\n void applyAcceptMine()\n }, [context, context.retryLastMutation, formId, state?.conflict, t])\n\n const handleKeepEditing = React.useCallback(() => {\n keepMineRetryVersionRef.current += 1\n setIsConflictDialogOpen(false)\n }, [])\n\n const noneLabel = t('audit_logs.common.none')\n const conflictChangeRows = React.useMemo<ChangeRow[]>(\n () => (state?.conflict?.changes ?? []).map((change) => ({\n field: change.field,\n from: change.incomingValue,\n to: change.mineValue,\n })),\n [state?.conflict?.changes],\n )\n const canKeepMyChanges = Boolean(\n state?.conflict?.allowIncomingOverride\n && state?.conflict?.canOverrideIncoming === true\n && state?.conflict?.resolutionOptions?.includes('accept_mine'),\n )\n const isRecordDeleted = state?.recordDeleted === true\n const showOverrideBlockedNotice = Boolean(\n state?.conflict?.allowIncomingOverride\n && !state?.conflict?.canOverrideIncoming,\n )\n const conflictDialog = (\n <Dialog open={Boolean(state?.conflict || isRecordDeleted) && isConflictDialogOpen} onOpenChange={(open) => {\n if (open) {\n setIsConflictDialogOpen(true)\n return\n }\n if (isRecordDeleted) {\n setIsConflictDialogOpen(true)\n return\n }\n setIsConflictDialogOpen(false)\n }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {isRecordDeleted\n ? t('record_locks.conflict.record_deleted_title', 'Record was deleted')\n : t('record_locks.conflict.title', 'Conflict detected')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-3 text-sm\">\n <p className=\"text-muted-foreground\">\n {isRecordDeleted\n ? t(\n 'record_locks.conflict.record_deleted_description',\n 'This record was deleted by another user while you were editing. Saving is blocked to avoid unexpected results.',\n )\n : t('record_locks.conflict.description', 'The record was changed by another user after you started editing.')}\n </p>\n {!isRecordDeleted ? (\n <ChangedFieldsTable\n changeRows={conflictChangeRows}\n noneLabel={noneLabel}\n t={t}\n beforeLabel={t('record_locks.conflict.incoming_label', 'Incoming')}\n afterLabel={t('record_locks.conflict.current_label', 'Current')}\n />\n ) : null}\n {(state?.conflict?.changes?.length ?? 0) === 0 ? (\n !isRecordDeleted ? (\n <Notice compact variant=\"info\">\n {t(\n 'record_locks.conflict.no_field_details',\n 'Field-level conflict details are unavailable for this record. Choose a resolution to continue.'\n )}\n </Notice>\n ) : null\n ) : null}\n {showOverrideBlockedNotice ? (\n <Notice compact variant=\"warning\">\n {t(\n 'record_locks.conflict.override_blocked_notice',\n 'You cannot keep your version because you do not have permission to override incoming changes.',\n )}\n </Notice>\n ) : null}\n <div className=\"-mx-6 -mb-6 mt-4 border-t bg-background/95 px-6 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80\">\n <div className=\"flex flex-wrap justify-end gap-2\">\n {isRecordDeleted ? null : (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleAcceptIncoming()\n }}\n >\n {t('record_locks.conflict.accept_incoming', 'Accept incoming')}\n </Button>\n {canKeepMyChanges ? (\n <Button\n type=\"button\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleKeepMine()\n }}\n >\n {t('record_locks.conflict.accept_mine', 'Keep my changes')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n handleKeepEditing()\n }}\n >\n {t('record_locks.conflict.keep_editing', 'Keep editing')}\n </Button>\n </>\n )}\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n\n const participantEmails = React.useMemo(() => {\n return otherParticipants\n .map((participant) => participant.lockedByEmail?.trim() ?? '')\n .filter((email, index, all) => email.length > 0 && all.indexOf(email) === index)\n .slice(0, 4)\n }, [otherParticipants])\n\n React.useEffect(() => {\n if (!showLockContentionBanner) return\n if (!mine) return\n if (activeParticipantCount > 1) return\n setShowLockContentionBanner(false)\n }, [activeParticipantCount, mine, showLockContentionBanner])\n\n const topBannerTarget = mounted ? document.getElementById('om-top-banners') : null\n\n if (!isPrimaryInstance) return null\n if (!state?.lock) return conflictDialog\n\n const defaultPresenceMessage = activeParticipantCount > 1\n ? `${activeParticipantCount} users are currently on this record.`\n : 'This record is currently locked by another user.'\n const bannerMessageRaw = !mine\n ? t('record_locks.banner.locked_by_other', 'This record is currently locked by another user.')\n : showLockContentionBanner\n ? t('record_locks.banner.contention_notice', 'Another user opened this record while you are editing it. Conflicts may occur on save.')\n : t('record_locks.banner.participants_notice', 'Multiple users are currently on this record.')\n const bannerMessage = typeof bannerMessageRaw === 'string' && bannerMessageRaw.trim().length > 0\n ? bannerMessageRaw\n : defaultPresenceMessage\n const shouldShowPresenceBanner = Boolean(\n showLockContentionBanner\n || activeParticipantCount > 1\n || !mine,\n )\n\n const lockBanner = shouldShowPresenceBanner ? (\n <div className=\"rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900 shadow-sm\">\n <div className=\"font-medium\">\n {bannerMessage}\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-amber-900/90\">\n <span>{`${t('record_locks.banner.active_users_label', 'Active users')}: ${activeParticipantCount}`}</span>\n {participantEmails.map((email) => (\n <span\n key={email}\n className=\"inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-100 px-2 py-0.5 text-[11px] font-medium text-amber-900\"\n >\n <Mail className=\"h-3 w-3\" />\n <span>{email}</span>\n </span>\n ))}\n </div>\n <div className=\"mt-2 flex gap-2\">\n {state.allowForceUnlock && !mine ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={handleTakeOver}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('record_locks.banner.take_over', 'Take over editing')}\n </Button>\n ) : null}\n {showLockContentionBanner ? (\n <div className=\"mt-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowLockContentionBanner(false)}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('common.close', 'Close')}\n </Button>\n </div>\n ) : null}\n </div>\n </div>\n ) : null\n\n return (\n <>\n {lockBanner ? (topBannerTarget ? createPortal(lockBanner, topBannerTarget) : lockBanner) : null}\n {conflictDialog}\n </>\n )\n}\n\nexport async function validateBeforeSave(\n data: Record<string, unknown>,\n context: CrudInjectionContext,\n): Promise<ValidateResponse> {\n const contextResourceKind = resolveResourceKind(context)\n const contextResourceId = resolveResourceId(context, data)\n const formId = resolveFormId(context, contextResourceKind, contextResourceId)\n const state = getRecordLockFormState(formId)\n const resourceKind = state?.resourceKind || contextResourceKind\n const resourceId = state?.resourceId || contextResourceId\n if (!resourceKind || !resourceId) {\n return { ok: true }\n }\n const hasSelectedConflictResolution = Boolean(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal',\n )\n if (state?.conflict && !hasSelectedConflictResolution) {\n return {\n ok: false,\n status: 409,\n code: 'record_lock_conflict',\n lock: state.lock ?? null,\n conflict: state.conflict,\n latestActionLogId: state.latestActionLogId ?? null,\n }\n }\n const hasResolvableConflict = Boolean(state?.conflict?.id && isUuid(state.conflict.id))\n const requestedResolution = state?.pendingResolution ?? 'normal'\n const resolution = requestedResolution !== 'normal' && !hasResolvableConflict\n ? 'normal'\n : requestedResolution\n const rawConflictId = resolution === 'normal' || !hasResolvableConflict\n ? undefined\n : (state?.pendingConflictId ?? state?.conflict?.id ?? undefined)\n const conflictId = isUuid(rawConflictId) ? rawConflictId : undefined\n const call = await apiCall<ValidateResponse>('/api/record_locks/validate', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind,\n resourceId,\n method: 'PUT',\n token: state?.lock?.token ?? undefined,\n baseLogId: state?.latestActionLogId ?? state?.lock?.baseActionLogId ?? undefined,\n conflictId,\n resolution,\n mutationPayload: data,\n }),\n })\n const payload = call.result ?? { ok: false }\n if (payload.ok) {\n const nextResolution = resolution === 'normal' ? 'normal' : resolution\n const preserveConflictUntilSuccessfulSave = nextResolution !== 'normal'\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n latestActionLogId: payload.latestActionLogId ?? state?.latestActionLogId ?? null,\n lock: payload.lock ?? state?.lock ?? null,\n conflict: preserveConflictUntilSuccessfulSave ? (state?.conflict ?? null) : null,\n pendingConflictId: nextResolution === 'normal' ? null : (conflictId ?? state?.pendingConflictId ?? null),\n pendingResolution: nextResolution,\n pendingResolutionArmed: nextResolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n }\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n lock: payload.lock ?? state?.lock ?? null,\n conflict: payload.conflict ?? state?.conflict ?? null,\n pendingConflictId: payload.conflict?.id ?? conflictId ?? state?.pendingConflictId ?? null,\n pendingResolution: resolution === 'normal' ? 'normal' : resolution,\n pendingResolutionArmed: resolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["currentState", "payload"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/enterprise",
|
|
3
|
-
"version": "0.4.6-develop-
|
|
3
|
+
"version": "0.4.6-develop-05b3bcb6f5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@open-mercato/core": "0.4.6-develop-
|
|
68
|
-
"@open-mercato/shared": "0.4.6-develop-
|
|
69
|
-
"@open-mercato/ui": "0.4.6-develop-
|
|
67
|
+
"@open-mercato/core": "0.4.6-develop-05b3bcb6f5",
|
|
68
|
+
"@open-mercato/shared": "0.4.6-develop-05b3bcb6f5",
|
|
69
|
+
"@open-mercato/ui": "0.4.6-develop-05b3bcb6f5"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/jest": "^30.0.0",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { NotificationHandler } from '@open-mercato/shared/modules/notifications/handler'
|
|
2
|
+
|
|
3
|
+
export const RECORD_LOCKS_LOCK_CONTENDED_EVENT = 'om:record_locks:lock-contended'
|
|
4
|
+
export const RECORD_LOCKS_RECORD_DELETED_EVENT = 'om:record_locks:record-deleted'
|
|
5
|
+
export const RECORD_LOCKS_INCOMING_CHANGES_EVENT = 'om:record_locks:incoming-changes'
|
|
6
|
+
export const RECORD_LOCKS_FORCE_RELEASED_EVENT = 'om:record_locks:force-released'
|
|
7
|
+
|
|
8
|
+
export const notificationHandlers: NotificationHandler[] = [
|
|
9
|
+
{
|
|
10
|
+
id: 'record_locks.lock-contended-event',
|
|
11
|
+
notificationType: 'record_locks.lock.contended',
|
|
12
|
+
features: ['record_locks.view'],
|
|
13
|
+
priority: 100,
|
|
14
|
+
handle(notification, context) {
|
|
15
|
+
context.emitEvent(RECORD_LOCKS_LOCK_CONTENDED_EVENT, {
|
|
16
|
+
notificationId: notification.id,
|
|
17
|
+
sourceEntityId: notification.sourceEntityId ?? null,
|
|
18
|
+
resourceKind: notification.bodyVariables?.resourceKind ?? null,
|
|
19
|
+
})
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'record_locks.record-deleted-event',
|
|
24
|
+
notificationType: 'record_locks.record.deleted',
|
|
25
|
+
features: ['record_locks.view'],
|
|
26
|
+
priority: 100,
|
|
27
|
+
handle(notification, context) {
|
|
28
|
+
context.emitEvent(RECORD_LOCKS_RECORD_DELETED_EVENT, {
|
|
29
|
+
notificationId: notification.id,
|
|
30
|
+
resourceId: notification.sourceEntityId ?? null,
|
|
31
|
+
resourceKind: notification.bodyVariables?.resourceKind ?? null,
|
|
32
|
+
})
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'record_locks.incoming-changes-event',
|
|
37
|
+
notificationType: 'record_locks.incoming_changes.available',
|
|
38
|
+
features: ['record_locks.view'],
|
|
39
|
+
priority: 90,
|
|
40
|
+
handle(notification, context) {
|
|
41
|
+
context.emitEvent(RECORD_LOCKS_INCOMING_CHANGES_EVENT, {
|
|
42
|
+
notificationId: notification.id,
|
|
43
|
+
sourceEntityId: notification.sourceEntityId ?? null,
|
|
44
|
+
resourceKind: notification.bodyVariables?.resourceKind ?? null,
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'record_locks.force-released-toast',
|
|
50
|
+
notificationType: 'record_locks.lock.force_released',
|
|
51
|
+
features: ['record_locks.view'],
|
|
52
|
+
priority: 110,
|
|
53
|
+
handle(notification, context) {
|
|
54
|
+
context.toast({
|
|
55
|
+
title: notification.title,
|
|
56
|
+
body: notification.body ?? undefined,
|
|
57
|
+
severity: 'warning',
|
|
58
|
+
})
|
|
59
|
+
context.emitEvent(RECORD_LOCKS_FORCE_RELEASED_EVENT, {
|
|
60
|
+
notificationId: notification.id,
|
|
61
|
+
sourceEntityId: notification.sourceEntityId ?? null,
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
export default notificationHandlers
|
|
@@ -12,6 +12,10 @@ import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules
|
|
|
12
12
|
import { BACKEND_MUTATION_ERROR_EVENT } from '@open-mercato/ui/backend/injection/mutationEvents'
|
|
13
13
|
import { useSearchParams } from 'next/navigation'
|
|
14
14
|
import { Mail } from 'lucide-react'
|
|
15
|
+
import {
|
|
16
|
+
RECORD_LOCKS_LOCK_CONTENDED_EVENT,
|
|
17
|
+
RECORD_LOCKS_RECORD_DELETED_EVENT,
|
|
18
|
+
} from '@open-mercato/enterprise/modules/record_locks/notifications.handlers'
|
|
15
19
|
import {
|
|
16
20
|
ChangedFieldsTable,
|
|
17
21
|
type ChangeRow,
|
|
@@ -84,10 +88,25 @@ type CrudSaveErrorEventDetail = {
|
|
|
84
88
|
error?: unknown
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
type RecordLockContendedEventDetail = {
|
|
92
|
+
sourceEntityId?: string | null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type RecordDeletedEventDetail = {
|
|
96
|
+
resourceId?: string | null
|
|
97
|
+
resourceKind?: string | null
|
|
98
|
+
}
|
|
99
|
+
|
|
87
100
|
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
88
101
|
return Boolean(value) && typeof value === 'object'
|
|
89
102
|
}
|
|
90
103
|
|
|
104
|
+
function readStringOrNull(value: unknown): string | null {
|
|
105
|
+
if (typeof value !== 'string') return null
|
|
106
|
+
const trimmed = value.trim()
|
|
107
|
+
return trimmed.length > 0 ? trimmed : null
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
function isUuid(value: string | null | undefined): value is string {
|
|
92
111
|
if (typeof value !== 'string') return false
|
|
93
112
|
const trimmed = value.trim()
|
|
@@ -685,28 +704,20 @@ export default function RecordLockingWidget({
|
|
|
685
704
|
React.useEffect(() => {
|
|
686
705
|
if (!isPrimaryInstance) return
|
|
687
706
|
if (!mine || !state?.lock?.id) return
|
|
688
|
-
let cancelled = false
|
|
689
707
|
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (hasUnreadContention) {
|
|
698
|
-
setShowLockContentionBanner(true)
|
|
699
|
-
}
|
|
708
|
+
const onContention = (event: Event) => {
|
|
709
|
+
const detail = isObjectRecord((event as CustomEvent<unknown>).detail)
|
|
710
|
+
? ((event as CustomEvent<unknown>).detail as RecordLockContendedEventDetail)
|
|
711
|
+
: null
|
|
712
|
+
if (!detail) return
|
|
713
|
+
if (detail.sourceEntityId !== state.lock?.id) return
|
|
714
|
+
setShowLockContentionBanner(true)
|
|
700
715
|
}
|
|
701
716
|
|
|
702
|
-
|
|
703
|
-
const interval = window.setInterval(() => {
|
|
704
|
-
void syncContentionBanner()
|
|
705
|
-
}, 5000)
|
|
717
|
+
window.addEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)
|
|
706
718
|
|
|
707
719
|
return () => {
|
|
708
|
-
|
|
709
|
-
window.clearInterval(interval)
|
|
720
|
+
window.removeEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)
|
|
710
721
|
}
|
|
711
722
|
}, [isPrimaryInstance, mine, state?.lock?.id])
|
|
712
723
|
|
|
@@ -714,27 +725,14 @@ export default function RecordLockingWidget({
|
|
|
714
725
|
if (!isPrimaryInstance) return
|
|
715
726
|
if (!state?.resourceKind || !state?.resourceId) return
|
|
716
727
|
if (state.recordDeleted === true) return
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
}>('/api/notifications?status=unread&type=record_locks.record.deleted&pageSize=20')
|
|
726
|
-
if (cancelled) return
|
|
727
|
-
const items = Array.isArray(call.result?.items) ? call.result.items : []
|
|
728
|
-
const hasUnreadRecordDeleted = items.some((item) => {
|
|
729
|
-
const matchesResourceId = item.sourceEntityId === state.resourceId
|
|
730
|
-
if (!matchesResourceId) return false
|
|
731
|
-
const kindFromBody = typeof item.bodyVariables?.resourceKind === 'string'
|
|
732
|
-
? item.bodyVariables.resourceKind.trim()
|
|
733
|
-
: ''
|
|
734
|
-
if (!kindFromBody) return true
|
|
735
|
-
return kindFromBody === state.resourceKind
|
|
736
|
-
})
|
|
737
|
-
if (!hasUnreadRecordDeleted) return
|
|
728
|
+
const onRecordDeleted = (event: Event) => {
|
|
729
|
+
const detail = isObjectRecord((event as CustomEvent<unknown>).detail)
|
|
730
|
+
? ((event as CustomEvent<unknown>).detail as RecordDeletedEventDetail)
|
|
731
|
+
: null
|
|
732
|
+
if (!detail) return
|
|
733
|
+
if (detail.resourceId !== state.resourceId) return
|
|
734
|
+
const kind = readStringOrNull(detail.resourceKind)
|
|
735
|
+
if (kind && kind !== state.resourceKind) return
|
|
738
736
|
setIsConflictDialogOpen(true)
|
|
739
737
|
setRecordLockFormState(formId, {
|
|
740
738
|
recordDeleted: true,
|
|
@@ -747,14 +745,10 @@ export default function RecordLockingWidget({
|
|
|
747
745
|
})
|
|
748
746
|
}
|
|
749
747
|
|
|
750
|
-
|
|
751
|
-
const interval = window.setInterval(() => {
|
|
752
|
-
void syncRecordDeletedState()
|
|
753
|
-
}, 5000)
|
|
748
|
+
window.addEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)
|
|
754
749
|
|
|
755
750
|
return () => {
|
|
756
|
-
|
|
757
|
-
window.clearInterval(interval)
|
|
751
|
+
window.removeEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)
|
|
758
752
|
}
|
|
759
753
|
}, [formId, isPrimaryInstance, state?.recordDeleted, state?.resourceId, state?.resourceKind])
|
|
760
754
|
|