@open-mercato/core 0.6.6-develop.6171.1.17de2dc37a → 0.6.6-develop.6174.1.8a7040f00d
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/resources/backend/resources/resource-types/page.js +25 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +88 -37
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js +27 -4
- package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
- package/dist/modules/resources/components/detail/activitiesAdapter.js +30 -13
- package/dist/modules/resources/components/detail/activitiesAdapter.js.map +2 -2
- package/dist/modules/resources/components/detail/notesAdapter.js +45 -29
- package/dist/modules/resources/components/detail/notesAdapter.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/resources/backend/resources/resource-types/page.tsx +38 -4
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +102 -37
- package/src/modules/resources/backend/resources/resources/page.tsx +40 -6
- package/src/modules/resources/components/detail/activitiesAdapter.ts +46 -13
- package/src/modules/resources/components/detail/notesAdapter.ts +61 -29
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/resources/components/detail/activitiesAdapter.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport type { ActivitiesDataAdapter, ActivitySummary } from '@open-mercato/ui/backend/detail'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport function createResourceActivitiesAdapter(translator: Translator): ActivitiesDataAdapter {\n return {\n list: async ({ entityId }) => {\n const params = new URLSearchParams({\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (entityId) params.set('entityId', entityId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/resources/activities?${params.toString()}`,\n undefined,\n { errorMessage: translator('resources.resources.detail.activities.loadError', 'Failed to load activities.') },\n )\n return Array.isArray(payload?.items) ? (payload.items as ActivitySummary[]) : []\n },\n create: async ({ entityId, activityType, subject, body, occurredAt, customFields }) => {\n
|
|
5
|
-
"mappings": ";AAEA,SAAS,4BAA4B;AACrC,SAAS,YAAY,YAAY,kBAAkB;
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport type { ActivitiesDataAdapter, ActivitySummary } from '@open-mercato/ui/backend/detail'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type ResourceActivitiesGuardedMutation = <T>(\n runner: () => Promise<T>,\n payload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type CreateResourceActivitiesAdapterOptions = {\n runMutation?: ResourceActivitiesGuardedMutation\n}\n\nexport function createResourceActivitiesAdapter(\n translator: Translator,\n options: CreateResourceActivitiesAdapterOptions = {},\n): ActivitiesDataAdapter {\n const runWrite = async <T,>(\n runner: () => Promise<T>,\n payload: Record<string, unknown>,\n ): Promise<T> => {\n if (options.runMutation) {\n return options.runMutation(runner, payload)\n }\n return runner()\n }\n\n return {\n list: async ({ entityId }) => {\n const params = new URLSearchParams({\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (entityId) params.set('entityId', entityId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/resources/activities?${params.toString()}`,\n undefined,\n { errorMessage: translator('resources.resources.detail.activities.loadError', 'Failed to load activities.') },\n )\n return Array.isArray(payload?.items) ? (payload.items as ActivitySummary[]) : []\n },\n create: async ({ entityId, activityType, subject, body, occurredAt, customFields }) => {\n const payload = {\n entityId,\n activityType,\n subject: subject ?? undefined,\n body: body ?? undefined,\n occurredAt: occurredAt ?? undefined,\n ...(customFields ? { customFields } : {}),\n }\n await runWrite(\n () => createCrud('resources/activities', payload, {\n errorMessage: translator('resources.resources.detail.activities.error', 'Failed to save activity'),\n }),\n { operation: 'createActivity', entityId, activityType },\n )\n },\n update: async ({ id, patch }) => {\n const payload = {\n id,\n entityId: patch.entityId,\n activityType: patch.activityType,\n subject: patch.subject ?? undefined,\n body: patch.body ?? undefined,\n occurredAt: patch.occurredAt ?? undefined,\n ...(patch.customFields ? { customFields: patch.customFields } : {}),\n }\n await runWrite(\n () => updateCrud('resources/activities', payload, {\n errorMessage: translator('resources.resources.detail.activities.error', 'Failed to save activity'),\n }),\n { operation: 'updateActivity', id },\n )\n },\n delete: async ({ id }) => {\n await runWrite(\n () => deleteCrud('resources/activities', {\n id,\n errorMessage: translator('resources.resources.detail.activities.deleteError', 'Failed to delete activity.'),\n }),\n { operation: 'deleteActivity', id },\n )\n },\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,SAAS,4BAA4B;AACrC,SAAS,YAAY,YAAY,kBAAkB;AAc5C,SAAS,gCACd,YACA,UAAkD,CAAC,GAC5B;AACvB,QAAM,WAAW,OACf,QACA,YACe;AACf,QAAI,QAAQ,aAAa;AACvB,aAAO,QAAQ,YAAY,QAAQ,OAAO;AAAA,IAC5C;AACA,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,EAAE,SAAS,MAAM;AAC5B,YAAM,SAAS,IAAI,gBAAgB;AAAA,QACjC,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AACD,UAAI,SAAU,QAAO,IAAI,YAAY,QAAQ;AAC7C,YAAM,UAAU,MAAM;AAAA,QACpB,6BAA6B,OAAO,SAAS,CAAC;AAAA,QAC9C;AAAA,QACA,EAAE,cAAc,WAAW,mDAAmD,4BAA4B,EAAE;AAAA,MAC9G;AACA,aAAO,MAAM,QAAQ,SAAS,KAAK,IAAK,QAAQ,QAA8B,CAAC;AAAA,IACjF;AAAA,IACA,QAAQ,OAAO,EAAE,UAAU,cAAc,SAAS,MAAM,YAAY,aAAa,MAAM;AACrF,YAAM,UAAU;AAAA,QACd;AAAA,QACA;AAAA,QACA,SAAS,WAAW;AAAA,QACpB,MAAM,QAAQ;AAAA,QACd,YAAY,cAAc;AAAA,QAC1B,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,MACzC;AACA,YAAM;AAAA,QACJ,MAAM,WAAW,wBAAwB,SAAS;AAAA,UAChD,cAAc,WAAW,+CAA+C,yBAAyB;AAAA,QACnG,CAAC;AAAA,QACD,EAAE,WAAW,kBAAkB,UAAU,aAAa;AAAA,MACxD;AAAA,IACF;AAAA,IACA,QAAQ,OAAO,EAAE,IAAI,MAAM,MAAM;AAC/B,YAAM,UAAU;AAAA,QACd;AAAA,QACA,UAAU,MAAM;AAAA,QAChB,cAAc,MAAM;AAAA,QACpB,SAAS,MAAM,WAAW;AAAA,QAC1B,MAAM,MAAM,QAAQ;AAAA,QACpB,YAAY,MAAM,cAAc;AAAA,QAChC,GAAI,MAAM,eAAe,EAAE,cAAc,MAAM,aAAa,IAAI,CAAC;AAAA,MACnE;AACA,YAAM;AAAA,QACJ,MAAM,WAAW,wBAAwB,SAAS;AAAA,UAChD,cAAc,WAAW,+CAA+C,yBAAyB;AAAA,QACnG,CAAC;AAAA,QACD,EAAE,WAAW,kBAAkB,GAAG;AAAA,MACpC;AAAA,IACF;AAAA,IACA,QAAQ,OAAO,EAAE,GAAG,MAAM;AACxB,YAAM;AAAA,QACJ,MAAM,WAAW,wBAAwB;AAAA,UACvC;AAAA,UACA,cAAc,WAAW,qDAAqD,4BAA4B;AAAA,QAC5G,CAAC;AAAA,QACD,EAAE,WAAW,kBAAkB,GAAG;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
3
3
|
import { mapCommentSummary } from "@open-mercato/ui/backend/detail/NotesSection";
|
|
4
|
-
function createResourceNotesAdapter(translator) {
|
|
4
|
+
function createResourceNotesAdapter(translator, options = {}) {
|
|
5
|
+
const runWrite = async (runner, payload) => {
|
|
6
|
+
if (options.runMutation) {
|
|
7
|
+
return options.runMutation(runner, payload);
|
|
8
|
+
}
|
|
9
|
+
return runner();
|
|
10
|
+
};
|
|
5
11
|
return {
|
|
6
12
|
list: async ({ entityId }) => {
|
|
7
13
|
const params = new URLSearchParams();
|
|
@@ -15,19 +21,23 @@ function createResourceNotesAdapter(translator) {
|
|
|
15
21
|
return items.map(mapCommentSummary);
|
|
16
22
|
},
|
|
17
23
|
create: async ({ entityId, body, appearanceIcon, appearanceColor }) => {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
const requestBody = {
|
|
25
|
+
entityId,
|
|
26
|
+
body,
|
|
27
|
+
appearanceIcon: appearanceIcon ?? void 0,
|
|
28
|
+
appearanceColor: appearanceColor ?? void 0
|
|
29
|
+
};
|
|
30
|
+
const response = await runWrite(
|
|
31
|
+
() => apiCallOrThrow(
|
|
32
|
+
"/api/resources/comments",
|
|
33
|
+
{
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "content-type": "application/json" },
|
|
36
|
+
body: JSON.stringify(requestBody)
|
|
37
|
+
},
|
|
38
|
+
{ errorMessage: translator("resources.resources.detail.notes.error", "Failed to save note.") }
|
|
39
|
+
),
|
|
40
|
+
{ operation: "createNote", entityId }
|
|
31
41
|
);
|
|
32
42
|
return response.result ?? {};
|
|
33
43
|
},
|
|
@@ -36,24 +46,30 @@ function createResourceNotesAdapter(translator) {
|
|
|
36
46
|
if (patch.body !== void 0) payload.body = patch.body;
|
|
37
47
|
if (patch.appearanceIcon !== void 0) payload.appearanceIcon = patch.appearanceIcon;
|
|
38
48
|
if (patch.appearanceColor !== void 0) payload.appearanceColor = patch.appearanceColor;
|
|
39
|
-
await
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
await runWrite(
|
|
50
|
+
() => apiCallOrThrow(
|
|
51
|
+
"/api/resources/comments",
|
|
52
|
+
{
|
|
53
|
+
method: "PUT",
|
|
54
|
+
headers: { "content-type": "application/json" },
|
|
55
|
+
body: JSON.stringify(payload)
|
|
56
|
+
},
|
|
57
|
+
{ errorMessage: translator("resources.resources.detail.notes.updateError", "Failed to update note.") }
|
|
58
|
+
),
|
|
59
|
+
{ operation: "updateNote", id }
|
|
47
60
|
);
|
|
48
61
|
},
|
|
49
62
|
delete: async ({ id }) => {
|
|
50
|
-
await
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
await runWrite(
|
|
64
|
+
() => apiCallOrThrow(
|
|
65
|
+
`/api/resources/comments?id=${encodeURIComponent(id)}`,
|
|
66
|
+
{
|
|
67
|
+
method: "DELETE",
|
|
68
|
+
headers: { "content-type": "application/json" }
|
|
69
|
+
},
|
|
70
|
+
{ errorMessage: translator("resources.resources.detail.notes.deleteError", "Failed to delete note") }
|
|
71
|
+
),
|
|
72
|
+
{ operation: "deleteNote", id }
|
|
57
73
|
);
|
|
58
74
|
}
|
|
59
75
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/resources/components/detail/notesAdapter.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { mapCommentSummary, type NotesDataAdapter } from '@open-mercato/ui/backend/detail/NotesSection'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport function createResourceNotesAdapter(translator: Translator): NotesDataAdapter {\n return {\n list: async ({ entityId }) => {\n const params = new URLSearchParams()\n if (entityId) params.set('entityId', entityId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/resources/comments?${params.toString()}`,\n undefined,\n { errorMessage: translator('resources.resources.detail.notes.loadError', 'Failed to load notes.') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items.map(mapCommentSummary)\n },\n create: async ({ entityId, body, appearanceIcon, appearanceColor }) => {\n const response = await apiCallOrThrow<Record<string, unknown>>(\n
|
|
5
|
-
"mappings": ";AAEA,SAAS,gBAAgB,4BAA4B;AACrD,SAAS,yBAAgD;
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { mapCommentSummary, type NotesDataAdapter } from '@open-mercato/ui/backend/detail/NotesSection'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type ResourceNotesGuardedMutation = <T>(\n runner: () => Promise<T>,\n payload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type CreateResourceNotesAdapterOptions = {\n runMutation?: ResourceNotesGuardedMutation\n}\n\nexport function createResourceNotesAdapter(\n translator: Translator,\n options: CreateResourceNotesAdapterOptions = {},\n): NotesDataAdapter {\n const runWrite = async <T,>(\n runner: () => Promise<T>,\n payload: Record<string, unknown>,\n ): Promise<T> => {\n if (options.runMutation) {\n return options.runMutation(runner, payload)\n }\n return runner()\n }\n\n return {\n list: async ({ entityId }) => {\n const params = new URLSearchParams()\n if (entityId) params.set('entityId', entityId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/resources/comments?${params.toString()}`,\n undefined,\n { errorMessage: translator('resources.resources.detail.notes.loadError', 'Failed to load notes.') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items.map(mapCommentSummary)\n },\n create: async ({ entityId, body, appearanceIcon, appearanceColor }) => {\n const requestBody = {\n entityId,\n body,\n appearanceIcon: appearanceIcon ?? undefined,\n appearanceColor: appearanceColor ?? undefined,\n }\n const response = await runWrite(\n () => apiCallOrThrow<Record<string, unknown>>(\n '/api/resources/comments',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(requestBody),\n },\n { errorMessage: translator('resources.resources.detail.notes.error', 'Failed to save note.') },\n ),\n { operation: 'createNote', entityId },\n )\n return response.result ?? {}\n },\n update: async ({ id, patch }) => {\n const payload: Record<string, unknown> = { id }\n if (patch.body !== undefined) payload.body = patch.body\n if (patch.appearanceIcon !== undefined) payload.appearanceIcon = patch.appearanceIcon\n if (patch.appearanceColor !== undefined) payload.appearanceColor = patch.appearanceColor\n await runWrite(\n () => apiCallOrThrow(\n '/api/resources/comments',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: translator('resources.resources.detail.notes.updateError', 'Failed to update note.') },\n ),\n { operation: 'updateNote', id },\n )\n },\n delete: async ({ id }) => {\n await runWrite(\n () => apiCallOrThrow(\n `/api/resources/comments?id=${encodeURIComponent(id)}`,\n {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n },\n { errorMessage: translator('resources.resources.detail.notes.deleteError', 'Failed to delete note') },\n ),\n { operation: 'deleteNote', id },\n )\n },\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,SAAS,gBAAgB,4BAA4B;AACrD,SAAS,yBAAgD;AAalD,SAAS,2BACd,YACA,UAA6C,CAAC,GAC5B;AAClB,QAAM,WAAW,OACf,QACA,YACe;AACf,QAAI,QAAQ,aAAa;AACvB,aAAO,QAAQ,YAAY,QAAQ,OAAO;AAAA,IAC5C;AACA,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,EAAE,SAAS,MAAM;AAC5B,YAAM,SAAS,IAAI,gBAAgB;AACnC,UAAI,SAAU,QAAO,IAAI,YAAY,QAAQ;AAC7C,YAAM,UAAU,MAAM;AAAA,QACpB,2BAA2B,OAAO,SAAS,CAAC;AAAA,QAC5C;AAAA,QACA,EAAE,cAAc,WAAW,8CAA8C,uBAAuB,EAAE;AAAA,MACpG;AACA,YAAM,QAAQ,MAAM,QAAQ,SAAS,KAAK,IAAI,QAAQ,QAAQ,CAAC;AAC/D,aAAO,MAAM,IAAI,iBAAiB;AAAA,IACpC;AAAA,IACA,QAAQ,OAAO,EAAE,UAAU,MAAM,gBAAgB,gBAAgB,MAAM;AACrE,YAAM,cAAc;AAAA,QAClB;AAAA,QACA;AAAA,QACA,gBAAgB,kBAAkB;AAAA,QAClC,iBAAiB,mBAAmB;AAAA,MACtC;AACA,YAAM,WAAW,MAAM;AAAA,QACrB,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,WAAW;AAAA,UAClC;AAAA,UACA,EAAE,cAAc,WAAW,0CAA0C,sBAAsB,EAAE;AAAA,QAC/F;AAAA,QACA,EAAE,WAAW,cAAc,SAAS;AAAA,MACtC;AACA,aAAO,SAAS,UAAU,CAAC;AAAA,IAC7B;AAAA,IACA,QAAQ,OAAO,EAAE,IAAI,MAAM,MAAM;AAC/B,YAAM,UAAmC,EAAE,GAAG;AAC9C,UAAI,MAAM,SAAS,OAAW,SAAQ,OAAO,MAAM;AACnD,UAAI,MAAM,mBAAmB,OAAW,SAAQ,iBAAiB,MAAM;AACvE,UAAI,MAAM,oBAAoB,OAAW,SAAQ,kBAAkB,MAAM;AACzE,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,UAC9B;AAAA,UACA,EAAE,cAAc,WAAW,gDAAgD,wBAAwB,EAAE;AAAA,QACvG;AAAA,QACA,EAAE,WAAW,cAAc,GAAG;AAAA,MAChC;AAAA,IACF;AAAA,IACA,QAAQ,OAAO,EAAE,GAAG,MAAM;AACxB,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,8BAA8B,mBAAmB,EAAE,CAAC;AAAA,UACpD;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAChD;AAAA,UACA,EAAE,cAAc,WAAW,gDAAgD,uBAAuB,EAAE;AAAA,QACtG;AAAA,QACA,EAAE,WAAW,cAAc,GAAG;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6174.1.8a7040f00d",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -246,16 +246,16 @@
|
|
|
246
246
|
"zod": "^4.4.3"
|
|
247
247
|
},
|
|
248
248
|
"peerDependencies": {
|
|
249
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
250
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
251
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
249
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6174.1.8a7040f00d",
|
|
250
|
+
"@open-mercato/shared": "0.6.6-develop.6174.1.8a7040f00d",
|
|
251
|
+
"@open-mercato/ui": "0.6.6-develop.6174.1.8a7040f00d",
|
|
252
252
|
"react": "^19.0.0",
|
|
253
253
|
"react-dom": "^19.0.0"
|
|
254
254
|
},
|
|
255
255
|
"devDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6174.1.8a7040f00d",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6174.1.8a7040f00d",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6174.1.8a7040f00d",
|
|
259
259
|
"@testing-library/dom": "^10.4.1",
|
|
260
260
|
"@testing-library/jest-dom": "^6.9.1",
|
|
261
261
|
"@testing-library/react": "^16.3.1",
|
|
@@ -13,6 +13,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
|
13
13
|
import { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
14
14
|
import { deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
15
15
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
16
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
16
17
|
import { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
|
|
17
18
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
18
19
|
import { Package } from 'lucide-react'
|
|
@@ -23,6 +24,7 @@ import { formatDateTime } from '@open-mercato/shared/lib/time'
|
|
|
23
24
|
const PAGE_SIZE = 50
|
|
24
25
|
const DESCRIPTION_CLASSNAME = 'line-clamp-3 whitespace-pre-line text-sm text-foreground'
|
|
25
26
|
const SUBTEXT_CLASSNAME = 'line-clamp-2 text-xs text-muted-foreground'
|
|
27
|
+
const RESOURCE_TYPES_MUTATION_CONTEXT_ID = 'resources.resource-types.list'
|
|
26
28
|
|
|
27
29
|
type ResourceTypeRow = {
|
|
28
30
|
id: string
|
|
@@ -40,6 +42,13 @@ type ResourceTypesResponse = {
|
|
|
40
42
|
totalPages?: number
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
type ResourceTypesMutationContext = {
|
|
46
|
+
formId: string
|
|
47
|
+
resourceKind: string
|
|
48
|
+
resourceId?: string
|
|
49
|
+
retryLastMutation: () => Promise<boolean>
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
export default function ResourcesResourceTypesPage() {
|
|
44
53
|
const translate = useT()
|
|
45
54
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
@@ -53,6 +62,27 @@ export default function ResourcesResourceTypesPage() {
|
|
|
53
62
|
const [search, setSearch] = React.useState('')
|
|
54
63
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
55
64
|
const [reloadToken, setReloadToken] = React.useState(0)
|
|
65
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<ResourceTypesMutationContext>({
|
|
66
|
+
contextId: RESOURCE_TYPES_MUTATION_CONTEXT_ID,
|
|
67
|
+
blockedMessage: translate('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
68
|
+
})
|
|
69
|
+
const runResourceTypeMutation = React.useCallback(
|
|
70
|
+
async <T,>(
|
|
71
|
+
operation: () => Promise<T>,
|
|
72
|
+
mutationPayload: Record<string, unknown>,
|
|
73
|
+
resourceId?: string,
|
|
74
|
+
): Promise<T> => runMutation({
|
|
75
|
+
operation,
|
|
76
|
+
mutationPayload,
|
|
77
|
+
context: {
|
|
78
|
+
formId: RESOURCE_TYPES_MUTATION_CONTEXT_ID,
|
|
79
|
+
resourceKind: 'resources.resourceType',
|
|
80
|
+
resourceId,
|
|
81
|
+
retryLastMutation,
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
[retryLastMutation, runMutation],
|
|
85
|
+
)
|
|
56
86
|
|
|
57
87
|
const translations = React.useMemo(() => ({
|
|
58
88
|
title: translate('resources.resourceTypes.page.title', 'Resource types'),
|
|
@@ -230,16 +260,20 @@ export default function ResourcesResourceTypesPage() {
|
|
|
230
260
|
if (!confirmed) return
|
|
231
261
|
try {
|
|
232
262
|
const headers = buildOptimisticLockHeader(entry.updatedAt)
|
|
233
|
-
await
|
|
234
|
-
|
|
235
|
-
|
|
263
|
+
await runResourceTypeMutation(
|
|
264
|
+
() => withScopedApiRequestHeaders(headers, () => (
|
|
265
|
+
deleteCrud('resources/resource-types', entry.id, { errorMessage: translations.errors.delete })
|
|
266
|
+
)),
|
|
267
|
+
{ operation: 'deleteResourceType', id: entry.id, updatedAt: entry.updatedAt ?? null },
|
|
268
|
+
entry.id,
|
|
269
|
+
)
|
|
236
270
|
flash(translations.messages.deleted, 'success')
|
|
237
271
|
handleRefresh()
|
|
238
272
|
} catch (error) {
|
|
239
273
|
console.error('resources.resource-types.delete', error)
|
|
240
274
|
flash(translations.errors.delete, 'error')
|
|
241
275
|
}
|
|
242
|
-
}, [confirm, handleRefresh, translations.actions.deleteConfirm, translations.errors.delete, translations.errors.deleteAssigned, translations.messages.deleted])
|
|
276
|
+
}, [confirm, handleRefresh, runResourceTypeMutation, translations.actions.deleteConfirm, translations.errors.delete, translations.errors.deleteAssigned, translations.messages.deleted])
|
|
243
277
|
|
|
244
278
|
return (
|
|
245
279
|
<Page>
|
|
@@ -12,6 +12,7 @@ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors
|
|
|
12
12
|
import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
13
13
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
14
14
|
import { ActivitiesSection, NotesSection, RecordNotFoundState, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'
|
|
15
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
15
16
|
import { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'
|
|
16
17
|
import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
|
|
17
18
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -62,6 +63,14 @@ type ResourceResponse = {
|
|
|
62
63
|
items: ResourceRecord[]
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
type ResourceDetailMutationContext = {
|
|
67
|
+
formId: string
|
|
68
|
+
resourceKind: string
|
|
69
|
+
resourceId?: string
|
|
70
|
+
data: Record<string, unknown> | null
|
|
71
|
+
retryLastMutation: () => Promise<boolean>
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
function normalizeResourceRecord(record: ResourceRecord): ResourceRecord {
|
|
66
75
|
return {
|
|
67
76
|
...record,
|
|
@@ -108,8 +117,42 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
108
117
|
const flashShownRef = React.useRef(false)
|
|
109
118
|
|
|
110
119
|
const availabilityMode = 'availability'
|
|
111
|
-
const
|
|
112
|
-
|
|
120
|
+
const mutationContextId = React.useMemo(
|
|
121
|
+
() => (resourceId ? `resources.resource:${resourceId}` : 'resources.resource:pending'),
|
|
122
|
+
[resourceId],
|
|
123
|
+
)
|
|
124
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<ResourceDetailMutationContext>({
|
|
125
|
+
contextId: mutationContextId,
|
|
126
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
127
|
+
})
|
|
128
|
+
const mutationContext = React.useMemo<ResourceDetailMutationContext>(
|
|
129
|
+
() => ({
|
|
130
|
+
formId: mutationContextId,
|
|
131
|
+
resourceKind: 'resources.resource',
|
|
132
|
+
resourceId,
|
|
133
|
+
data: initialValues,
|
|
134
|
+
retryLastMutation,
|
|
135
|
+
}),
|
|
136
|
+
[initialValues, mutationContextId, resourceId, retryLastMutation],
|
|
137
|
+
)
|
|
138
|
+
const runMutationWithContext = React.useCallback(
|
|
139
|
+
async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => (
|
|
140
|
+
runMutation({
|
|
141
|
+
operation,
|
|
142
|
+
mutationPayload,
|
|
143
|
+
context: mutationContext,
|
|
144
|
+
})
|
|
145
|
+
),
|
|
146
|
+
[mutationContext, runMutation],
|
|
147
|
+
)
|
|
148
|
+
const notesAdapter = React.useMemo(
|
|
149
|
+
() => createResourceNotesAdapter(detailTranslator, { runMutation: runMutationWithContext }),
|
|
150
|
+
[detailTranslator, runMutationWithContext],
|
|
151
|
+
)
|
|
152
|
+
const activitiesAdapter = React.useMemo(
|
|
153
|
+
() => createResourceActivitiesAdapter(detailTranslator, { runMutation: runMutationWithContext }),
|
|
154
|
+
[detailTranslator, runMutationWithContext],
|
|
155
|
+
)
|
|
113
156
|
|
|
114
157
|
const activityTypeLabels = React.useMemo<DictionarySelectLabels>(() => ({
|
|
115
158
|
placeholder: t('resources.resources.detail.activities.dictionary.placeholder', 'Select an activity type'),
|
|
@@ -342,14 +385,18 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
342
385
|
if (!trimmed.length) {
|
|
343
386
|
throw new Error(t('resources.resources.tags.labelRequired', 'Tag name is required.'))
|
|
344
387
|
}
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
388
|
+
const requestBody = { label: trimmed }
|
|
389
|
+
const response = await runMutationWithContext(
|
|
390
|
+
() => apiCallOrThrow<Record<string, unknown>>(
|
|
391
|
+
'/api/resources/tags',
|
|
392
|
+
{
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: { 'content-type': 'application/json' },
|
|
395
|
+
body: JSON.stringify(requestBody),
|
|
396
|
+
},
|
|
397
|
+
{ errorMessage: t('resources.resources.tags.createError', 'Failed to create tag.') },
|
|
398
|
+
),
|
|
399
|
+
{ operation: 'createTag', label: trimmed },
|
|
353
400
|
)
|
|
354
401
|
const payload = response.result ?? {}
|
|
355
402
|
const id =
|
|
@@ -364,34 +411,42 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
364
411
|
: null
|
|
365
412
|
return { id, label: trimmed, color }
|
|
366
413
|
},
|
|
367
|
-
[t],
|
|
414
|
+
[runMutationWithContext, t],
|
|
368
415
|
)
|
|
369
416
|
|
|
370
417
|
const assignTag = React.useCallback(async (tagId: string) => {
|
|
371
418
|
if (!resourceId) return
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
419
|
+
const requestBody = { tagId, resourceId }
|
|
420
|
+
await runMutationWithContext(
|
|
421
|
+
() => apiCallOrThrow(
|
|
422
|
+
'/api/resources/resources/tags/assign',
|
|
423
|
+
{
|
|
424
|
+
method: 'POST',
|
|
425
|
+
headers: { 'content-type': 'application/json' },
|
|
426
|
+
body: JSON.stringify(requestBody),
|
|
427
|
+
},
|
|
428
|
+
{ errorMessage: t('resources.resources.tags.updateError', 'Failed to update tags.') },
|
|
429
|
+
),
|
|
430
|
+
{ operation: 'assignTag', resourceId, tagId },
|
|
380
431
|
)
|
|
381
|
-
}, [resourceId, t])
|
|
432
|
+
}, [resourceId, runMutationWithContext, t])
|
|
382
433
|
|
|
383
434
|
const unassignTag = React.useCallback(async (tagId: string) => {
|
|
384
435
|
if (!resourceId) return
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
436
|
+
const requestBody = { tagId, resourceId }
|
|
437
|
+
await runMutationWithContext(
|
|
438
|
+
() => apiCallOrThrow(
|
|
439
|
+
'/api/resources/resources/tags/unassign',
|
|
440
|
+
{
|
|
441
|
+
method: 'POST',
|
|
442
|
+
headers: { 'content-type': 'application/json' },
|
|
443
|
+
body: JSON.stringify(requestBody),
|
|
444
|
+
},
|
|
445
|
+
{ errorMessage: t('resources.resources.tags.updateError', 'Failed to update tags.') },
|
|
446
|
+
),
|
|
447
|
+
{ operation: 'unassignTag', resourceId, tagId },
|
|
393
448
|
)
|
|
394
|
-
}, [resourceId, t])
|
|
449
|
+
}, [resourceId, runMutationWithContext, t])
|
|
395
450
|
|
|
396
451
|
const handleTagsSave = React.useCallback(
|
|
397
452
|
async ({ next, added, removed }: { next: TagOption[]; added: TagOption[]; removed: TagOption[] }) => {
|
|
@@ -508,12 +563,21 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
508
563
|
|
|
509
564
|
const handleDelete = React.useCallback(async () => {
|
|
510
565
|
if (!resourceId) return
|
|
511
|
-
|
|
566
|
+
const resourceOptimisticLockHeader = buildOptimisticLockHeader(
|
|
567
|
+
typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null,
|
|
568
|
+
)
|
|
569
|
+
const deleteResource = () => deleteCrud('resources/resources', resourceId, {
|
|
512
570
|
errorMessage: t('resources.resources.form.errors.delete', 'Failed to delete resource.'),
|
|
513
571
|
})
|
|
572
|
+
await runMutationWithContext(
|
|
573
|
+
() => Object.keys(resourceOptimisticLockHeader).length > 0
|
|
574
|
+
? withScopedApiRequestHeaders(resourceOptimisticLockHeader, deleteResource)
|
|
575
|
+
: deleteResource(),
|
|
576
|
+
{ operation: 'deleteResource', id: resourceId, updatedAt: initialValues?.updatedAt ?? null },
|
|
577
|
+
)
|
|
514
578
|
flash(t('resources.resources.form.flash.deleted', 'Resource deleted.'), 'success')
|
|
515
579
|
router.push('/backend/resources/resources')
|
|
516
|
-
}, [resourceId, router, t])
|
|
580
|
+
}, [initialValues?.updatedAt, resourceId, router, runMutationWithContext, t])
|
|
517
581
|
|
|
518
582
|
const handleRulesetChange = React.useCallback(async (nextId: string | null) => {
|
|
519
583
|
if (!resourceId) return
|
|
@@ -523,14 +587,15 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
523
587
|
const resourceOptimisticLockHeader = buildOptimisticLockHeader(
|
|
524
588
|
typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null,
|
|
525
589
|
)
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
590
|
+
await runMutationWithContext(
|
|
591
|
+
() => Object.keys(resourceOptimisticLockHeader).length > 0
|
|
592
|
+
? withScopedApiRequestHeaders(resourceOptimisticLockHeader, updateSchedule)
|
|
593
|
+
: updateSchedule(),
|
|
594
|
+
{ operation: 'updateAvailabilityRuleSet', id: resourceId, availabilityRuleSetId: nextId },
|
|
595
|
+
)
|
|
531
596
|
setAvailabilityRuleSetId(nextId)
|
|
532
597
|
flash(t('resources.resources.availability.ruleset.updateSuccess', 'Schedule updated.'), 'success')
|
|
533
|
-
}, [initialValues?.updatedAt, resourceId, t])
|
|
598
|
+
}, [initialValues?.updatedAt, resourceId, runMutationWithContext, t])
|
|
534
599
|
|
|
535
600
|
const resourceTitle =
|
|
536
601
|
typeof initialValues?.name === 'string' && initialValues.name.trim().length > 0
|
|
@@ -16,6 +16,7 @@ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimi
|
|
|
16
16
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
17
17
|
import type { FilterDef, FilterOption, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'
|
|
18
18
|
import type { TagOption } from '@open-mercato/ui/backend/detail'
|
|
19
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
19
20
|
import { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
|
|
20
21
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
21
22
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -23,6 +24,7 @@ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
|
23
24
|
import { Pencil } from 'lucide-react'
|
|
24
25
|
|
|
25
26
|
const PAGE_SIZE = 20
|
|
27
|
+
const RESOURCE_LIST_MUTATION_CONTEXT_ID = 'resources.resources.list'
|
|
26
28
|
|
|
27
29
|
type ResourceRow = {
|
|
28
30
|
id: string
|
|
@@ -66,6 +68,13 @@ type ResourceTypesResponse = {
|
|
|
66
68
|
items: Array<Record<string, unknown>>
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
type ResourceListMutationContext = {
|
|
72
|
+
formId: string
|
|
73
|
+
resourceKind: string
|
|
74
|
+
resourceId?: string
|
|
75
|
+
retryLastMutation: () => Promise<boolean>
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
export default function ResourcesResourcesPage() {
|
|
70
79
|
const [rows, setRows] = React.useState<ResourceRow[]>([])
|
|
71
80
|
const [page, setPage] = React.useState(1)
|
|
@@ -87,6 +96,27 @@ export default function ResourcesResourcesPage() {
|
|
|
87
96
|
const selectedResourceTypeId = typeof filterValues.resourceTypeId === 'string'
|
|
88
97
|
? filterValues.resourceTypeId
|
|
89
98
|
: resourceTypeFilter
|
|
99
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<ResourceListMutationContext>({
|
|
100
|
+
contextId: RESOURCE_LIST_MUTATION_CONTEXT_ID,
|
|
101
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
102
|
+
})
|
|
103
|
+
const runResourceMutation = React.useCallback(
|
|
104
|
+
async <T,>(
|
|
105
|
+
operation: () => Promise<T>,
|
|
106
|
+
mutationPayload: Record<string, unknown>,
|
|
107
|
+
resourceId?: string,
|
|
108
|
+
): Promise<T> => runMutation({
|
|
109
|
+
operation,
|
|
110
|
+
mutationPayload,
|
|
111
|
+
context: {
|
|
112
|
+
formId: RESOURCE_LIST_MUTATION_CONTEXT_ID,
|
|
113
|
+
resourceKind: 'resources.resource',
|
|
114
|
+
resourceId,
|
|
115
|
+
retryLastMutation,
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
[retryLastMutation, runMutation],
|
|
119
|
+
)
|
|
90
120
|
|
|
91
121
|
React.useEffect(() => {
|
|
92
122
|
setPage(1)
|
|
@@ -353,11 +383,15 @@ export default function ResourcesResourcesPage() {
|
|
|
353
383
|
if (!confirmed) return
|
|
354
384
|
try {
|
|
355
385
|
const headers = buildOptimisticLockHeader(row.updatedAt)
|
|
356
|
-
await
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
386
|
+
await runResourceMutation(
|
|
387
|
+
() => withScopedApiRequestHeaders(headers, () => (
|
|
388
|
+
deleteCrud('resources/resources', row.id, {
|
|
389
|
+
errorMessage: t('resources.resources.list.error.delete', 'Failed to delete resource.'),
|
|
390
|
+
})
|
|
391
|
+
)),
|
|
392
|
+
{ operation: 'deleteResource', id: row.id, updatedAt: row.updatedAt ?? null },
|
|
393
|
+
row.id,
|
|
394
|
+
)
|
|
361
395
|
flash(t('resources.resources.list.flash.deleted', 'Resource deleted.'), 'success')
|
|
362
396
|
setPage(1)
|
|
363
397
|
router.refresh()
|
|
@@ -365,7 +399,7 @@ export default function ResourcesResourcesPage() {
|
|
|
365
399
|
const message = error instanceof Error ? error.message : t('resources.resources.list.error.delete', 'Failed to delete resource.')
|
|
366
400
|
flash(message, 'error')
|
|
367
401
|
}
|
|
368
|
-
}, [confirm, router, t])
|
|
402
|
+
}, [confirm, router, runResourceMutation, t])
|
|
369
403
|
|
|
370
404
|
const columns = React.useMemo<ColumnDef<ResourceTableRow>[]>(() => [
|
|
371
405
|
{
|