@open-mercato/core 0.6.6-develop.6367.1.2321ccacd4 → 0.6.6-develop.6373.1.5bece5409d
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/customers/api/companies/[id]/route.js +80 -2
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/feature_toggles/components/formConfig.js +4 -4
- package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
- package/dist/modules/feature_toggles/components/overrideFormConfig.js +4 -4
- package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js +12 -0
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js +14 -1
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/i18n/de.json +72 -72
- package/src/modules/customers/api/companies/[id]/route.ts +109 -2
- package/src/modules/feature_toggles/components/formConfig.tsx +2 -2
- package/src/modules/feature_toggles/components/overrideFormConfig.tsx +2 -2
- package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +23 -0
- package/src/modules/messages/components/useMessagesInboxBulkActions.ts +15 -2
- package/src/modules/messages/i18n/de.json +1 -0
- package/src/modules/messages/i18n/en.json +1 -0
- package/src/modules/messages/i18n/es.json +1 -0
- package/src/modules/messages/i18n/pl.json +1 -0
|
@@ -103,88 +103,88 @@
|
|
|
103
103
|
"customer_accounts.admin.flash.deactivated": "User deactivated",
|
|
104
104
|
"customer_accounts.admin.flash.deleted": "User deleted",
|
|
105
105
|
"customer_accounts.admin.inactive": "Inactive",
|
|
106
|
-
"customer_accounts.admin.portalFeatures.addresses.manage": "
|
|
107
|
-
"customer_accounts.admin.portalFeatures.addresses.view": "
|
|
108
|
-
"customer_accounts.admin.portalFeatures.groups.addresses": "
|
|
109
|
-
"customer_accounts.admin.portalFeatures.groups.invoices": "
|
|
110
|
-
"customer_accounts.admin.portalFeatures.groups.orders": "
|
|
111
|
-
"customer_accounts.admin.portalFeatures.groups.profile": "
|
|
112
|
-
"customer_accounts.admin.portalFeatures.groups.quotes": "
|
|
113
|
-
"customer_accounts.admin.portalFeatures.groups.users": "
|
|
114
|
-
"customer_accounts.admin.portalFeatures.invoices.view": "
|
|
115
|
-
"customer_accounts.admin.portalFeatures.orders.create": "
|
|
116
|
-
"customer_accounts.admin.portalFeatures.orders.view": "
|
|
117
|
-
"customer_accounts.admin.portalFeatures.profile.edit": "
|
|
118
|
-
"customer_accounts.admin.portalFeatures.profile.view": "
|
|
119
|
-
"customer_accounts.admin.portalFeatures.quotes.request": "
|
|
120
|
-
"customer_accounts.admin.portalFeatures.quotes.view": "
|
|
121
|
-
"customer_accounts.admin.portalFeatures.users.invite": "
|
|
122
|
-
"customer_accounts.admin.portalFeatures.users.manage": "
|
|
123
|
-
"customer_accounts.admin.portalFeatures.users.view": "
|
|
106
|
+
"customer_accounts.admin.portalFeatures.addresses.manage": "Adressen verwalten",
|
|
107
|
+
"customer_accounts.admin.portalFeatures.addresses.view": "Adressen anzeigen",
|
|
108
|
+
"customer_accounts.admin.portalFeatures.groups.addresses": "Adressen",
|
|
109
|
+
"customer_accounts.admin.portalFeatures.groups.invoices": "Rechnungen",
|
|
110
|
+
"customer_accounts.admin.portalFeatures.groups.orders": "Bestellungen",
|
|
111
|
+
"customer_accounts.admin.portalFeatures.groups.profile": "Profil",
|
|
112
|
+
"customer_accounts.admin.portalFeatures.groups.quotes": "Angebote",
|
|
113
|
+
"customer_accounts.admin.portalFeatures.groups.users": "Benutzer",
|
|
114
|
+
"customer_accounts.admin.portalFeatures.invoices.view": "Rechnungen anzeigen",
|
|
115
|
+
"customer_accounts.admin.portalFeatures.orders.create": "Bestellungen erstellen",
|
|
116
|
+
"customer_accounts.admin.portalFeatures.orders.view": "Bestellungen anzeigen",
|
|
117
|
+
"customer_accounts.admin.portalFeatures.profile.edit": "Profil bearbeiten",
|
|
118
|
+
"customer_accounts.admin.portalFeatures.profile.view": "Profil anzeigen",
|
|
119
|
+
"customer_accounts.admin.portalFeatures.quotes.request": "Angebote anfordern",
|
|
120
|
+
"customer_accounts.admin.portalFeatures.quotes.view": "Angebote anzeigen",
|
|
121
|
+
"customer_accounts.admin.portalFeatures.users.invite": "Teammitglieder einladen",
|
|
122
|
+
"customer_accounts.admin.portalFeatures.users.manage": "Teammitglieder verwalten",
|
|
123
|
+
"customer_accounts.admin.portalFeatures.users.view": "Teammitglieder anzeigen",
|
|
124
124
|
"customer_accounts.admin.portalInfo.credentials": "Demo credentials: alice.johnson@example.com / Password123!",
|
|
125
125
|
"customer_accounts.admin.portalInfo.description": "Manage customer portal accounts. Customers can self-register, log in, and access orders, quotes, and invoices through the portal.",
|
|
126
126
|
"customer_accounts.admin.portalInfo.open": "Open Portal",
|
|
127
127
|
"customer_accounts.admin.portalInfo.openConfiguration": "Open Configuration",
|
|
128
128
|
"customer_accounts.admin.portalInfo.title": "Customer Portal",
|
|
129
129
|
"customer_accounts.admin.portalInfo.url": "Portal URL: {url}",
|
|
130
|
-
"customer_accounts.admin.roleCreate.actions.cancel": "
|
|
131
|
-
"customer_accounts.admin.roleCreate.actions.create": "
|
|
132
|
-
"customer_accounts.admin.roleCreate.actions.saving": "
|
|
133
|
-
"customer_accounts.admin.roleCreate.error.nameRequired": "Name
|
|
134
|
-
"customer_accounts.admin.roleCreate.error.required": "Name
|
|
135
|
-
"customer_accounts.admin.roleCreate.error.save": "
|
|
136
|
-
"customer_accounts.admin.roleCreate.error.slugFormat": "Slug
|
|
137
|
-
"customer_accounts.admin.roleCreate.error.slugRequired": "Slug
|
|
138
|
-
"customer_accounts.admin.roleCreate.fields.customerAssignable": "
|
|
139
|
-
"customer_accounts.admin.roleCreate.fields.description": "
|
|
140
|
-
"customer_accounts.admin.roleCreate.fields.isDefault": "
|
|
130
|
+
"customer_accounts.admin.roleCreate.actions.cancel": "Abbrechen",
|
|
131
|
+
"customer_accounts.admin.roleCreate.actions.create": "Rolle erstellen",
|
|
132
|
+
"customer_accounts.admin.roleCreate.actions.saving": "Erstellen...",
|
|
133
|
+
"customer_accounts.admin.roleCreate.error.nameRequired": "Name ist erforderlich",
|
|
134
|
+
"customer_accounts.admin.roleCreate.error.required": "Name und Slug sind erforderlich",
|
|
135
|
+
"customer_accounts.admin.roleCreate.error.save": "Rolle konnte nicht erstellt werden",
|
|
136
|
+
"customer_accounts.admin.roleCreate.error.slugFormat": "Slug darf nur Kleinbuchstaben, Zahlen, Bindestriche und Unterstriche enthalten",
|
|
137
|
+
"customer_accounts.admin.roleCreate.error.slugRequired": "Slug ist erforderlich",
|
|
138
|
+
"customer_accounts.admin.roleCreate.fields.customerAssignable": "Kunden können sich selbst zuweisen",
|
|
139
|
+
"customer_accounts.admin.roleCreate.fields.description": "Beschreibung",
|
|
140
|
+
"customer_accounts.admin.roleCreate.fields.isDefault": "Standardrolle (wird neuen Benutzern automatisch zugewiesen)",
|
|
141
141
|
"customer_accounts.admin.roleCreate.fields.name": "Name",
|
|
142
|
-
"customer_accounts.admin.roleCreate.fields.namePlaceholder": "
|
|
142
|
+
"customer_accounts.admin.roleCreate.fields.namePlaceholder": "z. B. Käufer",
|
|
143
143
|
"customer_accounts.admin.roleCreate.fields.slug": "Slug",
|
|
144
|
-
"customer_accounts.admin.roleCreate.fields.slugHint": "
|
|
145
|
-
"customer_accounts.admin.roleCreate.fields.slugPlaceholder": "
|
|
146
|
-
"customer_accounts.admin.roleCreate.flash.created": "
|
|
147
|
-
"customer_accounts.admin.roleCreate.sections.details": "
|
|
148
|
-
"customer_accounts.admin.roleCreate.sections.options": "
|
|
149
|
-
"customer_accounts.admin.roleCreate.title": "
|
|
150
|
-
"customer_accounts.admin.roleDetail.actions.backToList": "
|
|
151
|
-
"customer_accounts.admin.roleDetail.actions.cancel": "
|
|
152
|
-
"customer_accounts.admin.roleDetail.actions.delete": "
|
|
153
|
-
"customer_accounts.admin.roleDetail.actions.save": "
|
|
154
|
-
"customer_accounts.admin.roleDetail.actions.saving": "
|
|
155
|
-
"customer_accounts.admin.roleDetail.error.load": "
|
|
156
|
-
"customer_accounts.admin.roleDetail.error.notFound": "
|
|
157
|
-
"customer_accounts.admin.roleDetail.error.save": "
|
|
158
|
-
"customer_accounts.admin.roleDetail.error.saveAcl": "
|
|
159
|
-
"customer_accounts.admin.roleDetail.fields.customerAssignable": "
|
|
160
|
-
"customer_accounts.admin.roleDetail.fields.description": "
|
|
161
|
-
"customer_accounts.admin.roleDetail.fields.isDefault": "
|
|
144
|
+
"customer_accounts.admin.roleCreate.fields.slugHint": "Nur Kleinbuchstaben, Zahlen, Bindestriche und Unterstriche.",
|
|
145
|
+
"customer_accounts.admin.roleCreate.fields.slugPlaceholder": "z. B. kaeufer",
|
|
146
|
+
"customer_accounts.admin.roleCreate.flash.created": "Rolle erstellt",
|
|
147
|
+
"customer_accounts.admin.roleCreate.sections.details": "Rollendetails",
|
|
148
|
+
"customer_accounts.admin.roleCreate.sections.options": "Optionen",
|
|
149
|
+
"customer_accounts.admin.roleCreate.title": "Kundenrolle erstellen",
|
|
150
|
+
"customer_accounts.admin.roleDetail.actions.backToList": "Zurück zu Rollen",
|
|
151
|
+
"customer_accounts.admin.roleDetail.actions.cancel": "Abbrechen",
|
|
152
|
+
"customer_accounts.admin.roleDetail.actions.delete": "Löschen",
|
|
153
|
+
"customer_accounts.admin.roleDetail.actions.save": "Änderungen speichern",
|
|
154
|
+
"customer_accounts.admin.roleDetail.actions.saving": "Speichern...",
|
|
155
|
+
"customer_accounts.admin.roleDetail.error.load": "Rolle konnte nicht geladen werden",
|
|
156
|
+
"customer_accounts.admin.roleDetail.error.notFound": "Rolle nicht gefunden",
|
|
157
|
+
"customer_accounts.admin.roleDetail.error.save": "Rolle konnte nicht gespeichert werden",
|
|
158
|
+
"customer_accounts.admin.roleDetail.error.saveAcl": "Berechtigungen konnten nicht gespeichert werden",
|
|
159
|
+
"customer_accounts.admin.roleDetail.fields.customerAssignable": "Kunden können sich selbst zuweisen",
|
|
160
|
+
"customer_accounts.admin.roleDetail.fields.description": "Beschreibung",
|
|
161
|
+
"customer_accounts.admin.roleDetail.fields.isDefault": "Standardrolle (wird neuen Benutzern automatisch zugewiesen)",
|
|
162
162
|
"customer_accounts.admin.roleDetail.fields.name": "Name",
|
|
163
|
-
"customer_accounts.admin.roleDetail.flash.saved": "
|
|
164
|
-
"customer_accounts.admin.roleDetail.loading": "
|
|
165
|
-
"customer_accounts.admin.roleDetail.sections.details": "
|
|
166
|
-
"customer_accounts.admin.roleDetail.sections.options": "
|
|
167
|
-
"customer_accounts.admin.roleDetail.sections.permissions": "Portal
|
|
168
|
-
"customer_accounts.admin.roleDetail.selectAll": "
|
|
169
|
-
"customer_accounts.admin.roles": "
|
|
170
|
-
"customer_accounts.admin.roles.actions.create": "
|
|
171
|
-
"customer_accounts.admin.roles.actions.delete": "
|
|
172
|
-
"customer_accounts.admin.roles.actions.edit": "
|
|
173
|
-
"customer_accounts.admin.roles.assignable": "
|
|
174
|
-
"customer_accounts.admin.roles.columns.customerAssignable": "
|
|
175
|
-
"customer_accounts.admin.roles.columns.description": "
|
|
176
|
-
"customer_accounts.admin.roles.columns.isDefault": "
|
|
163
|
+
"customer_accounts.admin.roleDetail.flash.saved": "Rolle aktualisiert",
|
|
164
|
+
"customer_accounts.admin.roleDetail.loading": "Rolle wird geladen...",
|
|
165
|
+
"customer_accounts.admin.roleDetail.sections.details": "Rollendetails",
|
|
166
|
+
"customer_accounts.admin.roleDetail.sections.options": "Optionen",
|
|
167
|
+
"customer_accounts.admin.roleDetail.sections.permissions": "Portal-Berechtigungen",
|
|
168
|
+
"customer_accounts.admin.roleDetail.selectAll": "Alle auswählen",
|
|
169
|
+
"customer_accounts.admin.roles": "Rollen",
|
|
170
|
+
"customer_accounts.admin.roles.actions.create": "Rolle erstellen",
|
|
171
|
+
"customer_accounts.admin.roles.actions.delete": "Löschen",
|
|
172
|
+
"customer_accounts.admin.roles.actions.edit": "Bearbeiten",
|
|
173
|
+
"customer_accounts.admin.roles.assignable": "Ja",
|
|
174
|
+
"customer_accounts.admin.roles.columns.customerAssignable": "Selbst zuweisbar",
|
|
175
|
+
"customer_accounts.admin.roles.columns.description": "Beschreibung",
|
|
176
|
+
"customer_accounts.admin.roles.columns.isDefault": "Standard",
|
|
177
177
|
"customer_accounts.admin.roles.columns.isSystem": "System",
|
|
178
178
|
"customer_accounts.admin.roles.columns.name": "Name",
|
|
179
179
|
"customer_accounts.admin.roles.columns.slug": "Slug",
|
|
180
|
-
"customer_accounts.admin.roles.confirm.delete": "
|
|
181
|
-
"customer_accounts.admin.roles.default": "
|
|
182
|
-
"customer_accounts.admin.roles.error.delete": "
|
|
183
|
-
"customer_accounts.admin.roles.error.deleteSystem": "
|
|
184
|
-
"customer_accounts.admin.roles.error.load": "
|
|
185
|
-
"customer_accounts.admin.roles.flash.deleted": "
|
|
186
|
-
"customer_accounts.admin.roles.notAssignable": "
|
|
187
|
-
"customer_accounts.admin.roles.searchPlaceholder": "
|
|
180
|
+
"customer_accounts.admin.roles.confirm.delete": "Rolle \"{{name}}\" löschen?",
|
|
181
|
+
"customer_accounts.admin.roles.default": "Standard",
|
|
182
|
+
"customer_accounts.admin.roles.error.delete": "Rolle konnte nicht gelöscht werden",
|
|
183
|
+
"customer_accounts.admin.roles.error.deleteSystem": "Systemrollen können nicht gelöscht werden",
|
|
184
|
+
"customer_accounts.admin.roles.error.load": "Rollen konnten nicht geladen werden",
|
|
185
|
+
"customer_accounts.admin.roles.flash.deleted": "Rolle gelöscht",
|
|
186
|
+
"customer_accounts.admin.roles.notAssignable": "Nein",
|
|
187
|
+
"customer_accounts.admin.roles.searchPlaceholder": "Rollen suchen...",
|
|
188
188
|
"customer_accounts.admin.roles.system": "System",
|
|
189
189
|
"customer_accounts.admin.roles.title": "Rollen",
|
|
190
190
|
"customer_accounts.admin.searchPlaceholder": "Search by name or email...",
|
|
@@ -263,8 +263,8 @@
|
|
|
263
263
|
"customer_accounts.domainMapping.tls.retryButton": "Retry SSL",
|
|
264
264
|
"customer_accounts.domainMapping.verifyNow": "Check Now",
|
|
265
265
|
"customer_accounts.nav.group": "Customers",
|
|
266
|
-
"customer_accounts.nav.role_create": "
|
|
267
|
-
"customer_accounts.nav.role_detail": "
|
|
266
|
+
"customer_accounts.nav.role_create": "Rolle erstellen",
|
|
267
|
+
"customer_accounts.nav.role_detail": "Rollendetails",
|
|
268
268
|
"customer_accounts.nav.roles": "Kundenrollen",
|
|
269
269
|
"customer_accounts.nav.user_detail": "User Detail",
|
|
270
270
|
"customer_accounts.nav.users": "Kundenbenutzer",
|
|
@@ -50,6 +50,13 @@ import {
|
|
|
50
50
|
} from '../../../lib/personCompanyLinkTable'
|
|
51
51
|
import { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'
|
|
52
52
|
import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
|
|
53
|
+
import { runWithCacheTenant } from '@open-mercato/cache'
|
|
54
|
+
import {
|
|
55
|
+
buildCollectionTags,
|
|
56
|
+
canonicalizeResourceTag,
|
|
57
|
+
isCrudCacheEnabled,
|
|
58
|
+
resolveCrudCache,
|
|
59
|
+
} from '@open-mercato/shared/lib/crud/cache'
|
|
53
60
|
|
|
54
61
|
export const metadata = {
|
|
55
62
|
GET: { requireAuth: true, requireFeatures: ['customers.companies.view'] },
|
|
@@ -59,6 +66,66 @@ const paramsSchema = z.object({
|
|
|
59
66
|
id: z.string().uuid(),
|
|
60
67
|
})
|
|
61
68
|
|
|
69
|
+
const COMPANY_DETAIL_CACHE_TTL_MS = 60_000
|
|
70
|
+
|
|
71
|
+
// The customers write commands already invalidate these resource collections via
|
|
72
|
+
// invalidateCrudCache using these exact resourceKind strings. Reusing them as
|
|
73
|
+
// cache tags means the cached detail is flushed by those existing commands with
|
|
74
|
+
// no new wiring (#3664, mirroring #3143). Canonicalize each kind the same way
|
|
75
|
+
// invalidateCrudCache does (camelCase split + lowercase) so the tag shapes match.
|
|
76
|
+
const COMPANY_DETAIL_CACHE_RESOURCE_KINDS = [
|
|
77
|
+
'customers.company',
|
|
78
|
+
'customers.address',
|
|
79
|
+
'customers.tagAssignment',
|
|
80
|
+
'customers.labelAssignment',
|
|
81
|
+
'customers.personCompanyLink',
|
|
82
|
+
'customers.interaction',
|
|
83
|
+
'customers.activity',
|
|
84
|
+
] as const
|
|
85
|
+
|
|
86
|
+
function buildCompanyDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {
|
|
87
|
+
const tags = new Set<string>()
|
|
88
|
+
for (const resourceKind of COMPANY_DETAIL_CACHE_RESOURCE_KINDS) {
|
|
89
|
+
const resource = canonicalizeResourceTag(resourceKind) ?? resourceKind
|
|
90
|
+
for (const tag of buildCollectionTags(resource, tenantId, [organizationId])) {
|
|
91
|
+
tags.add(tag)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return Array.from(tags)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildCompanyDetailCacheKey(parts: {
|
|
98
|
+
tenantId: string | null
|
|
99
|
+
organizationId: string | null
|
|
100
|
+
companyId: string
|
|
101
|
+
selectedOrganizationId: string | null
|
|
102
|
+
filterIds: string[] | null
|
|
103
|
+
allowedIds: string[] | null
|
|
104
|
+
isSuperAdmin: boolean
|
|
105
|
+
viewerUserId: string | null
|
|
106
|
+
interactionMode: string
|
|
107
|
+
includeTokens: string[]
|
|
108
|
+
}): string {
|
|
109
|
+
const sortKeys = (values: string[]): string[] =>
|
|
110
|
+
[...values].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
111
|
+
const filterIds = parts.filterIds ? sortKeys(parts.filterIds).join(',') : 'all'
|
|
112
|
+
const allowedIds = parts.allowedIds ? sortKeys(parts.allowedIds).join(',') : 'all'
|
|
113
|
+
const includeTokens = sortKeys(parts.includeTokens).join(',')
|
|
114
|
+
return [
|
|
115
|
+
'customers:companies:detail',
|
|
116
|
+
`tenant:${parts.tenantId ?? 'null'}`,
|
|
117
|
+
`org:${parts.organizationId ?? 'null'}`,
|
|
118
|
+
`company:${parts.companyId}`,
|
|
119
|
+
`selected:${parts.selectedOrganizationId ?? 'null'}`,
|
|
120
|
+
`filter:${filterIds}`,
|
|
121
|
+
`allowed:${allowedIds}`,
|
|
122
|
+
`super:${parts.isSuperAdmin ? '1' : '0'}`,
|
|
123
|
+
`viewer:${parts.viewerUserId ?? 'null'}`,
|
|
124
|
+
`mode:${parts.interactionMode}`,
|
|
125
|
+
`include:${includeTokens}`,
|
|
126
|
+
].join('|')
|
|
127
|
+
}
|
|
128
|
+
|
|
62
129
|
function parseIncludeParams(request: Request): Set<string> {
|
|
63
130
|
const url = new URL(request.url)
|
|
64
131
|
const raw = url.searchParams.getAll('include')
|
|
@@ -414,6 +481,31 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
414
481
|
userFeatures: undefined,
|
|
415
482
|
})
|
|
416
483
|
|
|
484
|
+
// Opt-in cache (#3664): the company existence + org read-access checks above
|
|
485
|
+
// always run, so authorization is never served from cache. Only the expensive
|
|
486
|
+
// detail sweeps below are cached. Key on tenant/org/company + the RBAC scope,
|
|
487
|
+
// viewer (email privacy), interaction mode, and the requested include set.
|
|
488
|
+
const cacheTenantId = companyScope.tenantId
|
|
489
|
+
const cache = isCrudCacheEnabled() ? resolveCrudCache(container) : null
|
|
490
|
+
const cacheKey = cache
|
|
491
|
+
? buildCompanyDetailCacheKey({
|
|
492
|
+
tenantId: cacheTenantId,
|
|
493
|
+
organizationId: companyScope.organizationId,
|
|
494
|
+
companyId: company.id,
|
|
495
|
+
selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
496
|
+
filterIds: scope?.filterIds ?? null,
|
|
497
|
+
allowedIds: scope?.allowedIds ?? null,
|
|
498
|
+
isSuperAdmin: auth.isSuperAdmin === true,
|
|
499
|
+
viewerUserId,
|
|
500
|
+
interactionMode,
|
|
501
|
+
includeTokens: Array.from(includeTokens),
|
|
502
|
+
})
|
|
503
|
+
: null
|
|
504
|
+
if (cache && cacheKey) {
|
|
505
|
+
const cached = await runWithCacheTenant(cacheTenantId, () => cache.get(cacheKey))
|
|
506
|
+
if (cached) return NextResponse.json(cached)
|
|
507
|
+
}
|
|
508
|
+
|
|
417
509
|
const shouldLoadCanonicalInteractions = includeInteractions || includeActivities || includeTodos
|
|
418
510
|
|
|
419
511
|
// These reads only depend on the resolved company + scope + email visibility
|
|
@@ -961,7 +1053,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
961
1053
|
addresses: addressesCount,
|
|
962
1054
|
}
|
|
963
1055
|
|
|
964
|
-
|
|
1056
|
+
const payload = {
|
|
965
1057
|
interactionMode,
|
|
966
1058
|
company: {
|
|
967
1059
|
id: company.id,
|
|
@@ -1170,7 +1262,22 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
1170
1262
|
name: viewerUserId ? userMap.get(viewerUserId)?.name ?? null : null,
|
|
1171
1263
|
email: viewerUserId ? userMap.get(viewerUserId)?.email ?? auth.email ?? null : auth.email ?? null,
|
|
1172
1264
|
},
|
|
1173
|
-
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (cache && cacheKey) {
|
|
1268
|
+
try {
|
|
1269
|
+
await runWithCacheTenant(cacheTenantId, () =>
|
|
1270
|
+
cache.set(cacheKey, payload, {
|
|
1271
|
+
ttl: COMPANY_DETAIL_CACHE_TTL_MS,
|
|
1272
|
+
tags: buildCompanyDetailCacheTags(cacheTenantId, companyScope.organizationId),
|
|
1273
|
+
}),
|
|
1274
|
+
)
|
|
1275
|
+
} catch (err) {
|
|
1276
|
+
console.warn('[customers:companies] Failed to set company detail cache', err)
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
return NextResponse.json(payload)
|
|
1174
1281
|
}
|
|
1175
1282
|
|
|
1176
1283
|
const companyDetailQuerySchema = z.object({
|
|
@@ -13,7 +13,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
13
13
|
import { booleanOverrideSelectValue } from "./overrideFormConfig";
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
export function
|
|
16
|
+
export function DefaultValueField(props: CrudCustomFieldRenderProps) {
|
|
17
17
|
const t = useT()
|
|
18
18
|
const selectedType = props.values?.type as string;
|
|
19
19
|
|
|
@@ -132,7 +132,7 @@ export function createFieldDefinitions(
|
|
|
132
132
|
id: 'defaultValue',
|
|
133
133
|
label: '',
|
|
134
134
|
type: 'custom',
|
|
135
|
-
component:
|
|
135
|
+
component: (props) => <DefaultValueField {...props} />,
|
|
136
136
|
description: t('feature_toggles.form.fields.defaultValue.description'),
|
|
137
137
|
},
|
|
138
138
|
]
|
|
@@ -20,7 +20,7 @@ export function booleanOverrideSelectValue(value: unknown): 'true' | 'false' {
|
|
|
20
20
|
return value === true || value === 'true' ? 'true' : 'false'
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function
|
|
23
|
+
export function OverrideValueField(props: CrudCustomFieldRenderProps) {
|
|
24
24
|
const t = useT()
|
|
25
25
|
const toggleType = props.values?.toggleType as string;
|
|
26
26
|
const isOverride = props.values?.isOverride as boolean;
|
|
@@ -119,7 +119,7 @@ export function createOverrideFieldDefinitions(
|
|
|
119
119
|
id: 'overrideValue',
|
|
120
120
|
label: '',
|
|
121
121
|
type: 'custom',
|
|
122
|
-
component:
|
|
122
|
+
component: (props) => <OverrideValueField {...props} />,
|
|
123
123
|
description: t('feature_toggles.override.fields.overrideValue.description'),
|
|
124
124
|
},
|
|
125
125
|
]
|
|
@@ -324,6 +324,29 @@ export function ActionCard({
|
|
|
324
324
|
)
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
if (action.status !== 'pending') {
|
|
328
|
+
const isProcessing = action.status === 'processing'
|
|
329
|
+
const statusLabel = action.status === 'accepted'
|
|
330
|
+
? t('inbox_ops.status.accepted', 'Accepted')
|
|
331
|
+
: isProcessing
|
|
332
|
+
? t('inbox_ops.status.processing', 'Processing')
|
|
333
|
+
: action.status
|
|
334
|
+
return (
|
|
335
|
+
<div className="border rounded-lg p-3 md:p-4 bg-muted/50">
|
|
336
|
+
<div className="flex items-center gap-2 mb-2">
|
|
337
|
+
{isProcessing ? (
|
|
338
|
+
<RefreshCw className="h-5 w-5 text-muted-foreground flex-shrink-0 animate-spin" />
|
|
339
|
+
) : (
|
|
340
|
+
<CheckCircle className="h-5 w-5 text-muted-foreground flex-shrink-0" />
|
|
341
|
+
)}
|
|
342
|
+
<span className="text-sm font-medium">{label}</span>
|
|
343
|
+
<span className="text-xs text-muted-foreground">{statusLabel}</span>
|
|
344
|
+
</div>
|
|
345
|
+
<p className="text-sm text-muted-foreground">{displayDescription}</p>
|
|
346
|
+
</div>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
327
350
|
const hasNameIssue = hasContactNameIssue(action)
|
|
328
351
|
|
|
329
352
|
return (
|
|
@@ -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
|
-
export type MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'
|
|
16
|
+
export type MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'unarchive' | 'delete'
|
|
17
17
|
|
|
18
18
|
type BulkExecutionSummary = {
|
|
19
19
|
action: MessageBulkActionId
|
|
@@ -84,6 +84,14 @@ const MESSAGE_BULK_REQUESTS: Record<MessageBulkActionId, MessageBulkRequestConfi
|
|
|
84
84
|
errorKey: 'messages.errors.stateChangeFailed',
|
|
85
85
|
errorFallback: 'Failed to update message state.',
|
|
86
86
|
},
|
|
87
|
+
unarchive: {
|
|
88
|
+
method: 'DELETE',
|
|
89
|
+
buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/archive`,
|
|
90
|
+
successKey: 'messages.bulk.flash.unarchiveSuccess',
|
|
91
|
+
successFallback: '{count} messages moved to inbox.',
|
|
92
|
+
errorKey: 'messages.errors.stateChangeFailed',
|
|
93
|
+
errorFallback: 'Failed to update message state.',
|
|
94
|
+
},
|
|
87
95
|
delete: {
|
|
88
96
|
method: 'DELETE',
|
|
89
97
|
buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}`,
|
|
@@ -110,6 +118,11 @@ const MESSAGE_BULK_ACTIONS: Record<MessageBulkActionId, MessageBulkActionDefinit
|
|
|
110
118
|
labelKey: 'messages.actions.archive',
|
|
111
119
|
labelFallback: 'Archive',
|
|
112
120
|
},
|
|
121
|
+
unarchive: {
|
|
122
|
+
id: 'messages-unarchive',
|
|
123
|
+
labelKey: 'messages.actions.unarchive',
|
|
124
|
+
labelFallback: 'Unarchive',
|
|
125
|
+
},
|
|
113
126
|
delete: {
|
|
114
127
|
id: 'messages-delete',
|
|
115
128
|
labelKey: 'messages.actions.delete',
|
|
@@ -120,7 +133,7 @@ const MESSAGE_BULK_ACTIONS: Record<MessageBulkActionId, MessageBulkActionDefinit
|
|
|
120
133
|
|
|
121
134
|
const MESSAGE_BULK_ACTION_IDS_BY_FOLDER = {
|
|
122
135
|
inbox: ['markRead', 'markUnread', 'archive', 'delete'],
|
|
123
|
-
archived: ['markRead', 'markUnread', 'delete'],
|
|
136
|
+
archived: ['markRead', 'markUnread', 'unarchive', 'delete'],
|
|
124
137
|
sent: ['delete'],
|
|
125
138
|
drafts: ['delete'],
|
|
126
139
|
all: [],
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"messages.bulk.flash.markReadSuccess": "{count} Nachrichten als gelesen markiert.",
|
|
35
35
|
"messages.bulk.flash.markUnreadSuccess": "{count} Nachrichten als ungelesen markiert.",
|
|
36
36
|
"messages.bulk.flash.partial": "{succeeded} von {total} Nachrichten verarbeitet; {failed} fehlgeschlagen.",
|
|
37
|
+
"messages.bulk.flash.unarchiveSuccess": "{count} Nachrichten in den Posteingang verschoben.",
|
|
37
38
|
"messages.compose": "Nachricht verfassen",
|
|
38
39
|
"messages.composeHint": "Erstelle eine Nachricht, hänge Objekte an und sende sie an ausgewählte Empfänger.",
|
|
39
40
|
"messages.composer.attachmentPicker.confirm": "Auswahl anhängen",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"messages.bulk.flash.markReadSuccess": "{count} messages marked as read.",
|
|
35
35
|
"messages.bulk.flash.markUnreadSuccess": "{count} messages marked as unread.",
|
|
36
36
|
"messages.bulk.flash.partial": "{succeeded} of {total} messages processed; {failed} failed.",
|
|
37
|
+
"messages.bulk.flash.unarchiveSuccess": "{count} messages moved to inbox.",
|
|
37
38
|
"messages.compose": "Compose message",
|
|
38
39
|
"messages.composeHint": "Create a message, attach objects, and send it to selected recipients.",
|
|
39
40
|
"messages.composer.attachmentPicker.confirm": "Attach selected",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"messages.bulk.flash.markReadSuccess": "{count} mensajes marcados como leídos.",
|
|
35
35
|
"messages.bulk.flash.markUnreadSuccess": "{count} mensajes marcados como no leídos.",
|
|
36
36
|
"messages.bulk.flash.partial": "Se procesaron {succeeded} de {total} mensajes; {failed} fallaron.",
|
|
37
|
+
"messages.bulk.flash.unarchiveSuccess": "{count} mensajes movidos a la bandeja de entrada.",
|
|
37
38
|
"messages.compose": "Redactar mensaje",
|
|
38
39
|
"messages.composeHint": "Crea un mensaje, adjunta objetos y envíalo a los destinatarios seleccionados.",
|
|
39
40
|
"messages.composer.attachmentPicker.confirm": "Adjuntar seleccionados",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"messages.bulk.flash.markReadSuccess": "Oznaczono {count} wiadomości jako przeczytane.",
|
|
35
35
|
"messages.bulk.flash.markUnreadSuccess": "Oznaczono {count} wiadomości jako nieprzeczytane.",
|
|
36
36
|
"messages.bulk.flash.partial": "Przetworzono {succeeded} z {total} wiadomości; niepowodzeń: {failed}.",
|
|
37
|
+
"messages.bulk.flash.unarchiveSuccess": "Przeniesiono {count} wiadomości do skrzynki odbiorczej.",
|
|
37
38
|
"messages.compose": "Napisz wiadomość",
|
|
38
39
|
"messages.composeHint": "Utwórz wiadomość, dołącz obiekty i wyślij do wybranych odbiorców.",
|
|
39
40
|
"messages.composer.attachmentPicker.confirm": "Dołącz wybrane",
|