@open-mercato/core 0.6.2-develop.3461.1.605f31c2c9 → 0.6.2

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.
Files changed (173) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/queue-runner.js +15 -3
  3. package/dist/helpers/integration/queue-runner.js.map +2 -2
  4. package/dist/helpers/integration/queue.js +1 -1
  5. package/dist/helpers/integration/queue.js.map +2 -2
  6. package/dist/helpers/integration/salesUi.js +24 -1
  7. package/dist/helpers/integration/salesUi.js.map +2 -2
  8. package/dist/modules/auth/api/roles/acl/route.js +37 -1
  9. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  10. package/dist/modules/auth/api/roles/route.js +36 -2
  11. package/dist/modules/auth/api/roles/route.js.map +2 -2
  12. package/dist/modules/auth/api/users/acl/route.js +36 -1
  13. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  14. package/dist/modules/auth/api/users/route.js +101 -13
  15. package/dist/modules/auth/api/users/route.js.map +2 -2
  16. package/dist/modules/auth/backend/roles/[id]/edit/page.js +32 -14
  17. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  18. package/dist/modules/auth/backend/users/[id]/edit/page.js +1 -1
  19. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  20. package/dist/modules/auth/backend/users/create/page.js +1 -1
  21. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  22. package/dist/modules/auth/backend/users/roleOptions.js +1 -1
  23. package/dist/modules/auth/backend/users/roleOptions.js.map +2 -2
  24. package/dist/modules/auth/commands/roles.js +8 -0
  25. package/dist/modules/auth/commands/roles.js.map +2 -2
  26. package/dist/modules/auth/commands/users.js +3 -0
  27. package/dist/modules/auth/commands/users.js.map +2 -2
  28. package/dist/modules/auth/components/AclEditor.js +53 -26
  29. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  30. package/dist/modules/auth/frontend/login.js +35 -0
  31. package/dist/modules/auth/frontend/login.js.map +2 -2
  32. package/dist/modules/auth/lib/grantChecks.js +97 -2
  33. package/dist/modules/auth/lib/grantChecks.js.map +2 -2
  34. package/dist/modules/business_rules/lib/expression-evaluator.js +9 -7
  35. package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
  36. package/dist/modules/business_rules/lib/rule-evaluator.js +3 -3
  37. package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
  38. package/dist/modules/customers/ai-agents.js +10 -1
  39. package/dist/modules/customers/ai-agents.js.map +2 -2
  40. package/dist/modules/customers/ai-tools/settings-pack.js +55 -1
  41. package/dist/modules/customers/ai-tools/settings-pack.js.map +2 -2
  42. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +13 -5
  43. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  44. package/dist/modules/customers/lib/personCompanyLinkTable.js +4 -30
  45. package/dist/modules/customers/lib/personCompanyLinkTable.js.map +2 -2
  46. package/dist/modules/customers/migrations/Migration20260521175027.js +12 -0
  47. package/dist/modules/customers/migrations/Migration20260521175027.js.map +7 -0
  48. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +41 -12
  49. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  50. package/dist/modules/data_sync/api/options.js +2 -0
  51. package/dist/modules/data_sync/api/options.js.map +2 -2
  52. package/dist/modules/data_sync/api/run.js +77 -58
  53. package/dist/modules/data_sync/api/run.js.map +2 -2
  54. package/dist/modules/data_sync/backend/data-sync/page.js +3 -2
  55. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  56. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +8 -3
  57. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  58. package/dist/modules/integrations/backend/integrations/[id]/page.js +15 -13
  59. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  60. package/dist/modules/integrations/data/validators.js +9 -6
  61. package/dist/modules/integrations/data/validators.js.map +2 -2
  62. package/dist/modules/messages/api/route.js +2 -2
  63. package/dist/modules/messages/api/route.js.map +2 -2
  64. package/dist/modules/messages/commands/messages.js +42 -2
  65. package/dist/modules/messages/commands/messages.js.map +2 -2
  66. package/dist/modules/messages/components/MessagesInboxPageClient.js +70 -89
  67. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  68. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +8 -6
  69. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +2 -2
  70. package/dist/modules/messages/components/inboxFilters.js +133 -0
  71. package/dist/modules/messages/components/inboxFilters.js.map +7 -0
  72. package/dist/modules/messages/components/messageListLabels.js +14 -0
  73. package/dist/modules/messages/components/messageListLabels.js.map +7 -0
  74. package/dist/modules/messages/components/useMessagesInboxBulkActions.js +47 -23
  75. package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +2 -2
  76. package/dist/modules/messages/data/validators.js +8 -6
  77. package/dist/modules/messages/data/validators.js.map +2 -2
  78. package/dist/modules/progress/api/jobs/route.js +26 -16
  79. package/dist/modules/progress/api/jobs/route.js.map +2 -2
  80. package/dist/modules/query_index/di.js +8 -6
  81. package/dist/modules/query_index/di.js.map +2 -2
  82. package/dist/modules/query_index/lib/indexer.js +12 -10
  83. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  84. package/dist/modules/query_index/subscribers/upsert_one.js +7 -1
  85. package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
  86. package/dist/modules/sales/api/order-adjustments/route.js.map +2 -2
  87. package/dist/modules/sales/api/quote-adjustments/route.js.map +2 -2
  88. package/dist/modules/sales/commands/documents.js +104 -0
  89. package/dist/modules/sales/commands/documents.js.map +2 -2
  90. package/dist/modules/sales/data/validators.js +20 -1
  91. package/dist/modules/sales/data/validators.js.map +2 -2
  92. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
  93. package/dist/modules/sync_excel/api/import/route.js +126 -116
  94. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  95. package/dist/modules/sync_excel/lib/adapters/customers.js +1 -0
  96. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  97. package/dist/modules/translations/components/TranslationManager.js +9 -4
  98. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  99. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  100. package/dist/modules/workflows/lib/event-trigger-service.js +23 -11
  101. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  102. package/jest.config.cjs +4 -1
  103. package/package.json +15 -16
  104. package/src/helpers/integration/queue-runner.ts +21 -2
  105. package/src/helpers/integration/queue.ts +1 -1
  106. package/src/helpers/integration/salesUi.ts +26 -1
  107. package/src/modules/auth/api/roles/acl/route.ts +39 -1
  108. package/src/modules/auth/api/roles/route.ts +38 -2
  109. package/src/modules/auth/api/users/acl/route.ts +41 -1
  110. package/src/modules/auth/api/users/route.ts +105 -12
  111. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +35 -13
  112. package/src/modules/auth/backend/users/[id]/edit/page.tsx +1 -1
  113. package/src/modules/auth/backend/users/create/page.tsx +1 -1
  114. package/src/modules/auth/backend/users/roleOptions.ts +2 -1
  115. package/src/modules/auth/commands/roles.ts +8 -0
  116. package/src/modules/auth/commands/users.ts +3 -0
  117. package/src/modules/auth/components/AclEditor.tsx +50 -24
  118. package/src/modules/auth/frontend/login.tsx +40 -0
  119. package/src/modules/auth/lib/grantChecks.ts +116 -2
  120. package/src/modules/business_rules/lib/expression-evaluator.ts +9 -13
  121. package/src/modules/business_rules/lib/rule-evaluator.ts +3 -3
  122. package/src/modules/customers/ai-agents.ts +10 -1
  123. package/src/modules/customers/ai-tools/settings-pack.ts +62 -1
  124. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +18 -7
  125. package/src/modules/customers/lib/personCompanyLinkTable.ts +33 -41
  126. package/src/modules/customers/migrations/Migration20260521175027.ts +39 -0
  127. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +38 -12
  128. package/src/modules/data_sync/api/options.ts +2 -0
  129. package/src/modules/data_sync/api/run.ts +80 -61
  130. package/src/modules/data_sync/backend/data-sync/page.tsx +10 -0
  131. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +17 -3
  132. package/src/modules/data_sync/i18n/de.json +3 -0
  133. package/src/modules/data_sync/i18n/en.json +3 -0
  134. package/src/modules/data_sync/i18n/es.json +3 -0
  135. package/src/modules/data_sync/i18n/pl.json +3 -0
  136. package/src/modules/data_sync/lib/adapter.ts +9 -0
  137. package/src/modules/integrations/backend/integrations/[id]/page.tsx +15 -13
  138. package/src/modules/integrations/data/validators.ts +5 -4
  139. package/src/modules/messages/api/route.ts +2 -2
  140. package/src/modules/messages/commands/messages.ts +54 -2
  141. package/src/modules/messages/components/MessagesInboxPageClient.tsx +80 -92
  142. package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +9 -5
  143. package/src/modules/messages/components/inboxFilters.ts +162 -0
  144. package/src/modules/messages/components/messageListLabels.ts +29 -0
  145. package/src/modules/messages/components/useMessagesInboxBulkActions.ts +58 -26
  146. package/src/modules/messages/data/validators.ts +7 -5
  147. package/src/modules/messages/i18n/de.json +7 -4
  148. package/src/modules/messages/i18n/en.json +7 -4
  149. package/src/modules/messages/i18n/es.json +7 -4
  150. package/src/modules/messages/i18n/pl.json +7 -4
  151. package/src/modules/progress/api/jobs/route.ts +30 -20
  152. package/src/modules/query_index/di.ts +9 -7
  153. package/src/modules/query_index/lib/indexer.ts +13 -11
  154. package/src/modules/query_index/subscribers/upsert_one.ts +9 -1
  155. package/src/modules/sales/api/order-adjustments/route.ts +2 -1
  156. package/src/modules/sales/api/quote-adjustments/route.ts +2 -1
  157. package/src/modules/sales/commands/documents.ts +116 -0
  158. package/src/modules/sales/data/validators.ts +38 -0
  159. package/src/modules/sales/i18n/de.json +2 -0
  160. package/src/modules/sales/i18n/en.json +2 -0
  161. package/src/modules/sales/i18n/es.json +2 -0
  162. package/src/modules/sales/i18n/pl.json +2 -0
  163. package/src/modules/sales/lib/makeSalesLineRoute.ts +2 -1
  164. package/src/modules/staff/i18n/de.json +1 -0
  165. package/src/modules/staff/i18n/en.json +1 -0
  166. package/src/modules/staff/i18n/es.json +1 -0
  167. package/src/modules/staff/i18n/pl.json +1 -0
  168. package/src/modules/sync_excel/api/import/route.ts +141 -131
  169. package/src/modules/sync_excel/lib/adapters/customers.ts +1 -0
  170. package/src/modules/translations/components/TranslationManager.tsx +9 -4
  171. package/src/modules/workflows/lib/activity-executor.ts +4 -3
  172. package/src/modules/workflows/lib/event-trigger-service.ts +24 -11
  173. package/tsconfig.json +1 -0
