@open-mercato/core 0.5.1-develop.2800.bfe2178a4f → 0.5.1-develop.2802.9223828f7f
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/auth/cli.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +106 -107
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js +235 -0
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +7 -0
- package/package.json +3 -3
- package/src/modules/auth/cli.ts +1 -1
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +17 -29
- package/src/modules/messages/components/useMessagesInboxBulkActions.ts +324 -0
- package/src/modules/messages/i18n/de.json +8 -0
- package/src/modules/messages/i18n/en.json +8 -0
- package/src/modules/messages/i18n/es.json +8 -0
- package/src/modules/messages/i18n/pl.json +8 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
6
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
7
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
8
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
9
|
+
import { toErrorMessage } from "./message-detail/utils.js";
|
|
10
|
+
const MESSAGE_BULK_REQUESTS = {
|
|
11
|
+
markRead: {
|
|
12
|
+
method: "PUT",
|
|
13
|
+
buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,
|
|
14
|
+
successKey: "messages.bulk.flash.markReadSuccess",
|
|
15
|
+
successFallback: "{count} messages marked as read.",
|
|
16
|
+
errorKey: "messages.errors.stateChangeFailed",
|
|
17
|
+
errorFallback: "Failed to update message state."
|
|
18
|
+
},
|
|
19
|
+
markUnread: {
|
|
20
|
+
method: "DELETE",
|
|
21
|
+
buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,
|
|
22
|
+
successKey: "messages.bulk.flash.markUnreadSuccess",
|
|
23
|
+
successFallback: "{count} messages marked as unread.",
|
|
24
|
+
errorKey: "messages.errors.stateChangeFailed",
|
|
25
|
+
errorFallback: "Failed to update message state."
|
|
26
|
+
},
|
|
27
|
+
archive: {
|
|
28
|
+
method: "PUT",
|
|
29
|
+
buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/archive`,
|
|
30
|
+
successKey: "messages.bulk.flash.archiveSuccess",
|
|
31
|
+
successFallback: "{count} messages archived.",
|
|
32
|
+
errorKey: "messages.errors.stateChangeFailed",
|
|
33
|
+
errorFallback: "Failed to update message state."
|
|
34
|
+
},
|
|
35
|
+
delete: {
|
|
36
|
+
method: "DELETE",
|
|
37
|
+
buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}`,
|
|
38
|
+
successKey: "messages.bulk.flash.deleteSuccess",
|
|
39
|
+
successFallback: "{count} messages deleted.",
|
|
40
|
+
errorKey: "messages.errors.deleteFailed",
|
|
41
|
+
errorFallback: "Failed to delete message."
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function normalizeSelectionScopeValue(value) {
|
|
45
|
+
if (value == null) return void 0;
|
|
46
|
+
if (typeof value === "string") {
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
const normalized = value.map((item) => normalizeSelectionScopeValue(item)).filter((item) => item !== void 0);
|
|
52
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === "object") {
|
|
55
|
+
const normalizedEntries = Object.entries(value).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, nestedValue]) => [key, normalizeSelectionScopeValue(nestedValue)]).filter(([, nestedValue]) => nestedValue !== void 0);
|
|
56
|
+
if (normalizedEntries.length === 0) return void 0;
|
|
57
|
+
return Object.fromEntries(normalizedEntries);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function buildMessageSelectionScopeKey(folder, page, search, filterValues) {
|
|
62
|
+
return JSON.stringify({
|
|
63
|
+
folder,
|
|
64
|
+
page,
|
|
65
|
+
search: search.trim(),
|
|
66
|
+
filters: normalizeSelectionScopeValue(filterValues) ?? {}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function runWithConcurrency(items, limit, worker) {
|
|
70
|
+
if (items.length === 0) return [];
|
|
71
|
+
const results = new Array(items.length);
|
|
72
|
+
let nextIndex = 0;
|
|
73
|
+
const runWorker = async () => {
|
|
74
|
+
while (nextIndex < items.length) {
|
|
75
|
+
const currentIndex = nextIndex;
|
|
76
|
+
nextIndex += 1;
|
|
77
|
+
try {
|
|
78
|
+
await worker(items[currentIndex]);
|
|
79
|
+
results[currentIndex] = { status: "fulfilled", value: void 0 };
|
|
80
|
+
} catch (error) {
|
|
81
|
+
results[currentIndex] = { status: "rejected", reason: error };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
await Promise.all(
|
|
86
|
+
Array.from({ length: Math.min(limit, items.length) }, () => runWorker())
|
|
87
|
+
);
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
function useMessagesInboxBulkActions({
|
|
91
|
+
folder,
|
|
92
|
+
page,
|
|
93
|
+
search,
|
|
94
|
+
filterValues
|
|
95
|
+
}) {
|
|
96
|
+
const t = useT();
|
|
97
|
+
const queryClient = useQueryClient();
|
|
98
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
99
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
100
|
+
contextId: "messages-inbox-bulk-actions"
|
|
101
|
+
});
|
|
102
|
+
const selectionScopeKey = React.useMemo(
|
|
103
|
+
() => buildMessageSelectionScopeKey(folder, page, search, filterValues),
|
|
104
|
+
[filterValues, folder, page, search]
|
|
105
|
+
);
|
|
106
|
+
const injectionContext = React.useMemo(
|
|
107
|
+
() => ({
|
|
108
|
+
folder,
|
|
109
|
+
page,
|
|
110
|
+
search: search.trim(),
|
|
111
|
+
filters: filterValues,
|
|
112
|
+
retryLastMutation
|
|
113
|
+
}),
|
|
114
|
+
[filterValues, folder, page, retryLastMutation, search]
|
|
115
|
+
);
|
|
116
|
+
const executeBulkAction = React.useCallback(async (actionId, selectedRows) => {
|
|
117
|
+
const messageIds = selectedRows.map((row) => row.id).filter((id) => id.trim().length > 0);
|
|
118
|
+
if (messageIds.length === 0) return false;
|
|
119
|
+
if (actionId === "delete") {
|
|
120
|
+
const confirmed = await confirm({
|
|
121
|
+
title: t("messages.bulk.delete.title", "Delete {count} messages?", { count: messageIds.length }),
|
|
122
|
+
description: t("messages.bulk.delete.description", "This removes the selected messages from your view."),
|
|
123
|
+
confirmText: t("messages.actions.delete", "Delete"),
|
|
124
|
+
variant: "destructive"
|
|
125
|
+
});
|
|
126
|
+
if (!confirmed) return false;
|
|
127
|
+
}
|
|
128
|
+
const requestConfig = MESSAGE_BULK_REQUESTS[actionId];
|
|
129
|
+
try {
|
|
130
|
+
const summary = await runMutation({
|
|
131
|
+
operation: async () => {
|
|
132
|
+
const results = await runWithConcurrency(messageIds, 5, async (messageId) => {
|
|
133
|
+
const call = await apiCall(requestConfig.buildUrl(messageId), {
|
|
134
|
+
method: requestConfig.method
|
|
135
|
+
});
|
|
136
|
+
if (!call.ok) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
toErrorMessage(call.result) ?? t(requestConfig.errorKey, requestConfig.errorFallback)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
const failed = results.filter((result) => result.status === "rejected").length;
|
|
143
|
+
const succeeded = results.length - failed;
|
|
144
|
+
if (succeeded > 0) {
|
|
145
|
+
await queryClient.invalidateQueries({ queryKey: ["messages", "list"] });
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
action: actionId,
|
|
149
|
+
total: messageIds.length,
|
|
150
|
+
succeeded,
|
|
151
|
+
failed
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
context: {
|
|
155
|
+
actionId,
|
|
156
|
+
messageIds,
|
|
157
|
+
folder,
|
|
158
|
+
page,
|
|
159
|
+
search: search.trim(),
|
|
160
|
+
filters: filterValues,
|
|
161
|
+
retryLastMutation
|
|
162
|
+
},
|
|
163
|
+
mutationPayload: {
|
|
164
|
+
actionId,
|
|
165
|
+
messageIds
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
if (summary.succeeded === 0) {
|
|
169
|
+
flash(
|
|
170
|
+
t("messages.bulk.flash.failed", "Failed to process {count} messages.", { count: summary.failed }),
|
|
171
|
+
"error"
|
|
172
|
+
);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
if (summary.failed > 0) {
|
|
176
|
+
flash(
|
|
177
|
+
t("messages.bulk.flash.partial", "{succeeded} of {total} messages processed; {failed} failed.", {
|
|
178
|
+
succeeded: summary.succeeded,
|
|
179
|
+
total: summary.total,
|
|
180
|
+
failed: summary.failed
|
|
181
|
+
}),
|
|
182
|
+
"warning"
|
|
183
|
+
);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
flash(
|
|
187
|
+
t(requestConfig.successKey, requestConfig.successFallback, { count: summary.succeeded }),
|
|
188
|
+
"success"
|
|
189
|
+
);
|
|
190
|
+
return true;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
flash(
|
|
193
|
+
error instanceof Error ? error.message : t(requestConfig.errorKey, requestConfig.errorFallback),
|
|
194
|
+
"error"
|
|
195
|
+
);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}, [confirm, filterValues, folder, page, queryClient, retryLastMutation, runMutation, search, t]);
|
|
199
|
+
const bulkActions = React.useMemo(
|
|
200
|
+
() => folder === "inbox" ? [
|
|
201
|
+
{
|
|
202
|
+
id: "messages-mark-read",
|
|
203
|
+
label: t("messages.actions.markRead", "Mark read"),
|
|
204
|
+
onExecute: (selectedRows) => executeBulkAction("markRead", selectedRows)
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "messages-mark-unread",
|
|
208
|
+
label: t("messages.actions.markUnread", "Mark unread"),
|
|
209
|
+
onExecute: (selectedRows) => executeBulkAction("markUnread", selectedRows)
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: "messages-archive",
|
|
213
|
+
label: t("messages.actions.archive", "Archive"),
|
|
214
|
+
onExecute: (selectedRows) => executeBulkAction("archive", selectedRows)
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "messages-delete",
|
|
218
|
+
label: t("messages.actions.delete", "Delete"),
|
|
219
|
+
destructive: true,
|
|
220
|
+
onExecute: (selectedRows) => executeBulkAction("delete", selectedRows)
|
|
221
|
+
}
|
|
222
|
+
] : void 0,
|
|
223
|
+
[executeBulkAction, folder, t]
|
|
224
|
+
);
|
|
225
|
+
return {
|
|
226
|
+
bulkActions,
|
|
227
|
+
selectionScopeKey,
|
|
228
|
+
injectionContext,
|
|
229
|
+
ConfirmDialogElement
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
export {
|
|
233
|
+
useMessagesInboxBulkActions
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=useMessagesInboxBulkActions.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/messages/components/useMessagesInboxBulkActions.ts"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { BulkAction } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { toErrorMessage } from './message-detail/utils'\n\nexport type MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'\n\ntype MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'\n\ntype BulkExecutionSummary = {\n action: MessageBulkActionId\n total: number\n succeeded: number\n failed: number\n}\n\ntype MessageBulkRequestConfig = {\n method: 'PUT' | 'DELETE'\n buildUrl: (messageId: string) => string\n successKey: string\n successFallback: string\n errorKey: string\n errorFallback: string\n}\n\ntype MessageInboxBulkMutationContext = {\n actionId: MessageBulkActionId\n messageIds: string[]\n folder: MessageFolder\n page: number\n search: string\n filters: FilterValues\n retryLastMutation: () => Promise<boolean>\n}\n\ntype UseMessagesInboxBulkActionsInput = {\n folder: MessageFolder\n page: number\n search: string\n filterValues: FilterValues\n}\n\ntype MessageInboxBulkRow = {\n id: string\n}\n\nconst MESSAGE_BULK_REQUESTS: Record<MessageBulkActionId, MessageBulkRequestConfig> = {\n markRead: {\n method: 'PUT',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,\n successKey: 'messages.bulk.flash.markReadSuccess',\n successFallback: '{count} messages marked as read.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n markUnread: {\n method: 'DELETE',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,\n successKey: 'messages.bulk.flash.markUnreadSuccess',\n successFallback: '{count} messages marked as unread.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n archive: {\n method: 'PUT',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/archive`,\n successKey: 'messages.bulk.flash.archiveSuccess',\n successFallback: '{count} messages archived.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n delete: {\n method: 'DELETE',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}`,\n successKey: 'messages.bulk.flash.deleteSuccess',\n successFallback: '{count} messages deleted.',\n errorKey: 'messages.errors.deleteFailed',\n errorFallback: 'Failed to delete message.',\n },\n}\n\nfunction normalizeSelectionScopeValue(value: unknown): unknown {\n if (value == null) return undefined\n if (typeof value === 'string') {\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n }\n if (Array.isArray(value)) {\n const normalized = value\n .map((item) => normalizeSelectionScopeValue(item))\n .filter((item) => item !== undefined)\n return normalized.length > 0 ? normalized : undefined\n }\n if (typeof value === 'object') {\n const normalizedEntries = Object.entries(value)\n .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))\n .map(([key, nestedValue]) => [key, normalizeSelectionScopeValue(nestedValue)] as const)\n .filter(([, nestedValue]) => nestedValue !== undefined)\n if (normalizedEntries.length === 0) return undefined\n return Object.fromEntries(normalizedEntries)\n }\n return value\n}\n\nfunction buildMessageSelectionScopeKey(\n folder: MessageFolder,\n page: number,\n search: string,\n filterValues: FilterValues,\n): string {\n return JSON.stringify({\n folder,\n page,\n search: search.trim(),\n filters: normalizeSelectionScopeValue(filterValues) ?? {},\n })\n}\n\nasync function runWithConcurrency<TItem>(\n items: TItem[],\n limit: number,\n worker: (item: TItem) => Promise<void>,\n): Promise<PromiseSettledResult<void>[]> {\n if (items.length === 0) return []\n\n const results: PromiseSettledResult<void>[] = new Array(items.length)\n let nextIndex = 0\n\n const runWorker = async () => {\n while (nextIndex < items.length) {\n const currentIndex = nextIndex\n nextIndex += 1\n try {\n await worker(items[currentIndex])\n results[currentIndex] = { status: 'fulfilled', value: undefined }\n } catch (error) {\n results[currentIndex] = { status: 'rejected', reason: error }\n }\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(limit, items.length) }, () => runWorker()),\n )\n\n return results\n}\n\nexport function useMessagesInboxBulkActions<T extends MessageInboxBulkRow>({\n folder,\n page,\n search,\n filterValues,\n}: UseMessagesInboxBulkActionsInput): {\n bulkActions: BulkAction<T>[] | undefined\n selectionScopeKey: string\n injectionContext: Record<string, unknown>\n ConfirmDialogElement: React.ReactNode\n} {\n const t = useT()\n const queryClient = useQueryClient()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<MessageInboxBulkMutationContext>({\n contextId: 'messages-inbox-bulk-actions',\n })\n\n const selectionScopeKey = React.useMemo(\n () => buildMessageSelectionScopeKey(folder, page, search, filterValues),\n [filterValues, folder, page, search],\n )\n const injectionContext = React.useMemo<Record<string, unknown>>(\n () => ({\n folder,\n page,\n search: search.trim(),\n filters: filterValues,\n retryLastMutation,\n }),\n [filterValues, folder, page, retryLastMutation, search],\n )\n\n const executeBulkAction = React.useCallback(async (\n actionId: MessageBulkActionId,\n selectedRows: T[],\n ): Promise<boolean> => {\n const messageIds = selectedRows.map((row) => row.id).filter((id) => id.trim().length > 0)\n if (messageIds.length === 0) return false\n\n if (actionId === 'delete') {\n const confirmed = await confirm({\n title: t('messages.bulk.delete.title', 'Delete {count} messages?', { count: messageIds.length }),\n description: t('messages.bulk.delete.description', 'This removes the selected messages from your view.'),\n confirmText: t('messages.actions.delete', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return false\n }\n\n const requestConfig = MESSAGE_BULK_REQUESTS[actionId]\n\n try {\n const summary = await runMutation<BulkExecutionSummary>({\n operation: async () => {\n const results = await runWithConcurrency(messageIds, 5, async (messageId) => {\n const call = await apiCall<{ ok?: boolean }>(requestConfig.buildUrl(messageId), {\n method: requestConfig.method,\n })\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t(requestConfig.errorKey, requestConfig.errorFallback),\n )\n }\n })\n\n const failed = results.filter((result) => result.status === 'rejected').length\n const succeeded = results.length - failed\n\n if (succeeded > 0) {\n await queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }\n\n return {\n action: actionId,\n total: messageIds.length,\n succeeded,\n failed,\n }\n },\n context: {\n actionId,\n messageIds,\n folder,\n page,\n search: search.trim(),\n filters: filterValues,\n retryLastMutation,\n },\n mutationPayload: {\n actionId,\n messageIds,\n },\n })\n\n if (summary.succeeded === 0) {\n flash(\n t('messages.bulk.flash.failed', 'Failed to process {count} messages.', { count: summary.failed }),\n 'error',\n )\n return false\n }\n\n if (summary.failed > 0) {\n flash(\n t('messages.bulk.flash.partial', '{succeeded} of {total} messages processed; {failed} failed.', {\n succeeded: summary.succeeded,\n total: summary.total,\n failed: summary.failed,\n }),\n 'warning',\n )\n return true\n }\n\n flash(\n t(requestConfig.successKey, requestConfig.successFallback, { count: summary.succeeded }),\n 'success',\n )\n return true\n } catch (error) {\n flash(\n error instanceof Error\n ? error.message\n : t(requestConfig.errorKey, requestConfig.errorFallback),\n 'error',\n )\n return false\n }\n }, [confirm, filterValues, folder, page, queryClient, retryLastMutation, runMutation, search, t])\n\n const bulkActions = React.useMemo<BulkAction<T>[] | undefined>(\n () => folder === 'inbox'\n ? [\n {\n id: 'messages-mark-read',\n label: t('messages.actions.markRead', 'Mark read'),\n onExecute: (selectedRows: T[]) => executeBulkAction('markRead', selectedRows),\n },\n {\n id: 'messages-mark-unread',\n label: t('messages.actions.markUnread', 'Mark unread'),\n onExecute: (selectedRows: T[]) => executeBulkAction('markUnread', selectedRows),\n },\n {\n id: 'messages-archive',\n label: t('messages.actions.archive', 'Archive'),\n onExecute: (selectedRows: T[]) => executeBulkAction('archive', selectedRows),\n },\n {\n id: 'messages-delete',\n label: t('messages.actions.delete', 'Delete'),\n destructive: true,\n onExecute: (selectedRows: T[]) => executeBulkAction('delete', selectedRows),\n },\n ]\n : undefined,\n [executeBulkAction, folder, t],\n )\n\n return {\n bulkActions,\n selectionScopeKey,\n injectionContext,\n ConfirmDialogElement,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2802.9223828f7f",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -237,10 +237,10 @@
|
|
|
237
237
|
"ts-pattern": "^5.0.0"
|
|
238
238
|
},
|
|
239
239
|
"peerDependencies": {
|
|
240
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
240
|
+
"@open-mercato/shared": "0.5.1-develop.2802.9223828f7f"
|
|
241
241
|
},
|
|
242
242
|
"devDependencies": {
|
|
243
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
243
|
+
"@open-mercato/shared": "0.5.1-develop.2802.9223828f7f",
|
|
244
244
|
"@testing-library/dom": "^10.4.1",
|
|
245
245
|
"@testing-library/jest-dom": "^6.9.1",
|
|
246
246
|
"@testing-library/react": "^16.3.1",
|
package/src/modules/auth/cli.ts
CHANGED
|
@@ -642,7 +642,6 @@ const setPassword: ModuleCli = {
|
|
|
642
642
|
},
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
-
// Export the full CLI list
|
|
646
645
|
const syncRoleAcls: ModuleCli = {
|
|
647
646
|
command: 'sync-role-acls',
|
|
648
647
|
async run(rest) {
|
|
@@ -705,4 +704,5 @@ const syncRoleAcls: ModuleCli = {
|
|
|
705
704
|
},
|
|
706
705
|
}
|
|
707
706
|
|
|
707
|
+
// Export the full CLI list
|
|
708
708
|
export default [addUser, seedRoles, syncRoleAcls, rotateEncryptionKey, addOrganization, setupApp, listOrganizations, listTenants, listUsers, setPassword]
|
|
@@ -16,8 +16,8 @@ import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
|
|
|
16
16
|
import { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'
|
|
17
17
|
import { getMessageUiComponentRegistry } from './utils/typeUiRegistry'
|
|
18
18
|
import { DefaultMessageListItem } from './defaults/DefaultMessageListItem'
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
import { toErrorMessage } from './message-detail/utils'
|
|
20
|
+
import { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'
|
|
21
21
|
|
|
22
22
|
type MessageListItem = {
|
|
23
23
|
id: string
|
|
@@ -63,29 +63,6 @@ type UserListItem = {
|
|
|
63
63
|
name?: string | null
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function toErrorMessage(payload: unknown): string | null {
|
|
67
|
-
if (!payload) return null
|
|
68
|
-
if (typeof payload === 'string') return payload
|
|
69
|
-
if (Array.isArray(payload)) {
|
|
70
|
-
for (const item of payload) {
|
|
71
|
-
const nested = toErrorMessage(item)
|
|
72
|
-
if (nested) return nested
|
|
73
|
-
}
|
|
74
|
-
return null
|
|
75
|
-
}
|
|
76
|
-
if (typeof payload === 'object') {
|
|
77
|
-
const record = payload as Record<string, unknown>
|
|
78
|
-
return (
|
|
79
|
-
toErrorMessage(record.error)
|
|
80
|
-
?? toErrorMessage(record.message)
|
|
81
|
-
?? toErrorMessage(record.detail)
|
|
82
|
-
?? toErrorMessage(record.details)
|
|
83
|
-
?? null
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
88
|
-
|
|
89
66
|
export function MessagesInboxPageClient() {
|
|
90
67
|
const router = useRouter()
|
|
91
68
|
const t = useT()
|
|
@@ -108,6 +85,12 @@ export function MessagesInboxPageClient() {
|
|
|
108
85
|
const pageSize = 20
|
|
109
86
|
const folderMenuRef = React.useRef<HTMLDivElement | null>(null)
|
|
110
87
|
const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])
|
|
88
|
+
const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({
|
|
89
|
+
folder,
|
|
90
|
+
page,
|
|
91
|
+
search,
|
|
92
|
+
filterValues,
|
|
93
|
+
})
|
|
111
94
|
|
|
112
95
|
const listQuery = useQuery({
|
|
113
96
|
queryKey: [
|
|
@@ -393,6 +376,8 @@ export function MessagesInboxPageClient() {
|
|
|
393
376
|
title={t('messages.title', 'Messages')}
|
|
394
377
|
columns={columns}
|
|
395
378
|
data={rows}
|
|
379
|
+
bulkActions={bulkActions}
|
|
380
|
+
selectionScopeKey={selectionScopeKey}
|
|
396
381
|
searchValue={search}
|
|
397
382
|
onSearchChange={(value) => {
|
|
398
383
|
setSearch(value)
|
|
@@ -409,6 +394,7 @@ export function MessagesInboxPageClient() {
|
|
|
409
394
|
setFilterValues({})
|
|
410
395
|
setPage(1)
|
|
411
396
|
}}
|
|
397
|
+
injectionContext={injectionContext}
|
|
412
398
|
isLoading={listQuery.isLoading || listQuery.isFetching}
|
|
413
399
|
pagination={{
|
|
414
400
|
page,
|
|
@@ -443,12 +429,14 @@ export function MessagesInboxPageClient() {
|
|
|
443
429
|
const Icon = option.icon
|
|
444
430
|
const isActive = option.id === folder
|
|
445
431
|
return (
|
|
446
|
-
<
|
|
432
|
+
<Button
|
|
447
433
|
key={option.id}
|
|
448
434
|
type="button"
|
|
435
|
+
variant="ghost"
|
|
436
|
+
size="sm"
|
|
449
437
|
role="menuitemradio"
|
|
450
438
|
aria-checked={isActive}
|
|
451
|
-
className={`
|
|
439
|
+
className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}
|
|
452
440
|
onClick={() => {
|
|
453
441
|
setFolder(option.id)
|
|
454
442
|
setPage(1)
|
|
@@ -457,7 +445,7 @@ export function MessagesInboxPageClient() {
|
|
|
457
445
|
>
|
|
458
446
|
<Icon className="h-4 w-4" aria-hidden />
|
|
459
447
|
<span>{option.label}</span>
|
|
460
|
-
</
|
|
448
|
+
</Button>
|
|
461
449
|
)
|
|
462
450
|
})}
|
|
463
451
|
</div>
|
|
@@ -471,9 +459,9 @@ export function MessagesInboxPageClient() {
|
|
|
471
459
|
onRowClick={(row) => {
|
|
472
460
|
router.push(`/backend/messages/${row.id}`)
|
|
473
461
|
}}
|
|
474
|
-
perspective={{ tableId: 'messages.inbox' }}
|
|
475
462
|
embedded
|
|
476
463
|
/>
|
|
464
|
+
{ConfirmDialogElement}
|
|
477
465
|
</div>
|
|
478
466
|
)
|
|
479
467
|
}
|