@open-mercato/core 0.6.2-develop.3446.1.bd060c6017 → 0.6.2-develop.3467.1.2a1818709d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/users/route.js +3 -6
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +2 -1
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/commands/users.js +5 -8
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/entities.js.map +1 -1
- package/dist/modules/auth/frontend/login.js +35 -0
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/auth/lib/displayName.js +10 -0
- package/dist/modules/auth/lib/displayName.js.map +7 -0
- 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/messages/components/MessagesInboxPageClient.js +4 -2
- 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/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/package.json +7 -7
- package/src/modules/auth/api/users/route.ts +3 -6
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -1
- package/src/modules/auth/backend/users/create/page.tsx +2 -1
- package/src/modules/auth/commands/users.ts +5 -8
- package/src/modules/auth/data/entities.ts +1 -1
- package/src/modules/auth/frontend/login.tsx +40 -0
- package/src/modules/auth/lib/displayName.ts +6 -0
- package/src/modules/business_rules/lib/rule-evaluator.ts +3 -3
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +5 -2
- package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +9 -5
- package/src/modules/messages/components/messageListLabels.ts +29 -0
- package/src/modules/messages/components/useMessagesInboxBulkActions.ts +58 -26
- package/src/modules/messages/i18n/de.json +2 -0
- package/src/modules/messages/i18n/en.json +2 -0
- package/src/modules/messages/i18n/es.json +2 -0
- package/src/modules/messages/i18n/pl.json +2 -0
|
@@ -17,6 +17,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
17
17
|
import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
|
|
18
18
|
import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
19
19
|
import { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'
|
|
20
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
20
21
|
|
|
21
22
|
type EditUserFormValues = {
|
|
22
23
|
email: string
|
|
@@ -442,7 +443,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
442
443
|
const payload = {
|
|
443
444
|
id: id ? String(id) : '',
|
|
444
445
|
email: values.email,
|
|
445
|
-
name:
|
|
446
|
+
name: normalizeDisplayNameInput(values.name),
|
|
446
447
|
password: values.password && values.password.trim() ? values.password : undefined,
|
|
447
448
|
organizationId: values.organizationId ? values.organizationId : undefined,
|
|
448
449
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
@@ -14,6 +14,7 @@ import { RadioGroup } from '@open-mercato/ui/primitives/radio'
|
|
|
14
14
|
import { RadioField } from '@open-mercato/ui/primitives/radio-field'
|
|
15
15
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
16
16
|
import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
17
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
17
18
|
|
|
18
19
|
type CreateUserFormValues = {
|
|
19
20
|
email: string
|
|
@@ -326,7 +327,7 @@ export default function CreateUserPage() {
|
|
|
326
327
|
const customFields = collectCustomFieldValues(values)
|
|
327
328
|
const payload: Record<string, unknown> = {
|
|
328
329
|
email: values.email,
|
|
329
|
-
name:
|
|
330
|
+
name: normalizeDisplayNameInput(values.name),
|
|
330
331
|
organizationId: values.organizationId ? values.organizationId : null,
|
|
331
332
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
332
333
|
...(Object.keys(customFields).length ? { customFields } : {}),
|
|
@@ -37,6 +37,7 @@ import InviteUserEmail from '@open-mercato/core/modules/auth/emails/InviteUserEm
|
|
|
37
37
|
import { INVITE_TOKEN_TTL_MS } from '@open-mercato/core/modules/auth/lib/inviteToken'
|
|
38
38
|
import { getSecurityEmailBaseUrl } from '@open-mercato/shared/lib/url'
|
|
39
39
|
import { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'
|
|
40
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
40
41
|
|
|
41
42
|
type SerializedUser = {
|
|
42
43
|
email: string
|
|
@@ -76,12 +77,8 @@ type UserSnapshots = {
|
|
|
76
77
|
const passwordSchema = buildPasswordSchema()
|
|
77
78
|
|
|
78
79
|
const displayNameSchema = z.preprocess(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const trimmed = value.trim()
|
|
82
|
-
return trimmed.length ? trimmed : undefined
|
|
83
|
-
},
|
|
84
|
-
z.string().trim().min(1).max(120).optional(),
|
|
80
|
+
normalizeDisplayNameInput,
|
|
81
|
+
z.string().trim().min(1).max(120).nullable().optional(),
|
|
85
82
|
)
|
|
86
83
|
|
|
87
84
|
const createSchema = z.object({
|
|
@@ -589,7 +586,7 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
589
586
|
entity.organizationId = before.organizationId ?? null
|
|
590
587
|
entity.tenantId = before.tenantId ?? null
|
|
591
588
|
entity.passwordHash = before.passwordHash ?? null
|
|
592
|
-
entity.name = before.name ??
|
|
589
|
+
entity.name = before.name ?? null
|
|
593
590
|
entity.isConfirmed = before.isConfirmed
|
|
594
591
|
},
|
|
595
592
|
})
|
|
@@ -715,7 +712,7 @@ const deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?
|
|
|
715
712
|
user.organizationId = before.organizationId ?? null
|
|
716
713
|
user.tenantId = before.tenantId ?? null
|
|
717
714
|
user.passwordHash = before.passwordHash ?? null
|
|
718
|
-
user.name = before.name ??
|
|
715
|
+
user.name = before.name ?? null
|
|
719
716
|
user.isConfirmed = before.isConfirmed
|
|
720
717
|
await em.flush()
|
|
721
718
|
} else {
|
|
@@ -13,6 +13,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
|
|
|
13
13
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
14
14
|
import { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
15
15
|
import { clearAllOperations } from '@open-mercato/ui/backend/operations/store'
|
|
16
|
+
import { notifyAuthIdentityChange } from '@open-mercato/ui/backend/AuthSessionGuard'
|
|
16
17
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
17
18
|
import { X } from 'lucide-react'
|
|
18
19
|
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
@@ -125,6 +126,43 @@ export default function LoginPage() {
|
|
|
125
126
|
setClientReady(true)
|
|
126
127
|
}, [])
|
|
127
128
|
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
let cancelled = false
|
|
131
|
+
void (async () => {
|
|
132
|
+
try {
|
|
133
|
+
const res = await apiCall<{ userId?: string }>('/api/auth/feature-check', {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'content-type': 'application/json' },
|
|
136
|
+
body: JSON.stringify({ features: [] }),
|
|
137
|
+
cache: 'no-store',
|
|
138
|
+
})
|
|
139
|
+
if (cancelled) return
|
|
140
|
+
const activeUserId = typeof res.result?.userId === 'string' ? res.result.userId : ''
|
|
141
|
+
if (!activeUserId) return
|
|
142
|
+
const rawRedirect = searchParams.get('redirect') || ''
|
|
143
|
+
let destination = '/backend'
|
|
144
|
+
if (rawRedirect) {
|
|
145
|
+
try {
|
|
146
|
+
const resolved = new URL(rawRedirect, window.location.origin)
|
|
147
|
+
if (
|
|
148
|
+
resolved.origin === window.location.origin &&
|
|
149
|
+
resolved.pathname.startsWith('/') &&
|
|
150
|
+
!resolved.pathname.includes('//')
|
|
151
|
+
) {
|
|
152
|
+
destination = resolved.pathname + resolved.search + resolved.hash
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
// fall back to /backend
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
router.replace(destination)
|
|
159
|
+
} catch {
|
|
160
|
+
// ignore — leave login form usable on network failure
|
|
161
|
+
}
|
|
162
|
+
})()
|
|
163
|
+
return () => { cancelled = true }
|
|
164
|
+
}, [router, searchParams])
|
|
165
|
+
|
|
128
166
|
useEffect(() => {
|
|
129
167
|
const tenantParam = (searchParams.get('tenant') || '').trim()
|
|
130
168
|
if (tenantParam) {
|
|
@@ -212,6 +250,7 @@ export default function LoginPage() {
|
|
|
212
250
|
const res = await fetch('/api/auth/login', { method: 'POST', body: form })
|
|
213
251
|
if (res.redirected) {
|
|
214
252
|
clearAllOperations()
|
|
253
|
+
notifyAuthIdentityChange()
|
|
215
254
|
// NextResponse.redirect from API
|
|
216
255
|
router.replace(res.url)
|
|
217
256
|
return
|
|
@@ -265,6 +304,7 @@ export default function LoginPage() {
|
|
|
265
304
|
const data = await res.json().catch(() => null) as LoginResponseEventDetail
|
|
266
305
|
emitLoginResponseEvent(data)
|
|
267
306
|
clearAllOperations()
|
|
307
|
+
notifyAuthIdentityChange()
|
|
268
308
|
if (data && typeof data.redirect === 'string' && data.redirect.length > 0) {
|
|
269
309
|
router.replace(data.redirect)
|
|
270
310
|
}
|
|
@@ -77,7 +77,7 @@ export async function evaluate(
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
const evaluationTime = Date.now() - startTime
|
|
80
|
+
const evaluationTime = Math.max(0, Date.now() - startTime)
|
|
81
81
|
|
|
82
82
|
// Determine if conditions were met (at least one rule matched)
|
|
83
83
|
const conditionsPassed = matchedRules.length > 0
|
|
@@ -140,7 +140,7 @@ export async function evaluateSingleRule(
|
|
|
140
140
|
// Evaluate conditions
|
|
141
141
|
const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context)
|
|
142
142
|
|
|
143
|
-
const evaluationTime = Date.now() - startTime
|
|
143
|
+
const evaluationTime = Math.max(0, Date.now() - startTime)
|
|
144
144
|
|
|
145
145
|
console.log('[RULE EVAL] Final result:', {
|
|
146
146
|
ruleId: rule.ruleId,
|
|
@@ -156,7 +156,7 @@ export async function evaluateSingleRule(
|
|
|
156
156
|
evaluationTime,
|
|
157
157
|
}
|
|
158
158
|
} catch (error) {
|
|
159
|
-
const evaluationTime = Date.now() - startTime
|
|
159
|
+
const evaluationTime = Math.max(0, Date.now() - startTime)
|
|
160
160
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
161
161
|
|
|
162
162
|
console.log('[RULE EVAL] ✗ ERROR:', errorMessage)
|
|
@@ -16,6 +16,7 @@ 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'
|
|
21
22
|
|
|
@@ -33,6 +34,7 @@ type MessageListItem = {
|
|
|
33
34
|
objectCount: number
|
|
34
35
|
hasAttachments: boolean
|
|
35
36
|
attachmentCount: number
|
|
37
|
+
recipientCount?: number
|
|
36
38
|
hasActions: boolean
|
|
37
39
|
actionTaken?: string | null
|
|
38
40
|
sentAt?: string | null
|
|
@@ -319,11 +321,12 @@ export function MessagesInboxPageClient() {
|
|
|
319
321
|
bodyFormat: 'text' as const,
|
|
320
322
|
priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',
|
|
321
323
|
sentAt: item.sentAt ? new Date(item.sentAt) : null,
|
|
322
|
-
senderName: item
|
|
324
|
+
senderName: getMessageListParticipantLabel(item, folder, t),
|
|
323
325
|
hasObjects: item.hasObjects,
|
|
324
326
|
objectCount: item.objectCount,
|
|
325
327
|
hasAttachments: item.hasAttachments,
|
|
326
328
|
attachmentCount: item.attachmentCount,
|
|
329
|
+
recipientCount: item.recipientCount ?? 0,
|
|
327
330
|
hasActions: item.hasActions,
|
|
328
331
|
actionTaken: item.actionTaken ?? null,
|
|
329
332
|
unread: item.status === 'unread',
|
|
@@ -333,7 +336,7 @@ export function MessagesInboxPageClient() {
|
|
|
333
336
|
)
|
|
334
337
|
},
|
|
335
338
|
},
|
|
336
|
-
], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])
|
|
339
|
+
], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])
|
|
337
340
|
|
|
338
341
|
const folderOptions = React.useMemo(() => [
|
|
339
342
|
{ 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,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
|
|
|
@@ -159,6 +159,8 @@
|
|
|
159
159
|
"messages.forwardNote": "Optionale Notiz",
|
|
160
160
|
"messages.includeAttachments": "Anhänge einschließen",
|
|
161
161
|
"messages.includeAttachmentsHint": "Anhänge aus der Originalnachricht übernehmen.",
|
|
162
|
+
"messages.list.noRecipient": "(Kein Empfänger)",
|
|
163
|
+
"messages.list.noSubject": "(Kein Betreff)",
|
|
162
164
|
"messages.loading.attachments": "Anhänge werden geladen...",
|
|
163
165
|
"messages.loading.detail": "Nachricht wird geladen...",
|
|
164
166
|
"messages.loading.objectTypes": "Objekttypen werden geladen...",
|
|
@@ -159,6 +159,8 @@
|
|
|
159
159
|
"messages.forwardNote": "Optional note",
|
|
160
160
|
"messages.includeAttachments": "Include attachments",
|
|
161
161
|
"messages.includeAttachmentsHint": "Carry over attachments from the original message.",
|
|
162
|
+
"messages.list.noRecipient": "(No recipient)",
|
|
163
|
+
"messages.list.noSubject": "(No subject)",
|
|
162
164
|
"messages.loading.attachments": "Loading attachments...",
|
|
163
165
|
"messages.loading.detail": "Loading message...",
|
|
164
166
|
"messages.loading.objectTypes": "Loading object types...",
|
|
@@ -159,6 +159,8 @@
|
|
|
159
159
|
"messages.forwardNote": "Nota opcional",
|
|
160
160
|
"messages.includeAttachments": "Incluir adjuntos",
|
|
161
161
|
"messages.includeAttachmentsHint": "Conservar adjuntos del mensaje original.",
|
|
162
|
+
"messages.list.noRecipient": "(Sin destinatario)",
|
|
163
|
+
"messages.list.noSubject": "(Sin asunto)",
|
|
162
164
|
"messages.loading.attachments": "Cargando adjuntos...",
|
|
163
165
|
"messages.loading.detail": "Cargando mensaje...",
|
|
164
166
|
"messages.loading.objectTypes": "Cargando tipos de objeto...",
|
|
@@ -159,6 +159,8 @@
|
|
|
159
159
|
"messages.forwardNote": "Notatka opcjonalna",
|
|
160
160
|
"messages.includeAttachments": "Dołącz załączniki",
|
|
161
161
|
"messages.includeAttachmentsHint": "Przenieś załączniki z oryginalnej wiadomości.",
|
|
162
|
+
"messages.list.noRecipient": "(Brak odbiorcy)",
|
|
163
|
+
"messages.list.noSubject": "(Brak tematu)",
|
|
162
164
|
"messages.loading.attachments": "Ładowanie załączników...",
|
|
163
165
|
"messages.loading.detail": "Ładowanie wiadomości...",
|
|
164
166
|
"messages.loading.objectTypes": "Ładowanie typów obiektów...",
|