@@ -14,8 +14,13 @@ import { useAppEvent } from "@open-mercato/ui/backend/injection/useAppEvent";
14
14
  import { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from "lucide-react";
15
15
  import { getMessageUiComponentRegistry } from "./utils/typeUiRegistry.js";
16
16
  import { DefaultMessageListItem } from "./defaults/DefaultMessageListItem.js";
17
+ import { getMessageListParticipantLabel } from "./messageListLabels.js";
17
18
  import { toErrorMessage } from "./message-detail/utils.js";
18
19
  import { useMessagesInboxBulkActions } from "./useMessagesInboxBulkActions.js";
20
+ import {
21
+ buildMessagesInboxFilters,
22
+ buildMessagesListParams
23
+ } from "./inboxFilters.js";
19
24
  function MessagesInboxPageClient() {
20
25
  const router = useRouter();
21
26
  const t = useT();
@@ -52,27 +57,13 @@ function MessagesInboxPageClient() {
52
57
  scopeVersion
53
58
  ],
54
59
  queryFn: async () => {
55
- const params = new URLSearchParams();
56
- params.set("folder", folder);
57
- params.set("page", String(page));
58
- params.set("pageSize", String(pageSize));
59
- if (search.trim()) {
60
- params.set("search", search.trim());
61
- }
62
- const status = typeof filterValues.status === "string" ? filterValues.status.trim() : "";
63
- const type = typeof filterValues.type === "string" ? filterValues.type.trim() : "";
64
- const hasObjects = typeof filterValues.hasObjects === "string" ? filterValues.hasObjects.trim() : "";
65
- const hasAttachments = typeof filterValues.hasAttachments === "string" ? filterValues.hasAttachments.trim() : "";
66
- const hasActions = typeof filterValues.hasActions === "string" ? filterValues.hasActions.trim() : "";
67
- const senderId = typeof filterValues.senderId === "string" ? filterValues.senderId.trim() : "";
68
- const since = typeof filterValues.since === "string" ? filterValues.since.trim() : "";
69
- if (status) params.set("status", status);
70
- if (type) params.set("type", type);
71
- if (hasObjects) params.set("hasObjects", hasObjects);
72
- if (hasAttachments) params.set("hasAttachments", hasAttachments);
73
- if (hasActions) params.set("hasActions", hasActions);
74
- if (senderId) params.set("senderId", senderId);
75
- if (since) params.set("since", since);
60
+ const params = buildMessagesListParams({
61
+ folder,
62
+ page,
63
+ pageSize,
64
+ search,
65
+ filterValues
66
+ });
76
67
  const call = await apiCall(`/api/messages?${params.toString()}`);
77
68
  if (!call.ok) {
78
69
  throw new Error(
@@ -121,6 +112,17 @@ function MessagesInboxPageClient() {
121
112
  }
122
113
  return map;
123
114
  }, [messageTypesQuery.data, t]);
115
+ const [senderOptions, setSenderOptions] = React.useState([]);
116
+ const senderOptionsScopeRef = React.useRef(scopeVersion);
117
+ const mergeSenderOptions = React.useCallback((incoming) => {
118
+ if (incoming.length === 0) return;
119
+ setSenderOptions((prev) => {
120
+ const map = /* @__PURE__ */ new Map();
121
+ for (const opt of prev) map.set(opt.value, opt);
122
+ for (const opt of incoming) map.set(opt.value, opt);
123
+ return Array.from(map.values());
124
+ });
125
+ }, []);
124
126
  const loadSenderOptions = React.useCallback(async (query) => {
125
127
  const params = new URLSearchParams();
126
128
  params.set("page", "1");
@@ -128,10 +130,18 @@ function MessagesInboxPageClient() {
128
130
  if (query && query.trim().length > 0) {
129
131
  params.set("search", query.trim());
130
132
  }
131
- const call = await apiCall(`/api/auth/users?${params.toString()}`);
133
+ const call = await apiCall(
134
+ `/api/auth/users?${params.toString()}`,
135
+ {
136
+ headers: {
137
+ "x-om-forbidden-redirect": "0"
138
+ }
139
+ }
140
+ ).catch(() => null);
141
+ if (!call) return [];
132
142
  if (!call.ok) return [];
133
143
  const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : [];
134
- return items.flatMap((item) => {
144
+ const next = items.flatMap((item) => {
135
145
  if (!item || typeof item.id !== "string" || item.id.trim().length === 0) return [];
136
146
  const name = typeof item.name === "string" && item.name.trim().length > 0 ? item.name.trim() : null;
137
147
  const email = typeof item.email === "string" && item.email.trim().length > 0 ? item.email.trim() : null;
@@ -142,7 +152,33 @@ function MessagesInboxPageClient() {
142
152
  description: email && email !== label ? email : null
143
153
  }];
144
154
  });
145
- }, []);
155
+ if (senderOptionsScopeRef.current === scopeVersion) {
156
+ mergeSenderOptions(next);
157
+ }
158
+ return next;
159
+ }, [mergeSenderOptions, scopeVersion]);
160
+ React.useEffect(() => {
161
+ const items = listQuery.data?.items ?? [];
162
+ const next = items.flatMap((item) => {
163
+ if (typeof item.senderUserId !== "string" || item.senderUserId.trim().length === 0) return [];
164
+ const name = typeof item.senderName === "string" && item.senderName.trim().length > 0 ? item.senderName.trim() : null;
165
+ const email = typeof item.senderEmail === "string" && item.senderEmail.trim().length > 0 ? item.senderEmail.trim() : null;
166
+ const label = name ?? email ?? item.senderUserId;
167
+ return [{
168
+ value: item.senderUserId,
169
+ label,
170
+ description: email && email !== label ? email : null
171
+ }];
172
+ });
173
+ mergeSenderOptions(next);
174
+ }, [listQuery.data?.items, mergeSenderOptions]);
175
+ React.useEffect(() => {
176
+ senderOptionsScopeRef.current = scopeVersion;
177
+ setSenderOptions([]);
178
+ loadSenderOptions().catch((error) => {
179
+ console.warn("[messages] Failed to load sender filter options", error);
180
+ });
181
+ }, [loadSenderOptions, scopeVersion]);
146
182
  const listItemComponentKeyByType = React.useMemo(() => {
147
183
  const map = {};
148
184
  for (const item of messageTypesQuery.data ?? []) {
@@ -155,69 +191,13 @@ function MessagesInboxPageClient() {
155
191
  value: item.type,
156
192
  label: t(item.labelKey, item.type)
157
193
  }));
158
- return [
159
- {
160
- id: "status",
161
- label: t("messages.filters.status", "Status"),
162
- type: "select",
163
- options: [
164
- { value: "", label: t("messages.filters.all", "All") },
165
- { value: "unread", label: t("messages.status.unread", "Unread") },
166
- { value: "read", label: t("messages.status.read", "Read") },
167
- { value: "archived", label: t("messages.status.archived", "Archived") }
168
- ]
169
- },
170
- {
171
- id: "type",
172
- label: t("messages.filters.type", "Type"),
173
- type: "select",
174
- options: [{ value: "", label: t("messages.filters.all", "All") }, ...typeOptions]
175
- },
176
- {
177
- id: "hasObjects",
178
- label: t("messages.filters.hasObjects", "Objects"),
179
- type: "select",
180
- options: [
181
- { value: "", label: t("messages.filters.all", "All") },
182
- { value: "true", label: t("common.yes", "Yes") },
183
- { value: "false", label: t("common.no", "No") }
184
- ]
185
- },
186
- {
187
- id: "hasAttachments",
188
- label: t("messages.filters.hasAttachments", "Attachments"),
189
- type: "select",
190
- options: [
191
- { value: "", label: t("messages.filters.all", "All") },
192
- { value: "true", label: t("common.yes", "Yes") },
193
- { value: "false", label: t("common.no", "No") }
194
- ]
195
- },
196
- {
197
- id: "hasActions",
198
- label: t("messages.filters.hasActions", "Actions"),
199
- type: "select",
200
- options: [
201
- { value: "", label: t("messages.filters.all", "All") },
202
- { value: "true", label: t("common.yes", "Yes") },
203
- { value: "false", label: t("common.no", "No") }
204
- ]
205
- },
206
- {
207
- id: "senderId",
208
- label: t("messages.filters.sender", "Sender"),
209
- type: "select",
210
- options: [{ value: "", label: t("messages.filters.all", "All") }],
211
- loadOptions: loadSenderOptions
212
- },
213
- {
214
- id: "since",
215
- label: t("messages.filters.since", "Sent after"),
216
- type: "text",
217
- placeholder: t("messages.filters.sincePlaceholder", "YYYY-MM-DDTHH:mm:ssZ")
218
- }
219
- ];
220
- }, [loadSenderOptions, messageTypesQuery.data, t]);
194
+ return buildMessagesInboxFilters({
195
+ t,
196
+ typeOptions,
197
+ senderOptions,
198
+ loadSenderOptions
199
+ });
200
+ }, [loadSenderOptions, messageTypesQuery.data, senderOptions, t]);
221
201
  const columns = React.useMemo(() => [
222
202
  {
223
203
  accessorKey: "message",
@@ -243,11 +223,12 @@ function MessagesInboxPageClient() {
243
223
  bodyFormat: "text",
244
224
  priority: item.priority ?? "normal",
245
225
  sentAt: item.sentAt ? new Date(item.sentAt) : null,
246
- senderName: item.senderName || item.senderEmail || item.senderUserId,
226
+ senderName: getMessageListParticipantLabel(item, folder, t),
247
227
  hasObjects: item.hasObjects,
248
228
  objectCount: item.objectCount,
249
229
  hasAttachments: item.hasAttachments,
250
230
  attachmentCount: item.attachmentCount,
231
+ recipientCount: item.recipientCount ?? 0,
251
232
  hasActions: item.hasActions,
252
233
  actionTaken: item.actionTaken ?? null,
253
234
  unread: item.status === "unread"
@@ -257,7 +238,7 @@ function MessagesInboxPageClient() {
257
238
  );
258
239
  }
259
240
  }
260
- ], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t]);
241
+ ], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t]);
261
242
  const folderOptions = React.useMemo(() => [
262
243
  { id: "inbox", label: t("messages.folder.inbox", "Inbox"), icon: Inbox },
263
244
  { id: "sent", label: t("messages.folder.sent", "Sent"), icon: Send },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/components/MessagesInboxPageClient.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\nimport { toErrorMessage } from './message-detail/utils'\nimport { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nexport function MessagesInboxPageClient() {\n const router = useRouter()\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n\n const invalidateMessageListQueries = React.useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }, [queryClient])\n\n useAppEvent('messages.message.*', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n useAppEvent('om:bridge:reconnected', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({\n folder,\n page,\n search,\n filterValues,\n })\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n queryFn: async () => {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const status = typeof filterValues.status === 'string' ? filterValues.status.trim() : ''\n const type = typeof filterValues.type === 'string' ? filterValues.type.trim() : ''\n const hasObjects = typeof filterValues.hasObjects === 'string' ? filterValues.hasObjects.trim() : ''\n const hasAttachments = typeof filterValues.hasAttachments === 'string' ? filterValues.hasAttachments.trim() : ''\n const hasActions = typeof filterValues.hasActions === 'string' ? filterValues.hasActions.trim() : ''\n const senderId = typeof filterValues.senderId === 'string' ? filterValues.senderId.trim() : ''\n const since = typeof filterValues.since === 'string' ? filterValues.since.trim() : ''\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (since) params.set('since', since)\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 0),\n }\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!listQuery.error) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [listQuery.error, t])\n\n React.useEffect(() => {\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(`/api/auth/users?${params.toString()}`)\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n return items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n }, [])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Objects'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Actions'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }],\n loadOptions: loadSenderOptions,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DDTHH:mm:ssZ'),\n },\n ]\n }, [loadSenderOptions, messageTypesQuery.data, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: item.senderName || item.senderEmail || item.senderUserId,\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 0\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n columns={columns}\n data={rows}\n bulkActions={bulkActions}\n selectionScopeKey={selectionScopeKey}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n injectionContext={injectionContext}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-dropdown mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n embedded\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\nimport { getMessageListParticipantLabel } from './messageListLabels'\nimport { toErrorMessage } from './message-detail/utils'\nimport { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'\nimport {\n buildMessagesInboxFilters,\n buildMessagesListParams,\n type SenderOption,\n} from './inboxFilters'\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n recipientCount?: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nexport function MessagesInboxPageClient() {\n const router = useRouter()\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n\n const invalidateMessageListQueries = React.useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }, [queryClient])\n\n useAppEvent('messages.message.*', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n useAppEvent('om:bridge:reconnected', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({\n folder,\n page,\n search,\n filterValues,\n })\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n queryFn: async () => {\n const params = buildMessagesListParams({\n folder,\n page,\n pageSize,\n search,\n filterValues,\n })\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 0),\n }\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!listQuery.error) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [listQuery.error, t])\n\n React.useEffect(() => {\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const [senderOptions, setSenderOptions] = React.useState<SenderOption[]>([])\n const senderOptionsScopeRef = React.useRef(scopeVersion)\n\n const mergeSenderOptions = React.useCallback((incoming: SenderOption[]) => {\n if (incoming.length === 0) return\n setSenderOptions((prev) => {\n const map = new Map<string, SenderOption>()\n for (const opt of prev) map.set(opt.value, opt)\n for (const opt of incoming) map.set(opt.value, opt)\n return Array.from(map.values())\n })\n }, [])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(\n `/api/auth/users?${params.toString()}`,\n {\n headers: {\n 'x-om-forbidden-redirect': '0',\n },\n },\n ).catch(() => null)\n if (!call) return []\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n const next: SenderOption[] = items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n if (senderOptionsScopeRef.current === scopeVersion) {\n mergeSenderOptions(next)\n }\n return next\n }, [mergeSenderOptions, scopeVersion])\n\n React.useEffect(() => {\n const items = listQuery.data?.items ?? []\n const next = items.flatMap((item): SenderOption[] => {\n if (typeof item.senderUserId !== 'string' || item.senderUserId.trim().length === 0) return []\n const name = typeof item.senderName === 'string' && item.senderName.trim().length > 0\n ? item.senderName.trim()\n : null\n const email = typeof item.senderEmail === 'string' && item.senderEmail.trim().length > 0\n ? item.senderEmail.trim()\n : null\n const label = name ?? email ?? item.senderUserId\n return [{\n value: item.senderUserId,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n mergeSenderOptions(next)\n }, [listQuery.data?.items, mergeSenderOptions])\n\n React.useEffect(() => {\n senderOptionsScopeRef.current = scopeVersion\n setSenderOptions([])\n loadSenderOptions().catch((error: unknown) => {\n console.warn('[messages] Failed to load sender filter options', error)\n })\n }, [loadSenderOptions, scopeVersion])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n })\n }, [loadSenderOptions, messageTypesQuery.data, senderOptions, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: getMessageListParticipantLabel(item, folder, t),\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n recipientCount: item.recipientCount ?? 0,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 0\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n columns={columns}\n data={rows}\n bulkActions={bulkActions}\n selectionScopeKey={selectionScopeKey}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n injectionContext={injectionContext}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-dropdown mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n embedded\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useT } from "@open-mercato/shared/lib/i18n/context";
4
4
  import { cn } from "@open-mercato/shared/lib/utils";
5
5
  import { CheckCircle2, FileText, Paperclip, Zap } from "lucide-react";
@@ -24,15 +24,15 @@ function formatSentTime(value) {
24
24
  }
25
25
  function truncateWords(value, maxWords) {
26
26
  const normalized = value.trim().replace(/\s+/g, " ");
27
- if (!normalized) return "\u2014";
27
+ if (!normalized) return "";
28
28
  const words = normalized.split(" ");
29
29
  if (words.length <= maxWords) return normalized;
30
30
  return `${words.slice(0, maxWords).join(" ")}...`;
31
31
  }
32
32
  function DefaultMessageListItem({ message, onClick }) {
33
33
  const t = useT();
34
- const senderLabel = message.senderName || "\u2014";
35
- const subject = message.subject || "\u2014";
34
+ const senderLabel = message.senderName?.trim() || t("messages.list.noRecipient", "(No recipient)");
35
+ const subject = message.subject.trim() || t("messages.list.noSubject", "(No subject)");
36
36
  const absoluteSentAt = formatDateTime(message.sentAt);
37
37
  const sentAtLabel = formatSentTime(message.sentAt);
38
38
  const bodyPreview = truncateWords(message.body || "", 16);
@@ -61,8 +61,10 @@ function DefaultMessageListItem({ message, onClick }) {
61
61
  /* @__PURE__ */ jsx("span", { className: cn("w-40 flex-shrink-0 truncate text-sm md:w-56", message.unread ? "font-semibold" : "font-normal"), children: senderLabel }),
62
62
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: [
63
63
  /* @__PURE__ */ jsx("span", { className: cn("truncate text-foreground", message.unread ? "font-semibold" : "font-normal"), children: subject }),
64
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
65
- /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: bodyPreview })
64
+ bodyPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
66
+ /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: bodyPreview })
67
+ ] }) : null
66
68
  ] }),
