@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/queue-runner.js +15 -3
- package/dist/helpers/integration/queue-runner.js.map +2 -2
- package/dist/helpers/integration/queue.js +1 -1
- package/dist/helpers/integration/queue.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +24 -1
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/auth/api/roles/acl/route.js +37 -1
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/roles/route.js +36 -2
- package/dist/modules/auth/api/roles/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +36 -1
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +101 -13
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/roles/[id]/edit/page.js +32 -14
- package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +1 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +1 -1
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/backend/users/roleOptions.js +1 -1
- package/dist/modules/auth/backend/users/roleOptions.js.map +2 -2
- package/dist/modules/auth/commands/roles.js +8 -0
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/auth/commands/users.js +3 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/components/AclEditor.js +53 -26
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/auth/frontend/login.js +35 -0
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/auth/lib/grantChecks.js +97 -2
- package/dist/modules/auth/lib/grantChecks.js.map +2 -2
- package/dist/modules/business_rules/lib/expression-evaluator.js +9 -7
- package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
- package/dist/modules/business_rules/lib/rule-evaluator.js +3 -3
- package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
- package/dist/modules/customers/ai-agents.js +10 -1
- package/dist/modules/customers/ai-agents.js.map +2 -2
- package/dist/modules/customers/ai-tools/settings-pack.js +55 -1
- package/dist/modules/customers/ai-tools/settings-pack.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +13 -5
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/lib/personCompanyLinkTable.js +4 -30
- package/dist/modules/customers/lib/personCompanyLinkTable.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260521175027.js +12 -0
- package/dist/modules/customers/migrations/Migration20260521175027.js.map +7 -0
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +41 -12
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/options.js +2 -0
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +77 -58
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +3 -2
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +8 -3
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +15 -13
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/data/validators.js +9 -6
- package/dist/modules/integrations/data/validators.js.map +2 -2
- package/dist/modules/messages/api/route.js +2 -2
- package/dist/modules/messages/api/route.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +42 -2
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +70 -89
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +8 -6
- package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +2 -2
- package/dist/modules/messages/components/inboxFilters.js +133 -0
- package/dist/modules/messages/components/inboxFilters.js.map +7 -0
- package/dist/modules/messages/components/messageListLabels.js +14 -0
- package/dist/modules/messages/components/messageListLabels.js.map +7 -0
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js +47 -23
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +2 -2
- package/dist/modules/messages/data/validators.js +8 -6
- package/dist/modules/messages/data/validators.js.map +2 -2
- package/dist/modules/progress/api/jobs/route.js +26 -16
- package/dist/modules/progress/api/jobs/route.js.map +2 -2
- package/dist/modules/query_index/di.js +8 -6
- package/dist/modules/query_index/di.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +12 -10
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/subscribers/upsert_one.js +7 -1
- package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
- package/dist/modules/sales/api/order-adjustments/route.js.map +2 -2
- package/dist/modules/sales/api/quote-adjustments/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +104 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/data/validators.js +20 -1
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
- package/dist/modules/sync_excel/api/import/route.js +126 -116
- package/dist/modules/sync_excel/api/import/route.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +1 -0
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +9 -4
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +23 -11
- package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
- package/jest.config.cjs +4 -1
- package/package.json +15 -16
- package/src/helpers/integration/queue-runner.ts +21 -2
- package/src/helpers/integration/queue.ts +1 -1
- package/src/helpers/integration/salesUi.ts +26 -1
- package/src/modules/auth/api/roles/acl/route.ts +39 -1
- package/src/modules/auth/api/roles/route.ts +38 -2
- package/src/modules/auth/api/users/acl/route.ts +41 -1
- package/src/modules/auth/api/users/route.ts +105 -12
- package/src/modules/auth/backend/roles/[id]/edit/page.tsx +35 -13
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +1 -1
- package/src/modules/auth/backend/users/create/page.tsx +1 -1
- package/src/modules/auth/backend/users/roleOptions.ts +2 -1
- package/src/modules/auth/commands/roles.ts +8 -0
- package/src/modules/auth/commands/users.ts +3 -0
- package/src/modules/auth/components/AclEditor.tsx +50 -24
- package/src/modules/auth/frontend/login.tsx +40 -0
- package/src/modules/auth/lib/grantChecks.ts +116 -2
- package/src/modules/business_rules/lib/expression-evaluator.ts +9 -13
- package/src/modules/business_rules/lib/rule-evaluator.ts +3 -3
- package/src/modules/customers/ai-agents.ts +10 -1
- package/src/modules/customers/ai-tools/settings-pack.ts +62 -1
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +18 -7
- package/src/modules/customers/lib/personCompanyLinkTable.ts +33 -41
- package/src/modules/customers/migrations/Migration20260521175027.ts +39 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +38 -12
- package/src/modules/data_sync/api/options.ts +2 -0
- package/src/modules/data_sync/api/run.ts +80 -61
- package/src/modules/data_sync/backend/data-sync/page.tsx +10 -0
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +17 -3
- package/src/modules/data_sync/i18n/de.json +3 -0
- package/src/modules/data_sync/i18n/en.json +3 -0
- package/src/modules/data_sync/i18n/es.json +3 -0
- package/src/modules/data_sync/i18n/pl.json +3 -0
- package/src/modules/data_sync/lib/adapter.ts +9 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +15 -13
- package/src/modules/integrations/data/validators.ts +5 -4
- package/src/modules/messages/api/route.ts +2 -2
- package/src/modules/messages/commands/messages.ts +54 -2
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +80 -92
- package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +9 -5
- package/src/modules/messages/components/inboxFilters.ts +162 -0
- package/src/modules/messages/components/messageListLabels.ts +29 -0
- package/src/modules/messages/components/useMessagesInboxBulkActions.ts +58 -26
- package/src/modules/messages/data/validators.ts +7 -5
- package/src/modules/messages/i18n/de.json +7 -4
- package/src/modules/messages/i18n/en.json +7 -4
- package/src/modules/messages/i18n/es.json +7 -4
- package/src/modules/messages/i18n/pl.json +7 -4
- package/src/modules/progress/api/jobs/route.ts +30 -20
- package/src/modules/query_index/di.ts +9 -7
- package/src/modules/query_index/lib/indexer.ts +13 -11
- package/src/modules/query_index/subscribers/upsert_one.ts +9 -1
- package/src/modules/sales/api/order-adjustments/route.ts +2 -1
- package/src/modules/sales/api/quote-adjustments/route.ts +2 -1
- package/src/modules/sales/commands/documents.ts +116 -0
- package/src/modules/sales/data/validators.ts +38 -0
- package/src/modules/sales/i18n/de.json +2 -0
- package/src/modules/sales/i18n/en.json +2 -0
- package/src/modules/sales/i18n/es.json +2 -0
- package/src/modules/sales/i18n/pl.json +2 -0
- package/src/modules/sales/lib/makeSalesLineRoute.ts +2 -1
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/sync_excel/api/import/route.ts +141 -131
- package/src/modules/sync_excel/lib/adapters/customers.ts +1 -0
- package/src/modules/translations/components/TranslationManager.tsx +9 -4
- package/src/modules/workflows/lib/activity-executor.ts +4 -3
- package/src/modules/workflows/lib/event-trigger-service.ts +24 -11
- package/tsconfig.json +1 -0
|
@@ -16,8 +16,14 @@ 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
|
+
import { getMessageListParticipantLabel } from './messageListLabels'
|
|
19
20
|
import { toErrorMessage } from './message-detail/utils'
|
|
20
21
|
import { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'
|
|
22
|
+
import {
|
|
23
|
+
buildMessagesInboxFilters,
|
|
24
|
+
buildMessagesListParams,
|
|
25
|
+
type SenderOption,
|
|
26
|
+
} from './inboxFilters'
|
|
21
27
|
|
|
22
28
|
type MessageListItem = {
|
|
23
29
|
id: string
|
|
@@ -33,6 +39,7 @@ type MessageListItem = {
|
|
|
33
39
|
objectCount: number
|
|
34
40
|
hasAttachments: boolean
|
|
35
41
|
attachmentCount: number
|
|
42
|
+
recipientCount?: number
|
|
36
43
|
hasActions: boolean
|
|
37
44
|
actionTaken?: string | null
|
|
38
45
|
sentAt?: string | null
|
|
@@ -104,30 +111,13 @@ export function MessagesInboxPageClient() {
|
|
|
104
111
|
scopeVersion,
|
|
105
112
|
],
|
|
106
113
|
queryFn: async () => {
|
|
107
|
-
const params =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const status = typeof filterValues.status === 'string' ? filterValues.status.trim() : ''
|
|
117
|
-
const type = typeof filterValues.type === 'string' ? filterValues.type.trim() : ''
|
|
118
|
-
const hasObjects = typeof filterValues.hasObjects === 'string' ? filterValues.hasObjects.trim() : ''
|
|
119
|
-
const hasAttachments = typeof filterValues.hasAttachments === 'string' ? filterValues.hasAttachments.trim() : ''
|
|
120
|
-
const hasActions = typeof filterValues.hasActions === 'string' ? filterValues.hasActions.trim() : ''
|
|
121
|
-
const senderId = typeof filterValues.senderId === 'string' ? filterValues.senderId.trim() : ''
|
|
122
|
-
const since = typeof filterValues.since === 'string' ? filterValues.since.trim() : ''
|
|
123
|
-
|
|
124
|
-
if (status) params.set('status', status)
|
|
125
|
-
if (type) params.set('type', type)
|
|
126
|
-
if (hasObjects) params.set('hasObjects', hasObjects)
|
|
127
|
-
if (hasAttachments) params.set('hasAttachments', hasAttachments)
|
|
128
|
-
if (hasActions) params.set('hasActions', hasActions)
|
|
129
|
-
if (senderId) params.set('senderId', senderId)
|
|
130
|
-
if (since) params.set('since', since)
|
|
114
|
+
const params = buildMessagesListParams({
|
|
115
|
+
folder,
|
|
116
|
+
page,
|
|
117
|
+
pageSize,
|
|
118
|
+
search,
|
|
119
|
+
filterValues,
|
|
120
|
+
})
|
|
131
121
|
|
|
132
122
|
const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)
|
|
133
123
|
if (!call.ok) {
|
|
@@ -189,6 +179,19 @@ export function MessagesInboxPageClient() {
|
|
|
189
179
|
return map
|
|
190
180
|
}, [messageTypesQuery.data, t])
|
|
191
181
|
|
|
182
|
+
const [senderOptions, setSenderOptions] = React.useState<SenderOption[]>([])
|
|
183
|
+
const senderOptionsScopeRef = React.useRef(scopeVersion)
|
|
184
|
+
|
|
185
|
+
const mergeSenderOptions = React.useCallback((incoming: SenderOption[]) => {
|
|
186
|
+
if (incoming.length === 0) return
|
|
187
|
+
setSenderOptions((prev) => {
|
|
188
|
+
const map = new Map<string, SenderOption>()
|
|
189
|
+
for (const opt of prev) map.set(opt.value, opt)
|
|
190
|
+
for (const opt of incoming) map.set(opt.value, opt)
|
|
191
|
+
return Array.from(map.values())
|
|
192
|
+
})
|
|
193
|
+
}, [])
|
|
194
|
+
|
|
192
195
|
const loadSenderOptions = React.useCallback(async (query?: string) => {
|
|
193
196
|
const params = new URLSearchParams()
|
|
194
197
|
params.set('page', '1')
|
|
@@ -197,11 +200,19 @@ export function MessagesInboxPageClient() {
|
|
|
197
200
|
params.set('search', query.trim())
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
const call = await apiCall<{ items?: UserListItem[] }>(
|
|
203
|
+
const call = await apiCall<{ items?: UserListItem[] }>(
|
|
204
|
+
`/api/auth/users?${params.toString()}`,
|
|
205
|
+
{
|
|
206
|
+
headers: {
|
|
207
|
+
'x-om-forbidden-redirect': '0',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
).catch(() => null)
|
|
211
|
+
if (!call) return []
|
|
201
212
|
if (!call.ok) return []
|
|
202
213
|
|
|
203
214
|
const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []
|
|
204
|
-
|
|
215
|
+
const next: SenderOption[] = items.flatMap((item) => {
|
|
205
216
|
if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []
|
|
206
217
|
const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null
|
|
207
218
|
const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null
|
|
@@ -212,7 +223,39 @@ export function MessagesInboxPageClient() {
|
|
|
212
223
|
description: email && email !== label ? email : null,
|
|
213
224
|
}]
|
|
214
225
|
})
|
|
215
|
-
|
|
226
|
+
if (senderOptionsScopeRef.current === scopeVersion) {
|
|
227
|
+
mergeSenderOptions(next)
|
|
228
|
+
}
|
|
229
|
+
return next
|
|
230
|
+
}, [mergeSenderOptions, scopeVersion])
|
|
231
|
+
|
|
232
|
+
React.useEffect(() => {
|
|
233
|
+
const items = listQuery.data?.items ?? []
|
|
234
|
+
const next = items.flatMap((item): SenderOption[] => {
|
|
235
|
+
if (typeof item.senderUserId !== 'string' || item.senderUserId.trim().length === 0) return []
|
|
236
|
+
const name = typeof item.senderName === 'string' && item.senderName.trim().length > 0
|
|
237
|
+
? item.senderName.trim()
|
|
238
|
+
: null
|
|
239
|
+
const email = typeof item.senderEmail === 'string' && item.senderEmail.trim().length > 0
|
|
240
|
+
? item.senderEmail.trim()
|
|
241
|
+
: null
|
|
242
|
+
const label = name ?? email ?? item.senderUserId
|
|
243
|
+
return [{
|
|
244
|
+
value: item.senderUserId,
|
|
245
|
+
label,
|
|
246
|
+
description: email && email !== label ? email : null,
|
|
247
|
+
}]
|
|
248
|
+
})
|
|
249
|
+
mergeSenderOptions(next)
|
|
250
|
+
}, [listQuery.data?.items, mergeSenderOptions])
|
|
251
|
+
|
|
252
|
+
React.useEffect(() => {
|
|
253
|
+
senderOptionsScopeRef.current = scopeVersion
|
|
254
|
+
setSenderOptions([])
|
|
255
|
+
loadSenderOptions().catch((error: unknown) => {
|
|
256
|
+
console.warn('[messages] Failed to load sender filter options', error)
|
|
257
|
+
})
|
|
258
|
+
}, [loadSenderOptions, scopeVersion])
|
|
216
259
|
|
|
217
260
|
const listItemComponentKeyByType = React.useMemo(() => {
|
|
218
261
|
const map: Record<string, string | null> = {}
|
|
@@ -228,69 +271,13 @@ export function MessagesInboxPageClient() {
|
|
|
228
271
|
label: t(item.labelKey, item.type),
|
|
229
272
|
}))
|
|
230
273
|
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
{ value: 'unread', label: t('messages.status.unread', 'Unread') },
|
|
239
|
-
{ value: 'read', label: t('messages.status.read', 'Read') },
|
|
240
|
-
{ value: 'archived', label: t('messages.status.archived', 'Archived') },
|
|
241
|
-
],
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
id: 'type',
|
|
245
|
-
label: t('messages.filters.type', 'Type'),
|
|
246
|
-
type: 'select',
|
|
247
|
-
options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
id: 'hasObjects',
|
|
251
|
-
label: t('messages.filters.hasObjects', 'Objects'),
|
|
252
|
-
type: 'select',
|
|
253
|
-
options: [
|
|
254
|
-
{ value: '', label: t('messages.filters.all', 'All') },
|
|
255
|
-
{ value: 'true', label: t('common.yes', 'Yes') },
|
|
256
|
-
{ value: 'false', label: t('common.no', 'No') },
|
|
257
|
-
],
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
id: 'hasAttachments',
|
|
261
|
-
label: t('messages.filters.hasAttachments', 'Attachments'),
|
|
262
|
-
type: 'select',
|
|
263
|
-
options: [
|
|
264
|
-
{ value: '', label: t('messages.filters.all', 'All') },
|
|
265
|
-
{ value: 'true', label: t('common.yes', 'Yes') },
|
|
266
|
-
{ value: 'false', label: t('common.no', 'No') },
|
|
267
|
-
],
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
id: 'hasActions',
|
|
271
|
-
label: t('messages.filters.hasActions', 'Actions'),
|
|
272
|
-
type: 'select',
|
|
273
|
-
options: [
|
|
274
|
-
{ value: '', label: t('messages.filters.all', 'All') },
|
|
275
|
-
{ value: 'true', label: t('common.yes', 'Yes') },
|
|
276
|
-
{ value: 'false', label: t('common.no', 'No') },
|
|
277
|
-
],
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
id: 'senderId',
|
|
281
|
-
label: t('messages.filters.sender', 'Sender'),
|
|
282
|
-
type: 'select',
|
|
283
|
-
options: [{ value: '', label: t('messages.filters.all', 'All') }],
|
|
284
|
-
loadOptions: loadSenderOptions,
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
id: 'since',
|
|
288
|
-
label: t('messages.filters.since', 'Sent after'),
|
|
289
|
-
type: 'text',
|
|
290
|
-
placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DDTHH:mm:ssZ'),
|
|
291
|
-
},
|
|
292
|
-
]
|
|
293
|
-
}, [loadSenderOptions, messageTypesQuery.data, t])
|
|
274
|
+
return buildMessagesInboxFilters({
|
|
275
|
+
t,
|
|
276
|
+
typeOptions,
|
|
277
|
+
senderOptions,
|
|
278
|
+
loadSenderOptions,
|
|
279
|
+
})
|
|
280
|
+
}, [loadSenderOptions, messageTypesQuery.data, senderOptions, t])
|
|
294
281
|
|
|
295
282
|
const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [
|
|
296
283
|
{
|
|
@@ -319,11 +306,12 @@ export function MessagesInboxPageClient() {
|
|
|
319
306
|
bodyFormat: 'text' as const,
|
|
320
307
|
priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',
|
|
321
308
|
sentAt: item.sentAt ? new Date(item.sentAt) : null,
|
|
322
|
-
senderName: item
|
|
309
|
+
senderName: getMessageListParticipantLabel(item, folder, t),
|
|
323
310
|
hasObjects: item.hasObjects,
|
|
324
311
|
objectCount: item.objectCount,
|
|
325
312
|
hasAttachments: item.hasAttachments,
|
|
326
313
|
attachmentCount: item.attachmentCount,
|
|
314
|
+
recipientCount: item.recipientCount ?? 0,
|
|
327
315
|
hasActions: item.hasActions,
|
|
328
316
|
actionTaken: item.actionTaken ?? null,
|
|
329
317
|
unread: item.status === 'unread',
|
|
@@ -333,7 +321,7 @@ export function MessagesInboxPageClient() {
|
|
|
333
321
|
)
|
|
334
322
|
},
|
|
335
323
|
},
|
|
336
|
-
], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])
|
|
324
|
+
], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])
|
|
337
325
|
|
|
338
326
|
const folderOptions = React.useMemo(() => [
|
|
339
327
|
{ id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },
|
|
@@ -31,7 +31,7 @@ function formatSentTime(value: Date | null): string {
|
|
|
31
31
|
|
|
32
32
|
function truncateWords(value: string, maxWords: number): string {
|
|
33
33
|
const normalized = value.trim().replace(/\s+/g, ' ')
|
|
34
|
-
if (!normalized) return '
|
|
34
|
+
if (!normalized) return ''
|
|
35
35
|
const words = normalized.split(' ')
|
|
36
36
|
if (words.length <= maxWords) return normalized
|
|
37
37
|
return `${words.slice(0, maxWords).join(' ')}...`
|
|
@@ -39,8 +39,8 @@ function truncateWords(value: string, maxWords: number): string {
|
|
|
39
39
|
|
|
40
40
|
export function DefaultMessageListItem({ message, onClick }: MessageListItemProps) {
|
|
41
41
|
const t = useT()
|
|
42
|
-
const senderLabel = message.senderName || '
|
|
43
|
-
const subject = message.subject || '
|
|
42
|
+
const senderLabel = message.senderName?.trim() || t('messages.list.noRecipient', '(No recipient)')
|
|
43
|
+
const subject = message.subject.trim() || t('messages.list.noSubject', '(No subject)')
|
|
44
44
|
const absoluteSentAt = formatDateTime(message.sentAt)
|
|
45
45
|
const sentAtLabel = formatSentTime(message.sentAt)
|
|
46
46
|
const bodyPreview = truncateWords(message.body || '', 16)
|
|
@@ -75,8 +75,12 @@ export function DefaultMessageListItem({ message, onClick }: MessageListItemProp
|
|
|
75
75
|
<span className={cn('truncate text-foreground', message.unread ? 'font-semibold' : 'font-normal')}>
|
|
76
76
|
{subject}
|
|
77
77
|
</span>
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
{bodyPreview ? (
|
|
79
|
+
<>
|
|
80
|
+
<span className="text-muted-foreground">-</span>
|
|
81
|
+
<span className="truncate text-muted-foreground">{bodyPreview}</span>
|
|
82
|
+
</>
|
|
83
|
+
) : null}
|
|
80
84
|
</div>
|
|
81
85
|
|
|
82
86
|
<div className="flex flex-shrink-0 items-center gap-1.5 text-muted-foreground">
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
2
|
+
|
|
3
|
+
type Translator = (key: string, fallback?: string) => string
|
|
4
|
+
|
|
5
|
+
export type SenderOption = {
|
|
6
|
+
value: string
|
|
7
|
+
label: string
|
|
8
|
+
description?: string | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type MessagesInboxFilterContext = {
|
|
12
|
+
t: Translator
|
|
13
|
+
typeOptions: { value: string; label: string }[]
|
|
14
|
+
senderOptions: SenderOption[]
|
|
15
|
+
loadSenderOptions: (query?: string) => Promise<SenderOption[]>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildMessagesInboxFilters({
|
|
19
|
+
t,
|
|
20
|
+
typeOptions,
|
|
21
|
+
senderOptions,
|
|
22
|
+
loadSenderOptions,
|
|
23
|
+
}: MessagesInboxFilterContext): FilterDef[] {
|
|
24
|
+
const senderLabel = (val: string): string => {
|
|
25
|
+
const match = senderOptions.find((opt) => opt.value === val)
|
|
26
|
+
return match?.label ?? val
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
id: 'status',
|
|
32
|
+
label: t('messages.filters.status', 'Status'),
|
|
33
|
+
type: 'select',
|
|
34
|
+
options: [
|
|
35
|
+
{ value: '', label: t('messages.filters.all', 'All') },
|
|
36
|
+
{ value: 'unread', label: t('messages.status.unread', 'Unread') },
|
|
37
|
+
{ value: 'read', label: t('messages.status.read', 'Read') },
|
|
38
|
+
{ value: 'archived', label: t('messages.status.archived', 'Archived') },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'type',
|
|
43
|
+
label: t('messages.filters.type', 'Type'),
|
|
44
|
+
type: 'select',
|
|
45
|
+
options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'hasObjects',
|
|
49
|
+
label: t('messages.filters.hasObjects', 'Has objects'),
|
|
50
|
+
type: 'select',
|
|
51
|
+
options: [
|
|
52
|
+
{ value: '', label: t('messages.filters.all', 'All') },
|
|
53
|
+
{ value: 'true', label: t('common.yes', 'Yes') },
|
|
54
|
+
{ value: 'false', label: t('common.no', 'No') },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'hasAttachments',
|
|
59
|
+
label: t('messages.filters.hasAttachments', 'Has attachments'),
|
|
60
|
+
type: 'select',
|
|
61
|
+
options: [
|
|
62
|
+
{ value: '', label: t('messages.filters.all', 'All') },
|
|
63
|
+
{ value: 'true', label: t('common.yes', 'Yes') },
|
|
64
|
+
{ value: 'false', label: t('common.no', 'No') },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'hasActions',
|
|
69
|
+
label: t('messages.filters.hasActions', 'Has actions'),
|
|
70
|
+
type: 'select',
|
|
71
|
+
options: [
|
|
72
|
+
{ value: '', label: t('messages.filters.all', 'All') },
|
|
73
|
+
{ value: 'true', label: t('common.yes', 'Yes') },
|
|
74
|
+
{ value: 'false', label: t('common.no', 'No') },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'senderId',
|
|
79
|
+
label: t('messages.filters.sender', 'Sender'),
|
|
80
|
+
type: 'combobox',
|
|
81
|
+
placeholder: t('messages.filters.senderEmpty', 'Type to search users'),
|
|
82
|
+
options: senderOptions,
|
|
83
|
+
loadOptions: loadSenderOptions,
|
|
84
|
+
formatValue: senderLabel,
|
|
85
|
+
formatDescription: (val: string) =>
|
|
86
|
+
senderOptions.find((opt) => opt.value === val)?.description ?? null,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'since',
|
|
90
|
+
label: t('messages.filters.since', 'Sent after'),
|
|
91
|
+
type: 'text',
|
|
92
|
+
placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DD'),
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/
|
|
98
|
+
|
|
99
|
+
export function normalizeMessagesSinceValue(input: string): string | null {
|
|
100
|
+
const trimmed = input.trim()
|
|
101
|
+
if (!trimmed) return null
|
|
102
|
+
if (DATE_ONLY_PATTERN.test(trimmed)) {
|
|
103
|
+
const parsed = new Date(`${trimmed}T00:00:00.000Z`)
|
|
104
|
+
if (Number.isNaN(parsed.getTime())) return null
|
|
105
|
+
if (parsed.toISOString().slice(0, 10) !== trimmed) return null
|
|
106
|
+
return parsed.toISOString()
|
|
107
|
+
}
|
|
108
|
+
const parsed = new Date(trimmed)
|
|
109
|
+
if (Number.isNaN(parsed.getTime())) return null
|
|
110
|
+
return parsed.toISOString()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type BuildMessagesListParamsOptions = {
|
|
114
|
+
folder: string
|
|
115
|
+
page: number
|
|
116
|
+
pageSize: number
|
|
117
|
+
search: string
|
|
118
|
+
filterValues: FilterValues
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function buildMessagesListParams({
|
|
122
|
+
folder,
|
|
123
|
+
page,
|
|
124
|
+
pageSize,
|
|
125
|
+
search,
|
|
126
|
+
filterValues,
|
|
127
|
+
}: BuildMessagesListParamsOptions): URLSearchParams {
|
|
128
|
+
const params = new URLSearchParams()
|
|
129
|
+
params.set('folder', folder)
|
|
130
|
+
params.set('page', String(page))
|
|
131
|
+
params.set('pageSize', String(pageSize))
|
|
132
|
+
|
|
133
|
+
if (search.trim()) {
|
|
134
|
+
params.set('search', search.trim())
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const stringValue = (id: string): string => {
|
|
138
|
+
const raw = filterValues[id]
|
|
139
|
+
return typeof raw === 'string' ? raw.trim() : ''
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const status = stringValue('status')
|
|
143
|
+
const type = stringValue('type')
|
|
144
|
+
const hasObjects = stringValue('hasObjects')
|
|
145
|
+
const hasAttachments = stringValue('hasAttachments')
|
|
146
|
+
const hasActions = stringValue('hasActions')
|
|
147
|
+
const senderId = stringValue('senderId')
|
|
148
|
+
const sinceRaw = stringValue('since')
|
|
149
|
+
|
|
150
|
+
if (status) params.set('status', status)
|
|
151
|
+
if (type) params.set('type', type)
|
|
152
|
+
if (hasObjects) params.set('hasObjects', hasObjects)
|
|
153
|
+
if (hasAttachments) params.set('hasAttachments', hasAttachments)
|
|
154
|
+
if (hasActions) params.set('hasActions', hasActions)
|
|
155
|
+
if (senderId) params.set('senderId', senderId)
|
|
156
|
+
if (sinceRaw) {
|
|
157
|
+
const normalized = normalizeMessagesSinceValue(sinceRaw)
|
|
158
|
+
if (normalized) params.set('since', normalized)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return params
|
|
162
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MessageFolder } from './useMessagesInboxBulkActions'
|
|
2
|
+
|
|
3
|
+
type Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string
|
|
4
|
+
|
|
5
|
+
type MessageParticipantSource = {
|
|
6
|
+
senderName?: string | null
|
|
7
|
+
senderEmail?: string | null
|
|
8
|
+
senderUserId: string
|
|
9
|
+
recipientCount?: number | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeLabel(value: string | null | undefined): string | null {
|
|
13
|
+
const normalized = value?.trim()
|
|
14
|
+
return normalized ? normalized : null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getMessageListParticipantLabel(
|
|
18
|
+
item: MessageParticipantSource,
|
|
19
|
+
folder: MessageFolder,
|
|
20
|
+
t: Translate,
|
|
21
|
+
): string {
|
|
22
|
+
if ((folder === 'sent' || folder === 'drafts') && Number(item.recipientCount ?? 0) <= 0) {
|
|
23
|
+
return t('messages.list.noRecipient', '(No recipient)')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return normalizeLabel(item.senderName)
|
|
27
|
+
?? normalizeLabel(item.senderEmail)
|
|
28
|
+
?? item.senderUserId
|
|
29
|
+
}
|
|
@@ -13,7 +13,7 @@ import { toErrorMessage } from './message-detail/utils'
|
|
|
13
13
|
|
|
14
14
|
export type MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'
|
|
15
15
|
|
|
16
|
-
type MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'
|
|
16
|
+
export type MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'
|
|
17
17
|
|
|
18
18
|
type BulkExecutionSummary = {
|
|
19
19
|
action: MessageBulkActionId
|
|
@@ -31,6 +31,13 @@ type MessageBulkRequestConfig = {
|
|
|
31
31
|
errorFallback: string
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
type MessageBulkActionDefinition = {
|
|
35
|
+
id: string
|
|
36
|
+
labelKey: string
|
|
37
|
+
labelFallback: string
|
|
38
|
+
destructive?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
type MessageInboxBulkMutationContext = {
|
|
35
42
|
actionId: MessageBulkActionId
|
|
36
43
|
messageIds: string[]
|
|
@@ -87,6 +94,42 @@ const MESSAGE_BULK_REQUESTS: Record<MessageBulkActionId, MessageBulkRequestConfi
|
|
|
87
94
|
},
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
const MESSAGE_BULK_ACTIONS: Record<MessageBulkActionId, MessageBulkActionDefinition> = {
|
|
98
|
+
markRead: {
|
|
99
|
+
id: 'messages-mark-read',
|
|
100
|
+
labelKey: 'messages.actions.markRead',
|
|
101
|
+
labelFallback: 'Mark read',
|
|
102
|
+
},
|
|
103
|
+
markUnread: {
|
|
104
|
+
id: 'messages-mark-unread',
|
|
105
|
+
labelKey: 'messages.actions.markUnread',
|
|
106
|
+
labelFallback: 'Mark unread',
|
|
107
|
+
},
|
|
108
|
+
archive: {
|
|
109
|
+
id: 'messages-archive',
|
|
110
|
+
labelKey: 'messages.actions.archive',
|
|
111
|
+
labelFallback: 'Archive',
|
|
112
|
+
},
|
|
113
|
+
delete: {
|
|
114
|
+
id: 'messages-delete',
|
|
115
|
+
labelKey: 'messages.actions.delete',
|
|
116
|
+
labelFallback: 'Delete',
|
|
117
|
+
destructive: true,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const MESSAGE_BULK_ACTION_IDS_BY_FOLDER = {
|
|
122
|
+
inbox: ['markRead', 'markUnread', 'archive', 'delete'],
|
|
123
|
+
archived: ['markRead', 'markUnread', 'delete'],
|
|
124
|
+
sent: ['delete'],
|
|
125
|
+
drafts: ['delete'],
|
|
126
|
+
all: [],
|
|
127
|
+
} satisfies Record<MessageFolder, readonly MessageBulkActionId[]>
|
|
128
|
+
|
|
129
|
+
export function getMessageBulkActionIdsForFolder(folder: MessageFolder): MessageBulkActionId[] {
|
|
130
|
+
return [...MESSAGE_BULK_ACTION_IDS_BY_FOLDER[folder]]
|
|
131
|
+
}
|
|
132
|
+
|
|
90
133
|
function normalizeSelectionScopeValue(value: unknown): unknown {
|
|
91
134
|
if (value == null) return undefined
|
|
92
135
|
if (typeof value === 'string') {
|
|
@@ -287,31 +330,20 @@ export function useMessagesInboxBulkActions<T extends MessageInboxBulkRow>({
|
|
|
287
330
|
}, [confirm, filterValues, folder, page, queryClient, retryLastMutation, runMutation, search, t])
|
|
288
331
|
|
|
289
332
|
const bulkActions = React.useMemo<BulkAction<T>[] | undefined>(
|
|
290
|
-
() =>
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
label: t('messages.actions.archive', 'Archive'),
|
|
305
|
-
onExecute: (selectedRows: T[]) => executeBulkAction('archive', selectedRows),
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
id: 'messages-delete',
|
|
309
|
-
label: t('messages.actions.delete', 'Delete'),
|
|
310
|
-
destructive: true,
|
|
311
|
-
onExecute: (selectedRows: T[]) => executeBulkAction('delete', selectedRows),
|
|
312
|
-
},
|
|
313
|
-
]
|
|
314
|
-
: undefined,
|
|
333
|
+
() => {
|
|
334
|
+
const actionIds = getMessageBulkActionIdsForFolder(folder)
|
|
335
|
+
if (actionIds.length === 0) return undefined
|
|
336
|
+
|
|
337
|
+
return actionIds.map((actionId) => {
|
|
338
|
+
const action = MESSAGE_BULK_ACTIONS[actionId]
|
|
339
|
+
return {
|
|
340
|
+
id: action.id,
|
|
341
|
+
label: t(action.labelKey, action.labelFallback),
|
|
342
|
+
destructive: action.destructive,
|
|
343
|
+
onExecute: (selectedRows: T[]) => executeBulkAction(actionId, selectedRows),
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
},
|
|
315
347
|
[executeBulkAction, folder, t],
|
|
316
348
|
)
|
|
317
349
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
+
import { parseBooleanFlag } from '@open-mercato/shared/lib/boolean'
|
|
2
3
|
|
|
3
4
|
function collectDuplicateRecipientIds(
|
|
4
5
|
recipients: Array<{ userId: string }>,
|
|
@@ -178,14 +179,15 @@ export const updateDraftSchema = z.object({
|
|
|
178
179
|
externalEmail: z.string().email().optional(),
|
|
179
180
|
externalName: z.string().min(1).max(255).optional(),
|
|
180
181
|
recipients: z.array(messageRecipientSchema).optional(),
|
|
181
|
-
subject: z.string().
|
|
182
|
-
body: z.string().
|
|
182
|
+
subject: z.string().max(500).optional(),
|
|
183
|
+
body: z.string().max(50000).optional(),
|
|
183
184
|
bodyFormat: z.enum(['text', 'markdown']).optional(),
|
|
184
185
|
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),
|
|
185
186
|
objects: z.array(messageObjectSchema).optional(),
|
|
186
187
|
attachmentIds: z.array(z.string().uuid()).optional(),
|
|
187
188
|
actionData: messageActionDataSchema.optional(),
|
|
188
189
|
sendViaEmail: z.boolean().optional(),
|
|
190
|
+
isDraft: z.literal(false).optional(),
|
|
189
191
|
}).superRefine((value, ctx) => {
|
|
190
192
|
if (value.recipients) {
|
|
191
193
|
const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)
|
|
@@ -209,9 +211,9 @@ export const listMessagesSchema = z.object({
|
|
|
209
211
|
sourceEntityType: z.string().optional(),
|
|
210
212
|
sourceEntityId: z.string().uuid().optional(),
|
|
211
213
|
externalEmail: z.string().email().optional(),
|
|
212
|
-
hasObjects: z.
|
|
213
|
-
hasAttachments: z.
|
|
214
|
-
hasActions: z.
|
|
214
|
+
hasObjects: z.string().transform(parseBooleanFlag).optional(),
|
|
215
|
+
hasAttachments: z.string().transform(parseBooleanFlag).optional(),
|
|
216
|
+
hasActions: z.string().transform(parseBooleanFlag).optional(),
|
|
215
217
|
senderId: z.string().uuid().optional(),
|
|
216
218
|
search: z.string().max(200).optional(),
|
|
217
219
|
since: z.string().datetime().optional(),
|
|
@@ -131,12 +131,13 @@
|
|
|
131
131
|
"messages.externalName": "Externer Name",
|
|
132
132
|
"messages.fields.type": "Nachrichtentyp",
|
|
133
133
|
"messages.filters.all": "Alle",
|
|
134
|
-
"messages.filters.hasActions": "Aktionen",
|
|
135
|
-
"messages.filters.hasAttachments": "Anhänge",
|
|
136
|
-
"messages.filters.hasObjects": "Objekte",
|
|
134
|
+
"messages.filters.hasActions": "Hat Aktionen",
|
|
135
|
+
"messages.filters.hasAttachments": "Hat Anhänge",
|
|
136
|
+
"messages.filters.hasObjects": "Hat Objekte",
|
|
137
137
|
"messages.filters.sender": "Absender",
|
|
138
|
+
"messages.filters.senderEmpty": "Tippe, um Benutzer zu suchen",
|
|
138
139
|
"messages.filters.since": "Gesendet nach",
|
|
139
|
-
"messages.filters.sincePlaceholder": "YYYY-MM-
|
|
140
|
+
"messages.filters.sincePlaceholder": "YYYY-MM-DD",
|
|
140
141
|
"messages.filters.status": "Status",
|
|
141
142
|
"messages.filters.type": "Typ",
|
|
142
143
|
"messages.flash.actionSuccess": "Aktion abgeschlossen.",
|
|
@@ -159,6 +160,8 @@
|
|
|
159
160
|
"messages.forwardNote": "Optionale Notiz",
|
|
160
161
|
"messages.includeAttachments": "Anhänge einschließen",
|
|
161
162
|
"messages.includeAttachmentsHint": "Anhänge aus der Originalnachricht übernehmen.",
|
|
163
|
+
"messages.list.noRecipient": "(Kein Empfänger)",
|
|
164
|
+
"messages.list.noSubject": "(Kein Betreff)",
|
|
162
165
|
"messages.loading.attachments": "Anhänge werden geladen...",
|
|
163
166
|
"messages.loading.detail": "Nachricht wird geladen...",
|
|
164
167
|
"messages.loading.objectTypes": "Objekttypen werden geladen...",
|
|
@@ -131,12 +131,13 @@
|
|
|
131
131
|
"messages.externalName": "External name",
|
|
132
132
|
"messages.fields.type": "Message type",
|
|
133
133
|
"messages.filters.all": "All",
|
|
134
|
-
"messages.filters.hasActions": "
|
|
135
|
-
"messages.filters.hasAttachments": "
|
|
136
|
-
"messages.filters.hasObjects": "
|
|
134
|
+
"messages.filters.hasActions": "Has actions",
|
|
135
|
+
"messages.filters.hasAttachments": "Has attachments",
|
|
136
|
+
"messages.filters.hasObjects": "Has objects",
|
|
137
137
|
"messages.filters.sender": "Sender",
|
|
138
|
+
"messages.filters.senderEmpty": "Type to search users",
|
|
138
139
|
"messages.filters.since": "Sent after",
|
|
139
|
-
"messages.filters.sincePlaceholder": "YYYY-MM-
|
|
140
|
+
"messages.filters.sincePlaceholder": "YYYY-MM-DD",
|
|
140
141
|
"messages.filters.status": "Status",
|
|
141
142
|
"messages.filters.type": "Type",
|
|
142
143
|
"messages.flash.actionSuccess": "Action completed.",
|
|
@@ -159,6 +160,8 @@
|
|
|
159
160
|
"messages.forwardNote": "Optional note",
|
|
160
161
|
"messages.includeAttachments": "Include attachments",
|
|
161
162
|
"messages.includeAttachmentsHint": "Carry over attachments from the original message.",
|
|
163
|
+
"messages.list.noRecipient": "(No recipient)",
|
|
164
|
+
"messages.list.noSubject": "(No subject)",
|
|
162
165
|
"messages.loading.attachments": "Loading attachments...",
|
|
163
166
|
"messages.loading.detail": "Loading message...",
|
|
164
167
|
"messages.loading.objectTypes": "Loading object types...",
|