@open-mercato/core 0.6.7-develop.6842.1.8cd8a8883c → 0.6.7-develop.6851.1.c1680b00d8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/entities/api/definitions.js +2 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +18 -3
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/components/RecordListRelationCell.js +63 -0
- package/dist/modules/entities/components/RecordListRelationCell.js.map +7 -0
- package/dist/modules/entities/components/recordListRelationDisplays.js +80 -0
- package/dist/modules/entities/components/recordListRelationDisplays.js.map +7 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +14 -1
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +22 -17
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
- package/dist/modules/query_index/api/reindex.js +10 -0
- package/dist/modules/query_index/api/reindex.js.map +2 -2
- package/dist/modules/query_index/cli.js +125 -1
- package/dist/modules/query_index/cli.js.map +2 -2
- package/dist/modules/query_index/lib/coverage.js +19 -7
- package/dist/modules/query_index/lib/coverage.js.map +2 -2
- package/dist/modules/query_index/lib/reindexer.js +43 -37
- package/dist/modules/query_index/lib/reindexer.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +27 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/data/validators.js +2 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/entities/api/definitions.ts +4 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +94 -77
- package/src/modules/entities/components/RecordListRelationCell.tsx +90 -0
- package/src/modules/entities/components/recordListRelationDisplays.ts +109 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +17 -1
- package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +21 -16
- package/src/modules/inbox_ops/components/proposals/types.ts +1 -0
- package/src/modules/query_index/api/reindex.ts +15 -0
- package/src/modules/query_index/cli.ts +180 -1
- package/src/modules/query_index/lib/coverage.ts +38 -8
- package/src/modules/query_index/lib/reindexer.ts +62 -40
- package/src/modules/sales/commands/documents.ts +49 -0
- package/src/modules/sales/data/validators.ts +2 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/entities/components/RecordListRelationCell.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport {\n getRecordListRelationDisplays,\n resolveRecordListRelationDisplays,\n type RelationDisplaysByField,\n} from './recordListRelationDisplays'\n\nconst RelationDisplaysContext = React.createContext<RelationDisplaysByField>({})\n\nexport function RelationDisplaysProvider({\n displaysByField,\n children,\n}: {\n displaysByField: RelationDisplaysByField\n children: React.ReactNode\n}) {\n return (\n <RelationDisplaysContext.Provider value={displaysByField}>\n {children}\n </RelationDisplaysContext.Provider>\n )\n}\n\nexport function useRecordListRelationDisplays(\n definitions: CustomFieldDefDto[],\n records: Array<Record<string, unknown>>,\n): RelationDisplaysByField {\n const [displaysByField, setDisplaysByField] = React.useState<RelationDisplaysByField>({})\n\n React.useEffect(() => {\n const abortController = new AbortController()\n setDisplaysByField((current) => (Object.keys(current).length ? {} : current))\n if (!definitions.length || !records.length) {\n return () => abortController.abort()\n }\n\n void resolveRecordListRelationDisplays(definitions, records, abortController.signal)\n .then((resolvedDisplays) => {\n if (!abortController.signal.aborted) setDisplaysByField(resolvedDisplays)\n })\n .catch(() => {\n if (!abortController.signal.aborted) setDisplaysByField({})\n })\n\n return () => abortController.abort()\n }, [definitions, records])\n\n return displaysByField\n}\n\nexport function RelationCell({\n fieldKey,\n value,\n fallbackText,\n}: {\n fieldKey: string\n value: unknown\n fallbackText: string\n}) {\n const displaysByField = React.useContext(RelationDisplaysContext)\n const resolvedDisplays = getRecordListRelationDisplays(value, displaysByField[fieldKey])\n const rawValue = resolvedDisplays.map((display) => display.id).join(', ') || fallbackText\n\n if (!resolvedDisplays.length) {\n return <span className=\"inline-block max-w-60 truncate align-top\" title={rawValue}>{rawValue}</span>\n }\n\n return (\n <span className=\"inline-block max-w-60 truncate align-top\" title={rawValue}>\n {resolvedDisplays.map((display, index) => (\n <React.Fragment key={`${display.id}-${index}`}>\n {index > 0 ? ', ' : null}\n {display.href ? (\n <Link\n href={display.href}\n className=\"font-medium text-primary underline-offset-2 hover:underline focus-visible:underline\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n >\n {display.label}\n </Link>\n ) : display.label}\n </React.Fragment>\n ))}\n </span>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAoBI,cAqDI,YArDJ;AAnBJ,YAAY,WAAW;AACvB,OAAO,UAAU;AAEjB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAEP,MAAM,0BAA0B,MAAM,cAAuC,CAAC,CAAC;AAExE,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,wBAAwB,UAAxB,EAAiC,OAAO,iBACtC,UACH;AAEJ;AAEO,SAAS,8BACd,aACA,SACyB;AACzB,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAkC,CAAC,CAAC;AAExF,QAAM,UAAU,MAAM;AACpB,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,uBAAmB,CAAC,YAAa,OAAO,KAAK,OAAO,EAAE,SAAS,CAAC,IAAI,OAAQ;AAC5E,QAAI,CAAC,YAAY,UAAU,CAAC,QAAQ,QAAQ;AAC1C,aAAO,MAAM,gBAAgB,MAAM;AAAA,IACrC;AAEA,SAAK,kCAAkC,aAAa,SAAS,gBAAgB,MAAM,EAChF,KAAK,CAAC,qBAAqB;AAC1B,UAAI,CAAC,gBAAgB,OAAO,QAAS,oBAAmB,gBAAgB;AAAA,IAC1E,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,gBAAgB,OAAO,QAAS,oBAAmB,CAAC,CAAC;AAAA,IAC5D,CAAC;AAEH,WAAO,MAAM,gBAAgB,MAAM;AAAA,EACrC,GAAG,CAAC,aAAa,OAAO,CAAC;AAEzB,SAAO;AACT;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,kBAAkB,MAAM,WAAW,uBAAuB;AAChE,QAAM,mBAAmB,8BAA8B,OAAO,gBAAgB,QAAQ,CAAC;AACvF,QAAM,WAAW,iBAAiB,IAAI,CAAC,YAAY,QAAQ,EAAE,EAAE,KAAK,IAAI,KAAK;AAE7E,MAAI,CAAC,iBAAiB,QAAQ;AAC5B,WAAO,oBAAC,UAAK,WAAU,4CAA2C,OAAO,UAAW,oBAAS;AAAA,EAC/F;AAEA,SACE,oBAAC,UAAK,WAAU,4CAA2C,OAAO,UAC/D,2BAAiB,IAAI,CAAC,SAAS,UAC9B,qBAAC,MAAM,UAAN,EACE;AAAA,YAAQ,IAAI,OAAO;AAAA,IACnB,QAAQ,OACP;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,QAAQ;AAAA,QACd,WAAU;AAAA,QACV,SAAS,CAAC,UAAU,MAAM,gBAAgB;AAAA,QAC1C,WAAW,CAAC,UAAU,MAAM,gBAAgB;AAAA,QAE3C,kBAAQ;AAAA;AAAA,IACX,IACE,QAAQ;AAAA,OAXO,GAAG,QAAQ,EAAE,IAAI,KAAK,EAY3C,CACD,GACH;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRelationHref,
|
|
3
|
+
collectRelationValueIds,
|
|
4
|
+
fetchRelationRecordDisplays,
|
|
5
|
+
parseRelationOptionsMetadata
|
|
6
|
+
} from "@open-mercato/ui/backend/utils/customFieldRelationDisplay";
|
|
7
|
+
function resolveRelationOptionsUrl(definition) {
|
|
8
|
+
if (definition.kind !== "relation") return null;
|
|
9
|
+
const configuredUrl = definition.optionsUrl?.trim();
|
|
10
|
+
if (configuredUrl && parseRelationOptionsMetadata(configuredUrl)) return configuredUrl;
|
|
11
|
+
const relatedEntityId = definition.relatedEntityId?.trim();
|
|
12
|
+
if (!relatedEntityId) return null;
|
|
13
|
+
return `/api/entities/relations/options?entityId=${encodeURIComponent(relatedEntityId)}`;
|
|
14
|
+
}
|
|
15
|
+
function collectRelationLookups(definitions, records) {
|
|
16
|
+
const lookups = /* @__PURE__ */ new Map();
|
|
17
|
+
definitions.forEach((definition) => {
|
|
18
|
+
const optionsUrl = resolveRelationOptionsUrl(definition);
|
|
19
|
+
if (!optionsUrl) return;
|
|
20
|
+
const relation = parseRelationOptionsMetadata(optionsUrl);
|
|
21
|
+
if (!relation) return;
|
|
22
|
+
const recordIds = new Set(
|
|
23
|
+
records.flatMap((record) => collectRelationValueIds(record[definition.key]))
|
|
24
|
+
);
|
|
25
|
+
if (!recordIds.size) return;
|
|
26
|
+
const lookupKey = JSON.stringify([relation.entityId, optionsUrl]);
|
|
27
|
+
const lookup = lookups.get(lookupKey) ?? {
|
|
28
|
+
optionsUrl,
|
|
29
|
+
relation,
|
|
30
|
+
recordIds: /* @__PURE__ */ new Set(),
|
|
31
|
+
recordIdsByField: /* @__PURE__ */ new Map()
|
|
32
|
+
};
|
|
33
|
+
recordIds.forEach((recordId) => lookup.recordIds.add(recordId));
|
|
34
|
+
lookup.recordIdsByField.set(definition.key, recordIds);
|
|
35
|
+
lookups.set(lookupKey, lookup);
|
|
36
|
+
});
|
|
37
|
+
return Array.from(lookups.values());
|
|
38
|
+
}
|
|
39
|
+
async function resolveRecordListRelationDisplays(definitions, records, signal) {
|
|
40
|
+
const displaysByField = {};
|
|
41
|
+
const lookups = collectRelationLookups(definitions, records);
|
|
42
|
+
await Promise.all(lookups.map(async (lookup) => {
|
|
43
|
+
let fetchedDisplays = {};
|
|
44
|
+
try {
|
|
45
|
+
fetchedDisplays = await fetchRelationRecordDisplays(
|
|
46
|
+
lookup.optionsUrl,
|
|
47
|
+
lookup.relation,
|
|
48
|
+
Array.from(lookup.recordIds),
|
|
49
|
+
signal
|
|
50
|
+
);
|
|
51
|
+
} catch {
|
|
52
|
+
fetchedDisplays = {};
|
|
53
|
+
}
|
|
54
|
+
if (signal?.aborted) return;
|
|
55
|
+
lookup.recordIdsByField.forEach((recordIds, fieldKey) => {
|
|
56
|
+
const fieldDisplays = {};
|
|
57
|
+
recordIds.forEach((recordId) => {
|
|
58
|
+
fieldDisplays[recordId] = fetchedDisplays[recordId] ?? {
|
|
59
|
+
label: recordId,
|
|
60
|
+
href: buildRelationHref(lookup.relation.entityId, recordId)
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
displaysByField[fieldKey] = fieldDisplays;
|
|
64
|
+
});
|
|
65
|
+
}));
|
|
66
|
+
return displaysByField;
|
|
67
|
+
}
|
|
68
|
+
function getRecordListRelationDisplays(value, displays) {
|
|
69
|
+
return collectRelationValueIds(value).map((recordId) => ({
|
|
70
|
+
id: recordId,
|
|
71
|
+
label: displays?.[recordId]?.label ?? recordId,
|
|
72
|
+
href: displays?.[recordId]?.href
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
getRecordListRelationDisplays,
|
|
77
|
+
resolveRecordListRelationDisplays,
|
|
78
|
+
resolveRelationOptionsUrl
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=recordListRelationDisplays.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/entities/components/recordListRelationDisplays.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport {\n buildRelationHref,\n collectRelationValueIds,\n fetchRelationRecordDisplays,\n parseRelationOptionsMetadata,\n type RelationOptionsMetadata,\n type ResolvedValueDisplay,\n} from '@open-mercato/ui/backend/utils/customFieldRelationDisplay'\n\nexport type RelationDisplaysByField = Record<string, Record<string, ResolvedValueDisplay>>\n\ntype RelationLookup = {\n optionsUrl: string\n relation: RelationOptionsMetadata\n recordIds: Set<string>\n recordIdsByField: Map<string, Set<string>>\n}\n\nexport function resolveRelationOptionsUrl(definition: CustomFieldDefDto): string | null {\n if (definition.kind !== 'relation') return null\n\n const configuredUrl = definition.optionsUrl?.trim()\n if (configuredUrl && parseRelationOptionsMetadata(configuredUrl)) return configuredUrl\n\n const relatedEntityId = definition.relatedEntityId?.trim()\n if (!relatedEntityId) return null\n return `/api/entities/relations/options?entityId=${encodeURIComponent(relatedEntityId)}`\n}\n\nfunction collectRelationLookups(\n definitions: CustomFieldDefDto[],\n records: Array<Record<string, unknown>>,\n): RelationLookup[] {\n const lookups = new Map<string, RelationLookup>()\n\n definitions.forEach((definition) => {\n const optionsUrl = resolveRelationOptionsUrl(definition)\n if (!optionsUrl) return\n const relation = parseRelationOptionsMetadata(optionsUrl)\n if (!relation) return\n\n const recordIds = new Set(\n records.flatMap((record) => collectRelationValueIds(record[definition.key])),\n )\n if (!recordIds.size) return\n\n const lookupKey = JSON.stringify([relation.entityId, optionsUrl])\n const lookup = lookups.get(lookupKey) ?? {\n optionsUrl,\n relation,\n recordIds: new Set<string>(),\n recordIdsByField: new Map<string, Set<string>>(),\n }\n recordIds.forEach((recordId) => lookup.recordIds.add(recordId))\n lookup.recordIdsByField.set(definition.key, recordIds)\n lookups.set(lookupKey, lookup)\n })\n\n return Array.from(lookups.values())\n}\n\nexport async function resolveRecordListRelationDisplays(\n definitions: CustomFieldDefDto[],\n records: Array<Record<string, unknown>>,\n signal?: AbortSignal,\n): Promise<RelationDisplaysByField> {\n const displaysByField: RelationDisplaysByField = {}\n const lookups = collectRelationLookups(definitions, records)\n\n await Promise.all(lookups.map(async (lookup) => {\n let fetchedDisplays: Record<string, ResolvedValueDisplay> = {}\n try {\n fetchedDisplays = await fetchRelationRecordDisplays(\n lookup.optionsUrl,\n lookup.relation,\n Array.from(lookup.recordIds),\n signal,\n )\n } catch {\n fetchedDisplays = {}\n }\n\n if (signal?.aborted) return\n lookup.recordIdsByField.forEach((recordIds, fieldKey) => {\n const fieldDisplays: Record<string, ResolvedValueDisplay> = {}\n recordIds.forEach((recordId) => {\n fieldDisplays[recordId] = fetchedDisplays[recordId] ?? {\n label: recordId,\n href: buildRelationHref(lookup.relation.entityId, recordId),\n }\n })\n displaysByField[fieldKey] = fieldDisplays\n })\n }))\n\n return displaysByField\n}\n\nexport function getRecordListRelationDisplays(\n value: unknown,\n displays: Record<string, ResolvedValueDisplay> | undefined,\n): Array<{ id: string; label: string; href?: string }> {\n return collectRelationValueIds(value).map((recordId) => ({\n id: recordId,\n label: displays?.[recordId]?.label ?? recordId,\n href: displays?.[recordId]?.href,\n }))\n}\n"],
|
|
5
|
+
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAWA,SAAS,0BAA0B,YAA8C;AACtF,MAAI,WAAW,SAAS,WAAY,QAAO;AAE3C,QAAM,gBAAgB,WAAW,YAAY,KAAK;AAClD,MAAI,iBAAiB,6BAA6B,aAAa,EAAG,QAAO;AAEzE,QAAM,kBAAkB,WAAW,iBAAiB,KAAK;AACzD,MAAI,CAAC,gBAAiB,QAAO;AAC7B,SAAO,4CAA4C,mBAAmB,eAAe,CAAC;AACxF;AAEA,SAAS,uBACP,aACA,SACkB;AAClB,QAAM,UAAU,oBAAI,IAA4B;AAEhD,cAAY,QAAQ,CAAC,eAAe;AAClC,UAAM,aAAa,0BAA0B,UAAU;AACvD,QAAI,CAAC,WAAY;AACjB,UAAM,WAAW,6BAA6B,UAAU;AACxD,QAAI,CAAC,SAAU;AAEf,UAAM,YAAY,IAAI;AAAA,MACpB,QAAQ,QAAQ,CAAC,WAAW,wBAAwB,OAAO,WAAW,GAAG,CAAC,CAAC;AAAA,IAC7E;AACA,QAAI,CAAC,UAAU,KAAM;AAErB,UAAM,YAAY,KAAK,UAAU,CAAC,SAAS,UAAU,UAAU,CAAC;AAChE,UAAM,SAAS,QAAQ,IAAI,SAAS,KAAK;AAAA,MACvC;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,IAAY;AAAA,MAC3B,kBAAkB,oBAAI,IAAyB;AAAA,IACjD;AACA,cAAU,QAAQ,CAAC,aAAa,OAAO,UAAU,IAAI,QAAQ,CAAC;AAC9D,WAAO,iBAAiB,IAAI,WAAW,KAAK,SAAS;AACrD,YAAQ,IAAI,WAAW,MAAM;AAAA,EAC/B,CAAC;AAED,SAAO,MAAM,KAAK,QAAQ,OAAO,CAAC;AACpC;AAEA,eAAsB,kCACpB,aACA,SACA,QACkC;AAClC,QAAM,kBAA2C,CAAC;AAClD,QAAM,UAAU,uBAAuB,aAAa,OAAO;AAE3D,QAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,WAAW;AAC9C,QAAI,kBAAwD,CAAC;AAC7D,QAAI;AACF,wBAAkB,MAAM;AAAA,QACtB,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM,KAAK,OAAO,SAAS;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,QAAQ;AACN,wBAAkB,CAAC;AAAA,IACrB;AAEA,QAAI,QAAQ,QAAS;AACrB,WAAO,iBAAiB,QAAQ,CAAC,WAAW,aAAa;AACvD,YAAM,gBAAsD,CAAC;AAC7D,gBAAU,QAAQ,CAAC,aAAa;AAC9B,sBAAc,QAAQ,IAAI,gBAAgB,QAAQ,KAAK;AAAA,UACrD,OAAO;AAAA,UACP,MAAM,kBAAkB,OAAO,SAAS,UAAU,QAAQ;AAAA,QAC5D;AAAA,MACF,CAAC;AACD,sBAAgB,QAAQ,IAAI;AAAA,IAC9B,CAAC;AAAA,EACH,CAAC,CAAC;AAEF,SAAO;AACT;AAEO,SAAS,8BACd,OACA,UACqD;AACrD,SAAO,wBAAwB,KAAK,EAAE,IAAI,CAAC,cAAc;AAAA,IACvD,IAAI;AAAA,IACJ,OAAO,WAAW,QAAQ,GAAG,SAAS;AAAA,IACtC,MAAM,WAAW,QAAQ,GAAG;AAAA,EAC9B,EAAE;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { runWithCacheTenant } from "@open-mercato/cache";
|
|
3
|
+
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
4
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
5
|
import {
|
|
4
6
|
runCrudMutationGuardAfterSuccess,
|
|
5
7
|
validateCrudMutationGuard
|
|
@@ -32,6 +34,17 @@ async function PATCH(req) {
|
|
|
32
34
|
if (action.status !== "pending" && action.status !== "failed") {
|
|
33
35
|
return NextResponse.json({ error: "Action already processed" }, { status: 409 });
|
|
34
36
|
}
|
|
37
|
+
try {
|
|
38
|
+
await enforceCommandOptimisticLockWithGuards(ctx.container, {
|
|
39
|
+
resourceKind: "inbox_ops.action",
|
|
40
|
+
resourceId: action.id,
|
|
41
|
+
current: action.updatedAt ?? null,
|
|
42
|
+
request: req
|
|
43
|
+
});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
35
48
|
const mergedPayload = { ...action.payload, ...parsed.data.payload };
|
|
36
49
|
const payloadValidation = validateActionPayloadForType(action.actionType, mergedPayload);
|
|
37
50
|
if (!payloadValidation.success) {
|
|
@@ -93,7 +106,7 @@ const openApi = {
|
|
|
93
106
|
responses: [
|
|
94
107
|
{ status: 200, description: "Action updated" },
|
|
95
108
|
{ status: 404, description: "Action not found" },
|
|
96
|
-
{ status: 409, description: "Action already processed" }
|
|
109
|
+
{ status: 409, description: "Action already processed or stale (optimistic-lock conflict)" }
|
|
97
110
|
]
|
|
98
111
|
}
|
|
99
112
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/inbox_ops/api/proposals/%5Bid%5D/actions/%5BactionId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { actionEditSchema, validateActionPayloadForType } from '../../../../../data/validators'\nimport { emitInboxOpsEvent } from '../../../../../events'\nimport { resolveCache, invalidateCountsCache } from '../../../../../lib/cache'\nimport {\n resolveRequestContext,\n resolveActionAndProposal,\n handleRouteError,\n isErrorResponse,\n} from '../../../../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'action-edit' })\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.manage'] },\n}\n\nexport async function PATCH(req: Request) {\n try {\n const body = await req.json()\n const parsed = actionEditSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.issues }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n const resolved = await resolveActionAndProposal(new URL(req.url), ctx)\n if (isErrorResponse(resolved)) return resolved\n\n const { action } = resolved\n\n if (action.status !== 'pending' && action.status !== 'failed') {\n return NextResponse.json({ error: 'Action already processed' }, { status: 409 })\n }\n\n const mergedPayload = { ...action.payload as Record<string, unknown>, ...parsed.data.payload }\n const payloadValidation = validateActionPayloadForType(action.actionType, mergedPayload)\n if (!payloadValidation.success) {\n return NextResponse.json({ error: payloadValidation.error }, { status: 400 })\n }\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n action.payload = mergedPayload\n await ctx.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const cache = resolveCache(ctx.container)\n await runWithCacheTenant(ctx.tenantId, () => invalidateCountsCache(cache, ctx.tenantId))\n\n try {\n await emitInboxOpsEvent('inbox_ops.action.edited', {\n actionId: action.id,\n proposalId: action.proposalId,\n actionType: action.actionType,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n } catch (eventError) {\n logger.error('Failed to emit event', { err: eventError })\n }\n\n return NextResponse.json({ ok: true, action })\n } catch (err) {\n return handleRouteError(err, 'edit action')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Edit action',\n methods: {\n PATCH: {\n summary: 'Edit action payload before accepting',\n responses: [\n { status: 200, description: 'Action updated' },\n { status: 404, description: 'Action not found' },\n { status: 409, description: 'Action already processed' },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { actionEditSchema, validateActionPayloadForType } from '../../../../../data/validators'\nimport { emitInboxOpsEvent } from '../../../../../events'\nimport { resolveCache, invalidateCountsCache } from '../../../../../lib/cache'\nimport {\n resolveRequestContext,\n resolveActionAndProposal,\n handleRouteError,\n isErrorResponse,\n} from '../../../../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'action-edit' })\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.manage'] },\n}\n\nexport async function PATCH(req: Request) {\n try {\n const body = await req.json()\n const parsed = actionEditSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.issues }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n const resolved = await resolveActionAndProposal(new URL(req.url), ctx)\n if (isErrorResponse(resolved)) return resolved\n\n const { action } = resolved\n\n if (action.status !== 'pending' && action.status !== 'failed') {\n return NextResponse.json({ error: 'Action already processed' }, { status: 409 })\n }\n\n // Optimistic lock: refuse a stale overwrite when two tabs edit the same action\n // payload. Strictly additive \u2014 a no-op without the expected-version header.\n try {\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'inbox_ops.action',\n resourceId: action.id,\n current: action.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const mergedPayload = { ...action.payload as Record<string, unknown>, ...parsed.data.payload }\n const payloadValidation = validateActionPayloadForType(action.actionType, mergedPayload)\n if (!payloadValidation.success) {\n return NextResponse.json({ error: payloadValidation.error }, { status: 400 })\n }\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n action.payload = mergedPayload\n await ctx.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const cache = resolveCache(ctx.container)\n await runWithCacheTenant(ctx.tenantId, () => invalidateCountsCache(cache, ctx.tenantId))\n\n try {\n await emitInboxOpsEvent('inbox_ops.action.edited', {\n actionId: action.id,\n proposalId: action.proposalId,\n actionType: action.actionType,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n } catch (eventError) {\n logger.error('Failed to emit event', { err: eventError })\n }\n\n return NextResponse.json({ ok: true, action })\n } catch (err) {\n return handleRouteError(err, 'edit action')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Edit action',\n methods: {\n PATCH: {\n summary: 'Edit action payload before accepting',\n responses: [\n { status: 200, description: 'Action updated' },\n { status: 404, description: 'Action not found' },\n { status: 409, description: 'Action already processed or stale (optimistic-lock conflict)' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
SelectTrigger,
|
|
10
10
|
SelectValue
|
|
11
11
|
} from "@open-mercato/ui/primitives/select";
|
|
12
|
-
import {
|
|
13
|
-
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
12
|
+
import { apiCallOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
13
|
+
import { buildOptimisticLockHeader, extractOptimisticLockConflict } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
14
14
|
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
15
15
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
16
16
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -426,25 +426,30 @@ function EditActionDialog({
|
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
setIsSaving(true);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (result?.ok && result.result?.ok) {
|
|
429
|
+
try {
|
|
430
|
+
await runMutation({
|
|
431
|
+
operation: () => withScopedApiRequestHeaders(
|
|
432
|
+
buildOptimisticLockHeader(action.updatedAt),
|
|
433
|
+
() => apiCallOrThrow(
|
|
434
|
+
`/api/inbox_ops/proposals/${action.proposalId}/actions/${action.id}`,
|
|
435
|
+
{ method: "PATCH", body: JSON.stringify({ payload: finalPayload }) }
|
|
436
|
+
)
|
|
437
|
+
),
|
|
438
|
+
context: {}
|
|
439
|
+
});
|
|
441
440
|
flash(t("inbox_ops.edit_dialog.saved", "Action updated successfully"), "success");
|
|
442
441
|
onSaved();
|
|
443
442
|
onClose();
|
|
444
|
-
}
|
|
445
|
-
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (extractOptimisticLockConflict(error)) {
|
|
445
|
+
onClose();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const message = error instanceof Error ? error.message.trim() : "";
|
|
449
|
+
flash(message || t("inbox_ops.flash.save_failed", "Failed to save"), "error");
|
|
450
|
+
} finally {
|
|
451
|
+
setIsSaving(false);
|
|
446
452
|
}
|
|
447
|
-
setIsSaving(false);
|
|
448
453
|
}, [action, payload, jsonMode, jsonText, t, onSaved, onClose, runMutation]);
|
|
449
454
|
React.useEffect(() => {
|
|
450
455
|
function handleKeyDown(event) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/inbox_ops/components/proposals/EditActionDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Loader2 } from 'lucide-react'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DialogDescription,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport type { ActionDetail } from './types'\nimport { useActionDescriptionResolver } from './ActionCard'\n\nfunction ShipmentPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n const trackingNumbers = ((payload.trackingNumbers as string[]) || []).join(', ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.carrier', 'Carrier')}</Label>\n <Input value={(payload.carrierName as string) || ''} onChange={(event) => updateField('carrierName', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.status', 'Status')}</Label>\n <Input value={(payload.statusLabel as string) || ''} onChange={(event) => updateField('statusLabel', event.target.value)} />\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.tracking_numbers', 'Tracking Numbers')}</Label>\n <Input\n value={trackingNumbers}\n onChange={(event) => updateField('trackingNumbers', event.target.value.split(',').map((s) => s.trim()).filter(Boolean))}\n placeholder={t('inbox_ops.placeholder.comma_separated', 'Comma-separated')}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.shipped_at', 'Shipped At')}</Label>\n <Input value={(payload.shippedAt as string) || ''} onChange={(event) => updateField('shippedAt', event.target.value)} placeholder=\"YYYY-MM-DD\" />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.estimated_delivery', 'Estimated Delivery')}</Label>\n <Input value={(payload.estimatedDelivery as string) || ''} onChange={(event) => updateField('estimatedDelivery', event.target.value)} placeholder=\"YYYY-MM-DD\" />\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.notes', 'Notes')}</Label>\n <Textarea value={(payload.notes as string) || ''} onChange={(event) => updateField('notes', event.target.value)} rows={2} />\n </div>\n </div>\n )\n}\n\nfunction ContactPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n const type = (payload.type as string) || 'person'\n const isPerson = type === 'person'\n\n const existingName = (payload.name as string) || ''\n const nameParts = existingName.trim().split(/\\s+/).filter((p) => p.length > 0)\n const [firstName, setFirstName] = React.useState(() => isPerson ? (nameParts[0] || '') : '')\n const [lastName, setLastName] = React.useState(() => isPerson ? (nameParts.slice(1).join(' ') || '') : '')\n\n const updatePersonName = React.useCallback((first: string, last: string) => {\n const combined = `${first} ${last}`.trim()\n updateField('name', combined)\n }, [updateField])\n\n const lastNameMissing = isPerson && !lastName.trim()\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n {isPerson ? (\n <>\n <div>\n <Label>{t('inbox_ops.contact.first_name', 'First Name')}</Label>\n <Input\n value={firstName}\n onChange={(event) => {\n setFirstName(event.target.value)\n updatePersonName(event.target.value, lastName)\n }}\n />\n </div>\n <div>\n <Label>{t('inbox_ops.contact.last_name', 'Last Name')}</Label>\n <Input\n value={lastName}\n onChange={(event) => {\n setLastName(event.target.value)\n updatePersonName(firstName, event.target.value)\n }}\n className={lastNameMissing ? 'border-status-error-border' : ''}\n />\n {lastNameMissing && (\n <p className=\"text-xs text-status-error-text mt-1\">{t('inbox_ops.contact.last_name_required', 'Last name is required')}</p>\n )}\n </div>\n </>\n ) : (\n <div>\n <Label>{t('inbox_ops.edit_dialog.name', 'Name')}</Label>\n <Input value={existingName} onChange={(event) => updateField('name', event.target.value)} />\n </div>\n )}\n <div>\n <Label>{t('inbox_ops.edit_dialog.type', 'Type')}</Label>\n <Select\n value={type}\n onValueChange={(value) => updateField('type', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"person\">{t('inbox_ops.contact_type.person', 'Person')}</SelectItem>\n <SelectItem value=\"company\">{t('inbox_ops.contact_type.company', 'Company')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.email', 'Email')}</Label>\n <Input value={(payload.email as string) || ''} onChange={(event) => updateField('email', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.phone', 'Phone')}</Label>\n <Input value={(payload.phone as string) || ''} onChange={(event) => updateField('phone', event.target.value)} />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.company_name', 'Company Name')}</Label>\n <Input value={(payload.companyName as string) || ''} onChange={(event) => updateField('companyName', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.role', 'Role')}</Label>\n <Input value={(payload.role as string) || ''} onChange={(event) => updateField('role', event.target.value)} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction LinkContactPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.email', 'Email')}</Label>\n <Input value={(payload.emailAddress as string) || ''} onChange={(event) => updateField('emailAddress', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_id', 'Contact ID')}</Label>\n <Input value={(payload.contactId as string) || ''} onChange={(event) => updateField('contactId', event.target.value)} />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_type', 'Contact Type')}</Label>\n <Select\n value={(payload.contactType as string) || 'person'}\n onValueChange={(value) => updateField('contactType', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"person\">{t('inbox_ops.contact_type.person', 'Person')}</SelectItem>\n <SelectItem value=\"company\">{t('inbox_ops.contact_type.company', 'Company')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_name', 'Contact Name')}</Label>\n <Input value={(payload.contactName as string) || ''} onChange={(event) => updateField('contactName', event.target.value)} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction DraftReplyPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.to', 'To')}</Label>\n <Input value={(payload.to as string) || ''} onChange={(event) => updateField('to', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.to_name', 'To Name')}</Label>\n <Input value={(payload.toName as string) || ''} onChange={(event) => updateField('toName', event.target.value)} />\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.subject', 'Subject')}</Label>\n <Input value={(payload.subject as string) || ''} onChange={(event) => updateField('subject', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.body', 'Body')}</Label>\n <Textarea value={(payload.body as string) || ''} onChange={(event) => updateField('body', event.target.value)} rows={6} />\n </div>\n </div>\n )\n}\n\nfunction UpdateOrderPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n const quantityChanges = (payload.quantityChanges as Record<string, unknown>[]) || []\n const deliveryDateChange = (payload.deliveryDateChange as Record<string, unknown>) || {}\n const noteAdditions = Array.isArray(payload.noteAdditions) ? (payload.noteAdditions as string[]).join('\\n') : ''\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.order_id', 'Order ID')}</Label>\n <Input value={(payload.orderId as string) || ''} onChange={(event) => updateField('orderId', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.order_number', 'Order Number')}</Label>\n <Input value={(payload.orderNumber as string) || ''} onChange={(event) => updateField('orderNumber', event.target.value)} />\n </div>\n </div>\n\n {quantityChanges.length > 0 && (\n <div>\n <Label className=\"mb-1 block\">{t('inbox_ops.edit_dialog.quantity_changes', 'Quantity Changes')}</Label>\n <div className=\"space-y-2\">\n {quantityChanges.map((change, index) => (\n <div key={index} className=\"border rounded p-2 space-y-2\">\n <div className=\"grid grid-cols-3 gap-2\">\n <div>\n <Label className=\"text-xs\">{t('inbox_ops.edit_dialog.line_item', 'Line Item')}</Label>\n <Input\n className=\"h-8 text-sm\"\n value={(change.lineItemName as string) || ''}\n onChange={(event) => {\n const arr = [...quantityChanges]\n arr[index] = { ...arr[index], lineItemName: event.target.value }\n updateField('quantityChanges', arr)\n }}\n />\n </div>\n <div>\n <Label className=\"text-xs\">{t('inbox_ops.edit_dialog.old_qty', 'Old Qty')}</Label>\n <Input\n className=\"h-8 text-sm\"\n value={(change.oldQuantity as string) || ''}\n onChange={(event) => {\n const arr = [...quantityChanges]\n arr[index] = { ...arr[index], oldQuantity: event.target.value }\n updateField('quantityChanges', arr)\n }}\n />\n </div>\n <div>\n <Label className=\"text-xs\">{t('inbox_ops.edit_dialog.new_qty', 'New Qty')}</Label>\n <Input\n className=\"h-8 text-sm\"\n value={(change.newQuantity as string) || ''}\n onChange={(event) => {\n const arr = [...quantityChanges]\n arr[index] = { ...arr[index], newQuantity: event.target.value }\n updateField('quantityChanges', arr)\n }}\n />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {Boolean(deliveryDateChange.oldDate || deliveryDateChange.newDate) && (\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.old_delivery_date', 'Old Delivery Date')}</Label>\n <Input\n value={(deliveryDateChange.oldDate as string) || ''}\n onChange={(event) => updateField('deliveryDateChange', { ...deliveryDateChange, oldDate: event.target.value })}\n placeholder=\"YYYY-MM-DD\"\n />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.new_delivery_date', 'New Delivery Date')}</Label>\n <Input\n value={(deliveryDateChange.newDate as string) || ''}\n onChange={(event) => updateField('deliveryDateChange', { ...deliveryDateChange, newDate: event.target.value })}\n placeholder=\"YYYY-MM-DD\"\n />\n </div>\n </div>\n )}\n\n <div>\n <Label>{t('inbox_ops.edit_dialog.note_additions', 'Notes')}</Label>\n <Textarea\n value={noteAdditions}\n onChange={(event) => {\n const lines = event.target.value.split('\\n').filter(Boolean)\n updateField('noteAdditions', lines.length > 0 ? lines : undefined)\n }}\n rows={2}\n placeholder={t('inbox_ops.placeholder.one_note_per_line', 'One note per line')}\n />\n </div>\n </div>\n )\n}\n\nfunction LogActivityPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_name', 'Contact Name')}</Label>\n <Input value={(payload.contactName as string) || ''} onChange={(event) => updateField('contactName', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_type', 'Contact Type')}</Label>\n <Select\n value={(payload.contactType as string) || 'person'}\n onValueChange={(value) => updateField('contactType', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"person\">{t('inbox_ops.contact_type.person', 'Person')}</SelectItem>\n <SelectItem value=\"company\">{t('inbox_ops.contact_type.company', 'Company')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.activity_type', 'Activity Type')}</Label>\n <Select\n value={(payload.activityType as string) || 'email'}\n onValueChange={(value) => updateField('activityType', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"email\">{t('inbox_ops.activity_type.email', 'Email')}</SelectItem>\n <SelectItem value=\"call\">{t('inbox_ops.activity_type.call', 'Call')}</SelectItem>\n <SelectItem value=\"meeting\">{t('inbox_ops.activity_type.meeting', 'Meeting')}</SelectItem>\n <SelectItem value=\"note\">{t('inbox_ops.activity_type.note', 'Note')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.subject', 'Subject')}</Label>\n <Input value={(payload.subject as string) || ''} onChange={(event) => updateField('subject', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.body', 'Body')}</Label>\n <Textarea value={(payload.body as string) || ''} onChange={(event) => updateField('body', event.target.value)} rows={6} />\n </div>\n </div>\n )\n}\n\nexport function EditActionDialog({\n action,\n actionTypeLabels,\n onClose,\n onSaved,\n}: {\n action: ActionDetail\n actionTypeLabels: Record<string, string>\n onClose: () => void\n onSaved: () => void\n}) {\n const t = useT()\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'inbox-ops-edit-action',\n })\n const [payload, setPayload] = React.useState<Record<string, unknown>>(\n () => structuredClone(action.payload),\n )\n const [isSaving, setIsSaving] = React.useState(false)\n const [jsonMode, setJsonMode] = React.useState(false)\n const [jsonText, setJsonText] = React.useState(() => JSON.stringify(action.payload, null, 2))\n const [jsonError, setJsonError] = React.useState<string | null>(null)\n\n const handleSave = React.useCallback(async () => {\n let finalPayload = payload\n if (jsonMode) {\n try {\n finalPayload = JSON.parse(jsonText)\n setJsonError(null)\n } catch {\n setJsonError(t('inbox_ops.edit_dialog.invalid_json', 'Invalid JSON'))\n return\n }\n }\n\n setIsSaving(true)\n const result = await runMutation({\n // TODO(#2373-D): thread updatedAt \u2014 ActionDetail type lives outside this file's edit scope\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(undefined),\n () => apiCall<{ ok: boolean; error?: string }>(\n `/api/inbox_ops/proposals/${action.proposalId}/actions/${action.id}`,\n { method: 'PATCH', body: JSON.stringify({ payload: finalPayload }) },\n ),\n ),\n context: {},\n })\n if (result?.ok && result.result?.ok) {\n flash(t('inbox_ops.edit_dialog.saved', 'Action updated successfully'), 'success')\n onSaved()\n onClose()\n } else {\n flash(result?.result?.error || t('inbox_ops.flash.save_failed', 'Failed to save'), 'error')\n }\n setIsSaving(false)\n }, [action, payload, jsonMode, jsonText, t, onSaved, onClose, runMutation])\n\n React.useEffect(() => {\n function handleKeyDown(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSave()\n }\n }\n window.addEventListener('keydown', handleKeyDown)\n return () => window.removeEventListener('keydown', handleKeyDown)\n }, [handleSave])\n\n const updateField = (key: string, value: unknown) => {\n setPayload((prev) => ({ ...prev, [key]: value }))\n }\n\n const resolveActionDescription = useActionDescriptionResolver()\n const label = actionTypeLabels[action.actionType] || action.actionType\n const hasTypedEditor = [\n 'create_order', 'create_quote', 'update_order', 'update_shipment', 'create_contact', 'link_contact', 'log_activity', 'draft_reply',\n ].includes(action.actionType)\n\n return (\n <Dialog open onOpenChange={(open) => { if (!open) onClose() }}>\n <DialogContent className=\"sm:max-w-2xl sm:max-h-[85vh]\">\n <DialogHeader>\n <DialogTitle>{t('inbox_ops.edit_dialog.title', 'Edit Action')}: {label}</DialogTitle>\n <DialogDescription>{resolveActionDescription(action.description, action.payload)}</DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 overflow-y-auto max-h-[60vh] py-2\">\n {hasTypedEditor && !jsonMode && (\n <>\n {action.actionType === 'update_order' && (\n <UpdateOrderPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'update_shipment' && (\n <ShipmentPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'create_contact' && (\n <ContactPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'link_contact' && (\n <LinkContactPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'log_activity' && (\n <LogActivityPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'draft_reply' && (\n <DraftReplyPayloadEditor payload={payload} updateField={updateField} />\n )}\n </>\n )}\n\n {(!hasTypedEditor || jsonMode) && (\n <div className=\"space-y-2\">\n <Label>{t('inbox_ops.edit_dialog.payload_json', 'Payload (JSON)')}</Label>\n <Textarea\n className=\"font-mono text-xs min-h-[200px]\"\n value={jsonText}\n onChange={(event) => {\n setJsonText(event.target.value)\n setJsonError(null)\n }}\n />\n {jsonError && <p className=\"text-xs text-status-error-text\">{t('inbox_ops.edit_dialog.invalid_json', 'Invalid JSON')}</p>}\n </div>\n )}\n\n {hasTypedEditor && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n if (!jsonMode) {\n setJsonText(JSON.stringify(payload, null, 2))\n } else {\n try {\n setPayload(JSON.parse(jsonText))\n setJsonError(null)\n } catch {\n setJsonError(t('inbox_ops.edit_dialog.invalid_json', 'Invalid JSON'))\n return\n }\n }\n setJsonMode(!jsonMode)\n }}\n >\n {jsonMode ? t('inbox_ops.edit_dialog.form_view', 'Form view') : t('inbox_ops.edit_dialog.json_view', 'JSON view')}\n </Button>\n )}\n </div>\n\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} disabled={isSaving}>\n {t('inbox_ops.edit_dialog.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={isSaving}>\n {isSaving && <Loader2 className=\"h-4 w-4 animate-spin mr-1\" />}\n {t('inbox_ops.edit_dialog.save', 'Save Changes')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Loader2 } from 'lucide-react'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DialogDescription,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport type { ActionDetail } from './types'\nimport { useActionDescriptionResolver } from './ActionCard'\n\nfunction ShipmentPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n const trackingNumbers = ((payload.trackingNumbers as string[]) || []).join(', ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.carrier', 'Carrier')}</Label>\n <Input value={(payload.carrierName as string) || ''} onChange={(event) => updateField('carrierName', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.status', 'Status')}</Label>\n <Input value={(payload.statusLabel as string) || ''} onChange={(event) => updateField('statusLabel', event.target.value)} />\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.tracking_numbers', 'Tracking Numbers')}</Label>\n <Input\n value={trackingNumbers}\n onChange={(event) => updateField('trackingNumbers', event.target.value.split(',').map((s) => s.trim()).filter(Boolean))}\n placeholder={t('inbox_ops.placeholder.comma_separated', 'Comma-separated')}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.shipped_at', 'Shipped At')}</Label>\n <Input value={(payload.shippedAt as string) || ''} onChange={(event) => updateField('shippedAt', event.target.value)} placeholder=\"YYYY-MM-DD\" />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.estimated_delivery', 'Estimated Delivery')}</Label>\n <Input value={(payload.estimatedDelivery as string) || ''} onChange={(event) => updateField('estimatedDelivery', event.target.value)} placeholder=\"YYYY-MM-DD\" />\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.notes', 'Notes')}</Label>\n <Textarea value={(payload.notes as string) || ''} onChange={(event) => updateField('notes', event.target.value)} rows={2} />\n </div>\n </div>\n )\n}\n\nfunction ContactPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n const type = (payload.type as string) || 'person'\n const isPerson = type === 'person'\n\n const existingName = (payload.name as string) || ''\n const nameParts = existingName.trim().split(/\\s+/).filter((p) => p.length > 0)\n const [firstName, setFirstName] = React.useState(() => isPerson ? (nameParts[0] || '') : '')\n const [lastName, setLastName] = React.useState(() => isPerson ? (nameParts.slice(1).join(' ') || '') : '')\n\n const updatePersonName = React.useCallback((first: string, last: string) => {\n const combined = `${first} ${last}`.trim()\n updateField('name', combined)\n }, [updateField])\n\n const lastNameMissing = isPerson && !lastName.trim()\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n {isPerson ? (\n <>\n <div>\n <Label>{t('inbox_ops.contact.first_name', 'First Name')}</Label>\n <Input\n value={firstName}\n onChange={(event) => {\n setFirstName(event.target.value)\n updatePersonName(event.target.value, lastName)\n }}\n />\n </div>\n <div>\n <Label>{t('inbox_ops.contact.last_name', 'Last Name')}</Label>\n <Input\n value={lastName}\n onChange={(event) => {\n setLastName(event.target.value)\n updatePersonName(firstName, event.target.value)\n }}\n className={lastNameMissing ? 'border-status-error-border' : ''}\n />\n {lastNameMissing && (\n <p className=\"text-xs text-status-error-text mt-1\">{t('inbox_ops.contact.last_name_required', 'Last name is required')}</p>\n )}\n </div>\n </>\n ) : (\n <div>\n <Label>{t('inbox_ops.edit_dialog.name', 'Name')}</Label>\n <Input value={existingName} onChange={(event) => updateField('name', event.target.value)} />\n </div>\n )}\n <div>\n <Label>{t('inbox_ops.edit_dialog.type', 'Type')}</Label>\n <Select\n value={type}\n onValueChange={(value) => updateField('type', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"person\">{t('inbox_ops.contact_type.person', 'Person')}</SelectItem>\n <SelectItem value=\"company\">{t('inbox_ops.contact_type.company', 'Company')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.email', 'Email')}</Label>\n <Input value={(payload.email as string) || ''} onChange={(event) => updateField('email', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.phone', 'Phone')}</Label>\n <Input value={(payload.phone as string) || ''} onChange={(event) => updateField('phone', event.target.value)} />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.company_name', 'Company Name')}</Label>\n <Input value={(payload.companyName as string) || ''} onChange={(event) => updateField('companyName', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.role', 'Role')}</Label>\n <Input value={(payload.role as string) || ''} onChange={(event) => updateField('role', event.target.value)} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction LinkContactPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.email', 'Email')}</Label>\n <Input value={(payload.emailAddress as string) || ''} onChange={(event) => updateField('emailAddress', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_id', 'Contact ID')}</Label>\n <Input value={(payload.contactId as string) || ''} onChange={(event) => updateField('contactId', event.target.value)} />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_type', 'Contact Type')}</Label>\n <Select\n value={(payload.contactType as string) || 'person'}\n onValueChange={(value) => updateField('contactType', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"person\">{t('inbox_ops.contact_type.person', 'Person')}</SelectItem>\n <SelectItem value=\"company\">{t('inbox_ops.contact_type.company', 'Company')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_name', 'Contact Name')}</Label>\n <Input value={(payload.contactName as string) || ''} onChange={(event) => updateField('contactName', event.target.value)} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction DraftReplyPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.to', 'To')}</Label>\n <Input value={(payload.to as string) || ''} onChange={(event) => updateField('to', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.to_name', 'To Name')}</Label>\n <Input value={(payload.toName as string) || ''} onChange={(event) => updateField('toName', event.target.value)} />\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.subject', 'Subject')}</Label>\n <Input value={(payload.subject as string) || ''} onChange={(event) => updateField('subject', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.body', 'Body')}</Label>\n <Textarea value={(payload.body as string) || ''} onChange={(event) => updateField('body', event.target.value)} rows={6} />\n </div>\n </div>\n )\n}\n\nfunction UpdateOrderPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n const quantityChanges = (payload.quantityChanges as Record<string, unknown>[]) || []\n const deliveryDateChange = (payload.deliveryDateChange as Record<string, unknown>) || {}\n const noteAdditions = Array.isArray(payload.noteAdditions) ? (payload.noteAdditions as string[]).join('\\n') : ''\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.order_id', 'Order ID')}</Label>\n <Input value={(payload.orderId as string) || ''} onChange={(event) => updateField('orderId', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.order_number', 'Order Number')}</Label>\n <Input value={(payload.orderNumber as string) || ''} onChange={(event) => updateField('orderNumber', event.target.value)} />\n </div>\n </div>\n\n {quantityChanges.length > 0 && (\n <div>\n <Label className=\"mb-1 block\">{t('inbox_ops.edit_dialog.quantity_changes', 'Quantity Changes')}</Label>\n <div className=\"space-y-2\">\n {quantityChanges.map((change, index) => (\n <div key={index} className=\"border rounded p-2 space-y-2\">\n <div className=\"grid grid-cols-3 gap-2\">\n <div>\n <Label className=\"text-xs\">{t('inbox_ops.edit_dialog.line_item', 'Line Item')}</Label>\n <Input\n className=\"h-8 text-sm\"\n value={(change.lineItemName as string) || ''}\n onChange={(event) => {\n const arr = [...quantityChanges]\n arr[index] = { ...arr[index], lineItemName: event.target.value }\n updateField('quantityChanges', arr)\n }}\n />\n </div>\n <div>\n <Label className=\"text-xs\">{t('inbox_ops.edit_dialog.old_qty', 'Old Qty')}</Label>\n <Input\n className=\"h-8 text-sm\"\n value={(change.oldQuantity as string) || ''}\n onChange={(event) => {\n const arr = [...quantityChanges]\n arr[index] = { ...arr[index], oldQuantity: event.target.value }\n updateField('quantityChanges', arr)\n }}\n />\n </div>\n <div>\n <Label className=\"text-xs\">{t('inbox_ops.edit_dialog.new_qty', 'New Qty')}</Label>\n <Input\n className=\"h-8 text-sm\"\n value={(change.newQuantity as string) || ''}\n onChange={(event) => {\n const arr = [...quantityChanges]\n arr[index] = { ...arr[index], newQuantity: event.target.value }\n updateField('quantityChanges', arr)\n }}\n />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {Boolean(deliveryDateChange.oldDate || deliveryDateChange.newDate) && (\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.old_delivery_date', 'Old Delivery Date')}</Label>\n <Input\n value={(deliveryDateChange.oldDate as string) || ''}\n onChange={(event) => updateField('deliveryDateChange', { ...deliveryDateChange, oldDate: event.target.value })}\n placeholder=\"YYYY-MM-DD\"\n />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.new_delivery_date', 'New Delivery Date')}</Label>\n <Input\n value={(deliveryDateChange.newDate as string) || ''}\n onChange={(event) => updateField('deliveryDateChange', { ...deliveryDateChange, newDate: event.target.value })}\n placeholder=\"YYYY-MM-DD\"\n />\n </div>\n </div>\n )}\n\n <div>\n <Label>{t('inbox_ops.edit_dialog.note_additions', 'Notes')}</Label>\n <Textarea\n value={noteAdditions}\n onChange={(event) => {\n const lines = event.target.value.split('\\n').filter(Boolean)\n updateField('noteAdditions', lines.length > 0 ? lines : undefined)\n }}\n rows={2}\n placeholder={t('inbox_ops.placeholder.one_note_per_line', 'One note per line')}\n />\n </div>\n </div>\n )\n}\n\nfunction LogActivityPayloadEditor({\n payload,\n updateField,\n}: {\n payload: Record<string, unknown>\n updateField: (key: string, value: unknown) => void\n}) {\n const t = useT()\n return (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_name', 'Contact Name')}</Label>\n <Input value={(payload.contactName as string) || ''} onChange={(event) => updateField('contactName', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.contact_type', 'Contact Type')}</Label>\n <Select\n value={(payload.contactType as string) || 'person'}\n onValueChange={(value) => updateField('contactType', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"person\">{t('inbox_ops.contact_type.person', 'Person')}</SelectItem>\n <SelectItem value=\"company\">{t('inbox_ops.contact_type.company', 'Company')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.activity_type', 'Activity Type')}</Label>\n <Select\n value={(payload.activityType as string) || 'email'}\n onValueChange={(value) => updateField('activityType', value)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"email\">{t('inbox_ops.activity_type.email', 'Email')}</SelectItem>\n <SelectItem value=\"call\">{t('inbox_ops.activity_type.call', 'Call')}</SelectItem>\n <SelectItem value=\"meeting\">{t('inbox_ops.activity_type.meeting', 'Meeting')}</SelectItem>\n <SelectItem value=\"note\">{t('inbox_ops.activity_type.note', 'Note')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.subject', 'Subject')}</Label>\n <Input value={(payload.subject as string) || ''} onChange={(event) => updateField('subject', event.target.value)} />\n </div>\n <div>\n <Label>{t('inbox_ops.edit_dialog.body', 'Body')}</Label>\n <Textarea value={(payload.body as string) || ''} onChange={(event) => updateField('body', event.target.value)} rows={6} />\n </div>\n </div>\n )\n}\n\nexport function EditActionDialog({\n action,\n actionTypeLabels,\n onClose,\n onSaved,\n}: {\n action: ActionDetail\n actionTypeLabels: Record<string, string>\n onClose: () => void\n onSaved: () => void\n}) {\n const t = useT()\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'inbox-ops-edit-action',\n })\n const [payload, setPayload] = React.useState<Record<string, unknown>>(\n () => structuredClone(action.payload),\n )\n const [isSaving, setIsSaving] = React.useState(false)\n const [jsonMode, setJsonMode] = React.useState(false)\n const [jsonText, setJsonText] = React.useState(() => JSON.stringify(action.payload, null, 2))\n const [jsonError, setJsonError] = React.useState<string | null>(null)\n\n const handleSave = React.useCallback(async () => {\n let finalPayload = payload\n if (jsonMode) {\n try {\n finalPayload = JSON.parse(jsonText)\n setJsonError(null)\n } catch {\n setJsonError(t('inbox_ops.edit_dialog.invalid_json', 'Invalid JSON'))\n return\n }\n }\n\n setIsSaving(true)\n try {\n await runMutation({\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(action.updatedAt),\n () => apiCallOrThrow<{ ok: boolean; error?: string }>(\n `/api/inbox_ops/proposals/${action.proposalId}/actions/${action.id}`,\n { method: 'PATCH', body: JSON.stringify({ payload: finalPayload }) },\n ),\n ),\n context: {},\n })\n flash(t('inbox_ops.edit_dialog.saved', 'Action updated successfully'), 'success')\n onSaved()\n onClose()\n } catch (error) {\n if (extractOptimisticLockConflict(error)) {\n onClose()\n return\n }\n const message = error instanceof Error ? error.message.trim() : ''\n flash(message || t('inbox_ops.flash.save_failed', 'Failed to save'), 'error')\n } finally {\n setIsSaving(false)\n }\n }, [action, payload, jsonMode, jsonText, t, onSaved, onClose, runMutation])\n\n React.useEffect(() => {\n function handleKeyDown(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSave()\n }\n }\n window.addEventListener('keydown', handleKeyDown)\n return () => window.removeEventListener('keydown', handleKeyDown)\n }, [handleSave])\n\n const updateField = (key: string, value: unknown) => {\n setPayload((prev) => ({ ...prev, [key]: value }))\n }\n\n const resolveActionDescription = useActionDescriptionResolver()\n const label = actionTypeLabels[action.actionType] || action.actionType\n const hasTypedEditor = [\n 'create_order', 'create_quote', 'update_order', 'update_shipment', 'create_contact', 'link_contact', 'log_activity', 'draft_reply',\n ].includes(action.actionType)\n\n return (\n <Dialog open onOpenChange={(open) => { if (!open) onClose() }}>\n <DialogContent className=\"sm:max-w-2xl sm:max-h-[85vh]\">\n <DialogHeader>\n <DialogTitle>{t('inbox_ops.edit_dialog.title', 'Edit Action')}: {label}</DialogTitle>\n <DialogDescription>{resolveActionDescription(action.description, action.payload)}</DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 overflow-y-auto max-h-[60vh] py-2\">\n {hasTypedEditor && !jsonMode && (\n <>\n {action.actionType === 'update_order' && (\n <UpdateOrderPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'update_shipment' && (\n <ShipmentPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'create_contact' && (\n <ContactPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'link_contact' && (\n <LinkContactPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'log_activity' && (\n <LogActivityPayloadEditor payload={payload} updateField={updateField} />\n )}\n {action.actionType === 'draft_reply' && (\n <DraftReplyPayloadEditor payload={payload} updateField={updateField} />\n )}\n </>\n )}\n\n {(!hasTypedEditor || jsonMode) && (\n <div className=\"space-y-2\">\n <Label>{t('inbox_ops.edit_dialog.payload_json', 'Payload (JSON)')}</Label>\n <Textarea\n className=\"font-mono text-xs min-h-[200px]\"\n value={jsonText}\n onChange={(event) => {\n setJsonText(event.target.value)\n setJsonError(null)\n }}\n />\n {jsonError && <p className=\"text-xs text-status-error-text\">{t('inbox_ops.edit_dialog.invalid_json', 'Invalid JSON')}</p>}\n </div>\n )}\n\n {hasTypedEditor && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n if (!jsonMode) {\n setJsonText(JSON.stringify(payload, null, 2))\n } else {\n try {\n setPayload(JSON.parse(jsonText))\n setJsonError(null)\n } catch {\n setJsonError(t('inbox_ops.edit_dialog.invalid_json', 'Invalid JSON'))\n return\n }\n }\n setJsonMode(!jsonMode)\n }}\n >\n {jsonMode ? t('inbox_ops.edit_dialog.form_view', 'Form view') : t('inbox_ops.edit_dialog.json_view', 'JSON view')}\n </Button>\n )}\n </div>\n\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} disabled={isSaving}>\n {t('inbox_ops.edit_dialog.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={isSaving}>\n {isSaving && <Loader2 className=\"h-4 w-4 animate-spin mr-1\" />}\n {t('inbox_ops.edit_dialog.save', 'Save Changes')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -76,6 +76,16 @@ async function POST(req) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
).catch(() => void 0);
|
|
79
|
+
if (force && partitions.length > 1) {
|
|
80
|
+
try {
|
|
81
|
+
await bus.emitEvent(
|
|
82
|
+
"query_index.vectorize_purge",
|
|
83
|
+
{ entityType, tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
|
|
84
|
+
{ persistent: true, deliverInline: false }
|
|
85
|
+
);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
79
89
|
try {
|
|
80
90
|
await Promise.all(
|
|
81
91
|
partitions.map((part) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/query_index/api/reindex.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexReindexRequestSchema } from './openapi'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { isValidEntityIdShape } from '@open-mercato/shared/lib/query/engine'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['query_index.reindex'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({})) as any\n const entityType = String(body?.entityType || '')\n if (!entityType) return NextResponse.json({ error: 'Missing entityType' }, { status: 400 })\n if (!isValidEntityIdShape(entityType)) {\n return NextResponse.json({ error: 'Invalid entityType' }, { status: 400 })\n }\n const force = Boolean(body?.force)\n const batchSize = Number.isFinite(body?.batchSize) ? Math.max(1, Math.trunc(body.batchSize)) : undefined\n const partitionCountInput = Number(body?.partitionCount)\n const partitionCount = Number.isFinite(partitionCountInput)\n ? Math.max(1, Math.trunc(partitionCountInput))\n : 1\n const partitionIndexInput = Number(body?.partitionIndex)\n const partitionIndex = Number.isFinite(partitionIndexInput) ? Math.max(0, Math.trunc(partitionIndexInput)) : undefined\n if (partitionIndex !== undefined && partitionIndex >= partitionCount) {\n return NextResponse.json({ error: 'partitionIndex must be < partitionCount' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n let em: any | null = null\n try {\n em = container.resolve('em')\n } catch {}\n const bus = container.resolve('eventBus') as any\n const partitions = partitionIndex !== undefined\n ? [partitionIndex]\n : Array.from({ length: partitionCount }, (_, idx) => idx)\n const firstPartition = partitions[0] ?? 0\n\n const guardUserId = typeof auth.sub === 'string' ? auth.sub : ''\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: {\n entityType,\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex requested for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n try {\n await Promise.all(\n partitions.map((part) => {\n const payload: Record<string, unknown> = {\n entityType,\n force,\n batchSize,\n partitionCount,\n partitionIndex: part,\n resetCoverage: part === firstPartition,\n }\n if (auth.tenantId !== undefined) {\n payload.tenantId = auth.tenantId ?? null\n }\n if (auth.orgId !== undefined) {\n payload.organizationId = auth.orgId ?? null\n }\n if (typeof auth.sub === 'string' && auth.sub.length > 0) {\n payload.requestedByUserId = auth.sub\n }\n return bus.emitEvent(\n 'query_index.reindex',\n payload,\n { persistent: true, deliverInline: false },\n )\n }),\n )\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex queued for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n } catch (error) {\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n level: 'warn',\n message: `Failed to queue reindex for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n error: error instanceof Error ? error.message : String(error),\n },\n },\n ).catch(() => undefined)\n throw error\n }\n return NextResponse.json({ ok: true })\n}\n\nconst queryIndexReindexDoc: OpenApiMethodDoc = {\n summary: 'Trigger query index rebuild',\n description: 'Queues a reindex job for the specified entity type within the current tenant scope.',\n tags: [queryIndexTag],\n requestBody: {\n contentType: 'application/json',\n schema: queryIndexReindexRequestSchema,\n description: 'Entity identifier and optional force flag.',\n },\n responses: [\n { status: 200, description: 'Reindex job accepted.', schema: queryIndexOkSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity type', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Queue a query index rebuild',\n methods: {\n POST: queryIndexReindexDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexReindexRequestSchema } from './openapi'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { isValidEntityIdShape } from '@open-mercato/shared/lib/query/engine'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['query_index.reindex'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({})) as any\n const entityType = String(body?.entityType || '')\n if (!entityType) return NextResponse.json({ error: 'Missing entityType' }, { status: 400 })\n if (!isValidEntityIdShape(entityType)) {\n return NextResponse.json({ error: 'Invalid entityType' }, { status: 400 })\n }\n const force = Boolean(body?.force)\n const batchSize = Number.isFinite(body?.batchSize) ? Math.max(1, Math.trunc(body.batchSize)) : undefined\n const partitionCountInput = Number(body?.partitionCount)\n const partitionCount = Number.isFinite(partitionCountInput)\n ? Math.max(1, Math.trunc(partitionCountInput))\n : 1\n const partitionIndexInput = Number(body?.partitionIndex)\n const partitionIndex = Number.isFinite(partitionIndexInput) ? Math.max(0, Math.trunc(partitionIndexInput)) : undefined\n if (partitionIndex !== undefined && partitionIndex >= partitionCount) {\n return NextResponse.json({ error: 'partitionIndex must be < partitionCount' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n let em: any | null = null\n try {\n em = container.resolve('em')\n } catch {}\n const bus = container.resolve('eventBus') as any\n const partitions = partitionIndex !== undefined\n ? [partitionIndex]\n : Array.from({ length: partitionCount }, (_, idx) => idx)\n const firstPartition = partitions[0] ?? 0\n\n const guardUserId = typeof auth.sub === 'string' ? auth.sub : ''\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: {\n entityType,\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex requested for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n // Queued once here rather than from a partition: the purge deletes every vector for the\n // scope, so emitting it from inside the fan-out would race the per-record vectorize jobs\n // its siblings are already queueing and drop vectors this run had just rebuilt.\n if (force && partitions.length > 1) {\n try {\n await bus.emitEvent(\n 'query_index.vectorize_purge',\n { entityType, tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n { persistent: true, deliverInline: false },\n )\n } catch {\n // Best effort: a failed purge leaves stale vectors, which the orphan sweep still prunes.\n }\n }\n\n try {\n await Promise.all(\n partitions.map((part) => {\n const payload: Record<string, unknown> = {\n entityType,\n force,\n batchSize,\n partitionCount,\n partitionIndex: part,\n resetCoverage: part === firstPartition,\n }\n if (auth.tenantId !== undefined) {\n payload.tenantId = auth.tenantId ?? null\n }\n if (auth.orgId !== undefined) {\n payload.organizationId = auth.orgId ?? null\n }\n if (typeof auth.sub === 'string' && auth.sub.length > 0) {\n payload.requestedByUserId = auth.sub\n }\n return bus.emitEvent(\n 'query_index.reindex',\n payload,\n { persistent: true, deliverInline: false },\n )\n }),\n )\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex queued for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n } catch (error) {\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n level: 'warn',\n message: `Failed to queue reindex for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n error: error instanceof Error ? error.message : String(error),\n },\n },\n ).catch(() => undefined)\n throw error\n }\n return NextResponse.json({ ok: true })\n}\n\nconst queryIndexReindexDoc: OpenApiMethodDoc = {\n summary: 'Trigger query index rebuild',\n description: 'Queues a reindex job for the specified entity type within the current tenant scope.',\n tags: [queryIndexTag],\n requestBody: {\n contentType: 'application/json',\n schema: queryIndexReindexRequestSchema,\n description: 'Entity identifier and optional force flag.',\n },\n responses: [\n { status: 200, description: 'Reindex job accepted.', schema: queryIndexOkSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity type', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Queue a query index rebuild',\n methods: {\n POST: queryIndexReindexDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|