67
69
  /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center gap-1.5 text-muted-foreground", children: [
68
70
  message.hasObjects ? /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5", "aria-hidden": true }) : null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/messages/components/defaults/DefaultMessageListItem.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport type { MessageListItemProps } from '@open-mercato/shared/modules/messages/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { CheckCircle2, FileText, Paperclip, Zap } from 'lucide-react'\n\nfunction formatDateTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatSentTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n\n const now = new Date()\n const isSameDay = now.toDateString() === value.toDateString()\n if (isSameDay) {\n return value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n }\n\n const isSameYear = now.getFullYear() === value.getFullYear()\n if (isSameYear) {\n return value.toLocaleDateString([], { month: 'short', day: 'numeric' })\n }\n\n return value.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction truncateWords(value: string, maxWords: number): string {\n const normalized = value.trim().replace(/\\s+/g, ' ')\n if (!normalized) return '\u2014'\n const words = normalized.split(' ')\n if (words.length <= maxWords) return normalized\n return `${words.slice(0, maxWords).join(' ')}...`\n}\n\nexport function DefaultMessageListItem({ message, onClick }: MessageListItemProps) {\n const t = useT()\n const senderLabel = message.senderName || '\u2014'\n const subject = message.subject || '\u2014'\n const absoluteSentAt = formatDateTime(message.sentAt)\n const sentAtLabel = formatSentTime(message.sentAt)\n const bodyPreview = truncateWords(message.body || '', 16)\n\n return (\n <div\n className={cn(\n 'group min-w-0 w-full cursor-pointer rounded-md px-2 py-2 transition-colors hover:bg-muted/50',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n )}\n role=\"button\"\n tabIndex={0}\n aria-label={t('messages.openMessageDetailedA11y', 'Open message {subject} from {sender}, sent {sentAt}', {\n subject,\n sender: senderLabel,\n sentAt: absoluteSentAt,\n })}\n onClick={onClick}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onClick()\n }\n }}\n >\n <div className=\"flex items-center gap-3\">\n <span className={cn('w-40 flex-shrink-0 truncate text-sm md:w-56', message.unread ? 'font-semibold' : 'font-normal')}>\n {senderLabel}\n </span>\n\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5 text-sm\">\n <span className={cn('truncate text-foreground', message.unread ? 'font-semibold' : 'font-normal')}>\n {subject}\n </span>\n <span className=\"text-muted-foreground\">-</span>\n <span className=\"truncate text-muted-foreground\">{bodyPreview}</span>\n </div>\n\n <div className=\"flex flex-shrink-0 items-center gap-1.5 text-muted-foreground\">\n {message.hasObjects ? <FileText className=\"h-3.5 w-3.5\" aria-hidden /> : null}\n {message.hasAttachments ? <Paperclip className=\"h-3.5 w-3.5\" aria-hidden /> : null}\n {message.hasActions && !message.actionTaken ? (\n <Zap className=\"h-3.5 w-3.5 text-amber-600\" aria-hidden />\n ) : null}\n {message.actionTaken ? <CheckCircle2 className=\"h-3.5 w-3.5 text-green-600\" aria-hidden /> : null}\n </div>\n\n <span\n className={cn('flex-shrink-0 text-xs', message.unread ? 'font-semibold text-foreground' : 'font-normal text-muted-foreground')}\n title={absoluteSentAt}\n >\n {sentAtLabel}\n </span>\n </div>\n </div>\n )\n}\n\nexport default DefaultMessageListItem\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport type { MessageListItemProps } from '@open-mercato/shared/modules/messages/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { CheckCircle2, FileText, Paperclip, Zap } from 'lucide-react'\n\nfunction formatDateTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatSentTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n\n const now = new Date()\n const isSameDay = now.toDateString() === value.toDateString()\n if (isSameDay) {\n return value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n }\n\n const isSameYear = now.getFullYear() === value.getFullYear()\n if (isSameYear) {\n return value.toLocaleDateString([], { month: 'short', day: 'numeric' })\n }\n\n return value.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction truncateWords(value: string, maxWords: number): string {\n const normalized = value.trim().replace(/\\s+/g, ' ')\n if (!normalized) return ''\n const words = normalized.split(' ')\n if (words.length <= maxWords) return normalized\n return `${words.slice(0, maxWords).join(' ')}...`\n}\n\nexport function DefaultMessageListItem({ message, onClick }: MessageListItemProps) {\n const t = useT()\n const senderLabel = message.senderName?.trim() || t('messages.list.noRecipient', '(No recipient)')\n const subject = message.subject.trim() || t('messages.list.noSubject', '(No subject)')\n const absoluteSentAt = formatDateTime(message.sentAt)\n const sentAtLabel = formatSentTime(message.sentAt)\n const bodyPreview = truncateWords(message.body || '', 16)\n\n return (\n <div\n className={cn(\n 'group min-w-0 w-full cursor-pointer rounded-md px-2 py-2 transition-colors hover:bg-muted/50',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n )}\n role=\"button\"\n tabIndex={0}\n aria-label={t('messages.openMessageDetailedA11y', 'Open message {subject} from {sender}, sent {sentAt}', {\n subject,\n sender: senderLabel,\n sentAt: absoluteSentAt,\n })}\n onClick={onClick}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onClick()\n }\n }}\n >\n <div className=\"flex items-center gap-3\">\n <span className={cn('w-40 flex-shrink-0 truncate text-sm md:w-56', message.unread ? 'font-semibold' : 'font-normal')}>\n {senderLabel}\n </span>\n\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5 text-sm\">\n <span className={cn('truncate text-foreground', message.unread ? 'font-semibold' : 'font-normal')}>\n {subject}\n </span>\n {bodyPreview ? (\n <>\n <span className=\"text-muted-foreground\">-</span>\n <span className=\"truncate text-muted-foreground\">{bodyPreview}</span>\n </>\n ) : null}\n </div>\n\n <div className=\"flex flex-shrink-0 items-center gap-1.5 text-muted-foreground\">\n {message.hasObjects ? <FileText className=\"h-3.5 w-3.5\" aria-hidden /> : null}\n {message.hasAttachments ? <Paperclip className=\"h-3.5 w-3.5\" aria-hidden /> : null}\n {message.hasActions && !message.actionTaken ? (\n <Zap className=\"h-3.5 w-3.5 text-amber-600\" aria-hidden />\n ) : null}\n {message.actionTaken ? <CheckCircle2 className=\"h-3.5 w-3.5 text-green-600\" aria-hidden /> : null}\n </div>\n\n <span\n className={cn('flex-shrink-0 text-xs', message.unread ? 'font-semibold text-foreground' : 'font-normal text-muted-foreground')}\n title={absoluteSentAt}\n >\n {sentAtLabel}\n </span>\n </div>\n </div>\n )\n}\n\nexport default DefaultMessageListItem\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,133 @@
1
+ function buildMessagesInboxFilters({
2
+ t,
3
+ typeOptions,
4
+ senderOptions,
5
+ loadSenderOptions
6
+ }) {
7
+ const senderLabel = (val) => {
8
+ const match = senderOptions.find((opt) => opt.value === val);
9
+ return match?.label ?? val;
10
+ };
11
+ return [
12
+ {
13
+ id: "status",
14
+ label: t("messages.filters.status", "Status"),
15
+ type: "select",
16
+ options: [
17
+ { value: "", label: t("messages.filters.all", "All") },
18
+ { value: "unread", label: t("messages.status.unread", "Unread") },
19
+ { value: "read", label: t("messages.status.read", "Read") },
20
+ { value: "archived", label: t("messages.status.archived", "Archived") }
21
+ ]
22
+ },
23
+ {
24
+ id: "type",
25
+ label: t("messages.filters.type", "Type"),
26
+ type: "select",
27
+ options: [{ value: "", label: t("messages.filters.all", "All") }, ...typeOptions]
28
+ },
29
+ {
30
+ id: "hasObjects",
31
+ label: t("messages.filters.hasObjects", "Has objects"),
32
+ type: "select",
33
+ options: [
34
+ { value: "", label: t("messages.filters.all", "All") },
35
+ { value: "true", label: t("common.yes", "Yes") },
36
+ { value: "false", label: t("common.no", "No") }
37
+ ]
38
+ },
39
+ {
40
+ id: "hasAttachments",
41
+ label: t("messages.filters.hasAttachments", "Has attachments"),
42
+ type: "select",
43
+ options: [
44
+ { value: "", label: t("messages.filters.all", "All") },
45
+ { value: "true", label: t("common.yes", "Yes") },
46
+ { value: "false", label: t("common.no", "No") }
47
+ ]
48
+ },
49
+ {
50
+ id: "hasActions",
51
+ label: t("messages.filters.hasActions", "Has actions"),
52
+ type: "select",
53
+ options: [
54
+ { value: "", label: t("messages.filters.all", "All") },
55
+ { value: "true", label: t("common.yes", "Yes") },
56
+ { value: "false", label: t("common.no", "No") }
57
+ ]
58
+ },
59
+ {
60
+ id: "senderId",
61
+ label: t("messages.filters.sender", "Sender"),
62
+ type: "combobox",
63
+ placeholder: t("messages.filters.senderEmpty", "Type to search users"),
64
+ options: senderOptions,
65
+ loadOptions: loadSenderOptions,
66
+ formatValue: senderLabel,
67
+ formatDescription: (val) => senderOptions.find((opt) => opt.value === val)?.description ?? null
68
+ },
69
+ {
70
+ id: "since",
71
+ label: t("messages.filters.since", "Sent after"),
72
+ type: "text",
73
+ placeholder: t("messages.filters.sincePlaceholder", "YYYY-MM-DD")
74
+ }
75
+ ];
76
+ }
77
+ const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
78
+ function normalizeMessagesSinceValue(input) {
79
+ const trimmed = input.trim();
80
+ if (!trimmed) return null;
81
+ if (DATE_ONLY_PATTERN.test(trimmed)) {
82
+ const parsed2 = /* @__PURE__ */ new Date(`${trimmed}T00:00:00.000Z`);
83
+ if (Number.isNaN(parsed2.getTime())) return null;
84
+ if (parsed2.toISOString().slice(0, 10) !== trimmed) return null;
85
+ return parsed2.toISOString();
86
+ }
87
+ const parsed = new Date(trimmed);
88
+ if (Number.isNaN(parsed.getTime())) return null;
89
+ return parsed.toISOString();
90
+ }
91
+ function buildMessagesListParams({
92
+ folder,
93
+ page,
94
+ pageSize,
95
+ search,
96
+ filterValues
97
+ }) {
98
+ const params = new URLSearchParams();
99
+ params.set("folder", folder);
100
+ params.set("page", String(page));
101
+ params.set("pageSize", String(pageSize));
102
+ if (search.trim()) {
103
+ params.set("search", search.trim());
104
+ }
105
+ const stringValue = (id) => {
106
+ const raw = filterValues[id];
107
+ return typeof raw === "string" ? raw.trim() : "";
108
+ };
109
+ const status = stringValue("status");
110
+ const type = stringValue("type");
111
+ const hasObjects = stringValue("hasObjects");
112
+ const hasAttachments = stringValue("hasAttachments");
113
+ const hasActions = stringValue("hasActions");
114
+ const senderId = stringValue("senderId");
115
+ const sinceRaw = stringValue("since");
116
+ if (status) params.set("status", status);
117
+ if (type) params.set("type", type);
118
+ if (hasObjects) params.set("hasObjects", hasObjects);
119
+ if (hasAttachments) params.set("hasAttachments", hasAttachments);
120
+ if (hasActions) params.set("hasActions", hasActions);
121
+ if (senderId) params.set("senderId", senderId);
122
+ if (sinceRaw) {
123
+ const normalized = normalizeMessagesSinceValue(sinceRaw);
124
+ if (normalized) params.set("since", normalized);
125
+ }
126
+ return params;
127
+ }
128
+ export {
129
+ buildMessagesInboxFilters,
130
+ buildMessagesListParams,
131
+ normalizeMessagesSinceValue
132
+ };
133
+ //# sourceMappingURL=inboxFilters.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/messages/components/inboxFilters.ts"],
4
+ "sourcesContent": ["import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\n\ntype Translator = (key: string, fallback?: string) => string\n\nexport type SenderOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type MessagesInboxFilterContext = {\n t: Translator\n typeOptions: { value: string; label: string }[]\n senderOptions: SenderOption[]\n loadSenderOptions: (query?: string) => Promise<SenderOption[]>\n}\n\nexport function buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n}: MessagesInboxFilterContext): FilterDef[] {\n const senderLabel = (val: string): string => {\n const match = senderOptions.find((opt) => opt.value === val)\n return match?.label ?? val\n }\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Has objects'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Has attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Has actions'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'combobox',\n placeholder: t('messages.filters.senderEmpty', 'Type to search users'),\n options: senderOptions,\n loadOptions: loadSenderOptions,\n formatValue: senderLabel,\n formatDescription: (val: string) =>\n senderOptions.find((opt) => opt.value === val)?.description ?? null,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DD'),\n },\n ]\n}\n\nconst DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\nexport function normalizeMessagesSinceValue(input: string): string | null {\n const trimmed = input.trim()\n if (!trimmed) return null\n if (DATE_ONLY_PATTERN.test(trimmed)) {\n const parsed = new Date(`${trimmed}T00:00:00.000Z`)\n if (Number.isNaN(parsed.getTime())) return null\n if (parsed.toISOString().slice(0, 10) !== trimmed) return null\n return parsed.toISOString()\n }\n const parsed = new Date(trimmed)\n if (Number.isNaN(parsed.getTime())) return null\n return parsed.toISOString()\n}\n\nexport type BuildMessagesListParamsOptions = {\n folder: string\n page: number\n pageSize: number\n search: string\n filterValues: FilterValues\n}\n\nexport function buildMessagesListParams({\n folder,\n page,\n pageSize,\n search,\n filterValues,\n}: BuildMessagesListParamsOptions): URLSearchParams {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const stringValue = (id: string): string => {\n const raw = filterValues[id]\n return typeof raw === 'string' ? raw.trim() : ''\n }\n\n const status = stringValue('status')\n const type = stringValue('type')\n const hasObjects = stringValue('hasObjects')\n const hasAttachments = stringValue('hasAttachments')\n const hasActions = stringValue('hasActions')\n const senderId = stringValue('senderId')\n const sinceRaw = stringValue('since')\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (sinceRaw) {\n const normalized = normalizeMessagesSinceValue(sinceRaw)\n if (normalized) params.set('since', normalized)\n }\n\n return params\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["parsed"]
7
+ }
@@ -0,0 +1,14 @@
1
+ function normalizeLabel(value) {
2
+ const normalized = value?.trim();
3
+ return normalized ? normalized : null;
4
+ }
5
+ function getMessageListParticipantLabel(item, folder, t) {
6
+ if ((folder === "sent" || folder === "drafts") && Number(item.recipientCount ?? 0) <= 0) {
7
+ return t("messages.list.noRecipient", "(No recipient)");
8
+ }
9
+ return normalizeLabel(item.senderName) ?? normalizeLabel(item.senderEmail) ?? item.senderUserId;
10
+ }
11
+ export {
12
+ getMessageListParticipantLabel
13
+ };
14
+ //# sourceMappingURL=messageListLabels.js.map