@open-mercato/core 0.5.1-develop.2800.bfe2178a4f → 0.5.1-develop.2851.2854b4507f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/action_log/index.js +4 -0
  3. package/dist/generated/entities/action_log/index.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +2 -0
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/audit_logs/data/entities.js +10 -1
  7. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  8. package/dist/modules/audit_logs/data/validators.js +2 -0
  9. package/dist/modules/audit_logs/data/validators.js.map +2 -2
  10. package/dist/modules/audit_logs/migrations/Migration20260423202109.js +15 -0
  11. package/dist/modules/audit_logs/migrations/Migration20260423202109.js.map +7 -0
  12. package/dist/modules/audit_logs/services/accessLogService.js +3 -2
  13. package/dist/modules/audit_logs/services/accessLogService.js.map +3 -3
  14. package/dist/modules/audit_logs/services/actionLogService.js +13 -2
  15. package/dist/modules/audit_logs/services/actionLogService.js.map +3 -3
  16. package/dist/modules/auth/cli.js.map +2 -2
  17. package/dist/modules/customers/api/entity-roles-factory.js +3 -18
  18. package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
  19. package/dist/modules/customers/api/interactions/cancel/route.js +7 -2
  20. package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
  21. package/dist/modules/customers/api/interactions/complete/route.js +7 -2
  22. package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
  23. package/dist/modules/customers/backend/customers/deals/page.js +45 -44
  24. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  25. package/dist/modules/customers/commands/comments.js +6 -0
  26. package/dist/modules/customers/commands/comments.js.map +2 -2
  27. package/dist/modules/customers/components/detail/AssignRoleDialog.js +41 -13
  28. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  29. package/dist/modules/customers/components/detail/CompanyDetailHeader.js +30 -0
  30. package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
  31. package/dist/modules/customers/components/detail/DealDetailHeader.js +32 -0
  32. package/dist/modules/customers/components/detail/DealDetailHeader.js.map +2 -2
  33. package/dist/modules/customers/components/detail/DealWonPopup.js +2 -2
  34. package/dist/modules/customers/components/detail/DealWonPopup.js.map +2 -2
  35. package/dist/modules/customers/components/detail/InlineActivityComposer.js +62 -6
  36. package/dist/modules/customers/components/detail/InlineActivityComposer.js.map +2 -2
  37. package/dist/modules/customers/components/detail/ObjectHistoryButton.js +39 -0
  38. package/dist/modules/customers/components/detail/ObjectHistoryButton.js.map +7 -0
  39. package/dist/modules/customers/components/detail/PersonDetailHeader.js +30 -0
  40. package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
  41. package/dist/modules/customers/components/detail/RolesSection.js +14 -4
  42. package/dist/modules/customers/components/detail/RolesSection.js.map +3 -3
  43. package/dist/modules/customers/components/formConfig.js +16 -2
  44. package/dist/modules/customers/components/formConfig.js.map +2 -2
  45. package/dist/modules/customers/lib/displayName.js +15 -0
  46. package/dist/modules/customers/lib/displayName.js.map +7 -0
  47. package/dist/modules/customers/lib/interactionReadModel.js +1 -2
  48. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  49. package/dist/modules/customers/lib/operationMetadata.js +21 -0
  50. package/dist/modules/customers/lib/operationMetadata.js.map +7 -0
  51. package/dist/modules/messages/components/MessagesInboxPageClient.js +106 -107
  52. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  53. package/dist/modules/messages/components/useMessagesInboxBulkActions.js +235 -0
  54. package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +7 -0
  55. package/generated/entities/action_log/index.ts +2 -0
  56. package/generated/entity-fields-registry.ts +2 -0
  57. package/package.json +3 -3
  58. package/src/modules/audit_logs/data/entities.ts +7 -0
  59. package/src/modules/audit_logs/data/validators.ts +2 -0
  60. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +51 -5
  61. package/src/modules/audit_logs/migrations/Migration20260423202109.ts +15 -0
  62. package/src/modules/audit_logs/services/accessLogService.ts +1 -3
  63. package/src/modules/audit_logs/services/actionLogService.ts +11 -6
  64. package/src/modules/auth/cli.ts +1 -1
  65. package/src/modules/customers/api/entity-roles-factory.ts +3 -23
  66. package/src/modules/customers/api/interactions/cancel/route.ts +7 -2
  67. package/src/modules/customers/api/interactions/complete/route.ts +7 -2
  68. package/src/modules/customers/backend/customers/deals/page.tsx +48 -44
  69. package/src/modules/customers/commands/comments.ts +6 -0
  70. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +37 -9
  71. package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +25 -0
  72. package/src/modules/customers/components/detail/DealDetailHeader.tsx +29 -0
  73. package/src/modules/customers/components/detail/DealWonPopup.tsx +2 -2
  74. package/src/modules/customers/components/detail/InlineActivityComposer.tsx +65 -6
  75. package/src/modules/customers/components/detail/ObjectHistoryButton.tsx +47 -0
  76. package/src/modules/customers/components/detail/PersonDetailHeader.tsx +25 -0
  77. package/src/modules/customers/components/detail/RolesSection.tsx +20 -1
  78. package/src/modules/customers/components/formConfig.tsx +14 -2
  79. package/src/modules/customers/i18n/de.json +12 -0
  80. package/src/modules/customers/i18n/en.json +12 -0
  81. package/src/modules/customers/i18n/es.json +13 -1
  82. package/src/modules/customers/i18n/pl.json +13 -1
  83. package/src/modules/customers/lib/displayName.ts +16 -0
  84. package/src/modules/customers/lib/interactionReadModel.ts +1 -7
  85. package/src/modules/customers/lib/operationMetadata.ts +38 -0
  86. package/src/modules/messages/components/MessagesInboxPageClient.tsx +17 -29
  87. package/src/modules/messages/components/useMessagesInboxBulkActions.ts +324 -0
  88. package/src/modules/messages/i18n/de.json +8 -0
  89. package/src/modules/messages/i18n/en.json +8 -0
  90. package/src/modules/messages/i18n/es.json +8 -0
  91. package/src/modules/messages/i18n/pl.json +8 -0
@@ -7,7 +7,9 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
7
  import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
8
8
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
9
9
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
10
+ import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
10
11
  import { Button } from '@open-mercato/ui/primitives/button'
12
+ import { hasFeature } from '@open-mercato/shared/security/features'
11
13
  import type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'
12
14
  import { RoleAssignmentRow, type RoleAssignment } from './RoleAssignmentRow'
13
15
  import { AssignRoleDialog } from './AssignRoleDialog'
@@ -25,6 +27,11 @@ type GuardedMutationRunner = <T,>(
25
27
 
26
28
  export function RolesSection({ entityType, entityId, entityName }: RolesSectionProps) {
27
29
  const t = useT()
30
+ const { payload } = useBackendChrome()
31
+ const canManageRoleTypes = React.useMemo(
32
+ () => hasFeature(payload?.grantedFeatures ?? [], 'customers.settings.manage'),
33
+ [payload?.grantedFeatures],
34
+ )
28
35
  const [roles, setRoles] = React.useState<RoleAssignment[]>([])
29
36
  const [roleTypes, setRoleTypes] = React.useState<DictionaryEntryOption[]>([])
30
37
  const [loading, setLoading] = React.useState(true)
@@ -183,10 +190,21 @@ export function RolesSection({ entityType, entityId, entityName }: RolesSectionP
183
190
  )
184
191
  }
185
192
 
193
+ const resolvedEntityName =
194
+ entityName && entityName.trim().length
195
+ ? entityName.trim()
196
+ : entityType === 'company'
197
+ ? t('customers.roles.defaultEntityName.company', 'this company')
198
+ : t('customers.roles.defaultEntityName.person', 'this person')
199
+ const groupTitle =
200
+ entityType === 'company'
201
+ ? t('customers.roles.groupTitle.company', 'Roles at {{name}}', { name: resolvedEntityName })
202
+ : t('customers.roles.groupTitle.person', 'My roles with {{name}}', { name: resolvedEntityName })
203
+
186
204
  return (
187
205
  <div className="space-y-4">
188
206
  <div className="space-y-1">
189
- <div className="text-sm font-semibold">{t('customers.roles.groupTitle', 'Roles')}</div>
207
+ <div className="text-sm font-semibold">{groupTitle}</div>
190
208
  <p className="text-xs text-muted-foreground">
191
209
  {entityType === 'company'
192
210
  ? t('customers.roles.subtitle.company', 'Who is responsible for this company on your side')
@@ -305,6 +323,7 @@ export function RolesSection({ entityType, entityId, entityName }: RolesSectionP
305
323
  existingRoleTypes={assignedRoleTypes}
306
324
  existingAssignments={roles}
307
325
  initialRoleType={initialRoleType}
326
+ canManageRoleTypes={canManageRoleTypes}
308
327
  />
309
328
  </div>
310
329
  )
@@ -1553,7 +1553,13 @@ export const createCompanyEditGroups = (t: Translator): CrudFormGroup[] => [
1553
1553
  title: t('customers.roles.groupTitle', 'Roles'),
1554
1554
  column: 1,
1555
1555
  component: ({ values }: CrudFormGroupComponentProps) => (
1556
- values.id ? <RolesSection entityType="company" entityId={values.id as string} /> : null
1556
+ values.id ? (
1557
+ <RolesSection
1558
+ entityType="company"
1559
+ entityId={values.id as string}
1560
+ entityName={typeof values.displayName === 'string' ? values.displayName : null}
1561
+ />
1562
+ ) : null
1557
1563
  ),
1558
1564
  },
1559
1565
  {
@@ -1645,7 +1651,13 @@ export const createPersonEditGroups = (t: Translator): CrudFormGroup[] => [
1645
1651
  title: t('customers.roles.groupTitle', 'Roles'),
1646
1652
  column: 1,
1647
1653
  component: ({ values }: CrudFormGroupComponentProps) => (
1648
- values.id ? <RolesSection entityType="person" entityId={values.id as string} /> : null
1654
+ values.id ? (
1655
+ <RolesSection
1656
+ entityType="person"
1657
+ entityId={values.id as string}
1658
+ entityName={typeof values.displayName === 'string' ? values.displayName : null}
1659
+ />
1660
+ ) : null
1649
1661
  ),
1650
1662
  },
1651
1663
  {
@@ -13,8 +13,10 @@
13
13
  "customers.activities.yearSeparator": "",
14
14
  "customers.activityComposer.cancel": "Cancel",
15
15
  "customers.activityComposer.dateLabel": "Date",
16
+ "customers.activityComposer.descriptionLabel": "Beschreibung",
16
17
  "customers.activityComposer.descriptionPlaceholder": "What happened?",
17
18
  "customers.activityComposer.error": "Failed to save activity",
19
+ "customers.activityComposer.hideWeekPreview": "Wochenvorschau ausblenden",
18
20
  "customers.activityComposer.hint": "Cmd+Enter to save",
19
21
  "customers.activityComposer.save": "Save activity",
20
22
  "customers.activityComposer.saveActivity": "Save activity",
@@ -22,6 +24,7 @@
22
24
  "customers.activityComposer.saving": "Saving...",
23
25
  "customers.activityComposer.schedule": "Schedule",
24
26
  "customers.activityComposer.scheduledLabel": "Scheduled for",
27
+ "customers.activityComposer.showWeekPreview": "Wochenvorschau anzeigen",
25
28
  "customers.activityComposer.title": "Log activity",
26
29
  "customers.activityComposer.today": "Today",
27
30
  "customers.activityComposer.types.call": "Call",
@@ -30,6 +33,7 @@
30
33
  "customers.activityComposer.types.note": "Note",
31
34
  "customers.activityComposer.validation.descriptionRequired": "Description is required",
32
35
  "customers.activityComposer.validation.typeRequired": "Select an activity type",
36
+ "customers.activityComposer.weekPreviewTitle": "Diese Woche",
33
37
  "customers.activityLog.direction.to": "to",
34
38
  "customers.activityLog.direction.with": "with",
35
39
  "customers.activityLog.emptyDescription": "Try broadening the date range or removing some filters.",
@@ -201,6 +205,7 @@
201
205
  "customers.companies.detail.actions.manageTags": "Tags bearbeiten",
202
206
  "customers.companies.detail.actions.more": "Mehr",
203
207
  "customers.companies.detail.actions.save": "Save",
208
+ "customers.companies.detail.actions.sendMessage": "Nachricht senden",
204
209
  "customers.companies.detail.activeDeal": "AKTIVER DEAL",
205
210
  "customers.companies.detail.activities.add": "Aktivität protokollieren",
206
211
  "customers.companies.detail.activities.loading": "Aktivitäten werden geladen…",
@@ -583,6 +588,7 @@
583
588
  "customers.deals.detail.actions.delete": "Delete",
584
589
  "customers.deals.detail.actions.moveStage": "Phase ändern",
585
590
  "customers.deals.detail.actions.save": "Save",
591
+ "customers.deals.detail.actions.sendMessage": "Nachricht senden",
586
592
  "customers.deals.detail.activities.linkEntityDescription": "Activities on a deal still need a customer record for timeline ownership.",
587
593
  "customers.deals.detail.activities.linkEntityTitle": "Link a person or company first",
588
594
  "customers.deals.detail.activities.selectEntityDescription": "Pick the person or company that should own new deal activities and follow-ups.",
@@ -1043,6 +1049,7 @@
1043
1049
  "customers.people.detail.actions.manageTags": "Tags bearbeiten",
1044
1050
  "customers.people.detail.actions.more": "More",
1045
1051
  "customers.people.detail.actions.save": "Save",
1052
+ "customers.people.detail.actions.sendMessage": "Nachricht senden",
1046
1053
  "customers.people.detail.activities.add": "Aktivität hinzufügen",
1047
1054
  "customers.people.detail.activities.addTitle": "Aktivität hinzufügen",
1048
1055
  "customers.people.detail.activities.bodyPlaceholder": "Interaktion beschreiben",
@@ -1707,6 +1714,8 @@
1707
1714
  "customers.roles.changeUser": "Change user",
1708
1715
  "customers.roles.choosePerson": "Person auswählen",
1709
1716
  "customers.roles.configureRoleTypes": "Rollentypen konfigurieren",
1717
+ "customers.roles.defaultEntityName.company": "dieses Unternehmen",
1718
+ "customers.roles.defaultEntityName.person": "diese Person",
1710
1719
  "customers.roles.dialog.assign": "Assign role",
1711
1720
  "customers.roles.dialog.change": "Change",
1712
1721
  "customers.roles.dialog.conflict": "Conflict: {{roles}}",
@@ -1716,6 +1725,7 @@
1716
1725
  "customers.roles.dialog.footerNote": "One person per role · can be changed at any time",
1717
1726
  "customers.roles.dialog.loadMore": "Mehr laden",
1718
1727
  "customers.roles.dialog.loadingMore": "Wird geladen...",
1728
+ "customers.roles.dialog.manageRoleTypes": "Rollentypen verwalten",
1719
1729
  "customers.roles.dialog.next": "Next",
1720
1730
  "customers.roles.dialog.noAvailableRoles": "All available role types are already assigned.",
1721
1731
  "customers.roles.dialog.noResults": "No matching team members found.",
@@ -1736,6 +1746,8 @@
1736
1746
  "customers.roles.emptySlot": "Not assigned",
1737
1747
  "customers.roles.emptyState": "Noch keine Rollen zugewiesen. Klicken Sie unten, um eine Person zuzuweisen.",
1738
1748
  "customers.roles.groupTitle": "Roles",
1749
+ "customers.roles.groupTitle.company": "Rollen bei {{name}}",
1750
+ "customers.roles.groupTitle.person": "Meine Rollen mit {{name}}",
1739
1751
  "customers.roles.loadFailed": "Rollenzuweisungen konnten nicht geladen werden.",
1740
1752
  "customers.roles.loading": "Loading roles...",
1741
1753
  "customers.roles.moreActions": "More actions",
@@ -13,8 +13,10 @@
13
13
  "customers.activities.yearSeparator": "{year}",
14
14
  "customers.activityComposer.cancel": "Cancel",
15
15
  "customers.activityComposer.dateLabel": "Date",
16
+ "customers.activityComposer.descriptionLabel": "Description",
16
17
  "customers.activityComposer.descriptionPlaceholder": "What happened?",
17
18
  "customers.activityComposer.error": "Failed to save activity",
19
+ "customers.activityComposer.hideWeekPreview": "Hide week preview",
18
20
  "customers.activityComposer.hint": "Cmd+Enter to save",
19
21
  "customers.activityComposer.save": "Save activity",
20
22
  "customers.activityComposer.saveActivity": "Save activity",
@@ -22,6 +24,7 @@
22
24
  "customers.activityComposer.saving": "Saving...",
23
25
  "customers.activityComposer.schedule": "Schedule",
24
26
  "customers.activityComposer.scheduledLabel": "Scheduled for",
27
+ "customers.activityComposer.showWeekPreview": "Show week preview",
25
28
  "customers.activityComposer.title": "Log activity",
26
29
  "customers.activityComposer.today": "Today",
27
30
  "customers.activityComposer.types.call": "Call",
@@ -30,6 +33,7 @@
30
33
  "customers.activityComposer.types.note": "Note",
31
34
  "customers.activityComposer.validation.descriptionRequired": "Description is required",
32
35
  "customers.activityComposer.validation.typeRequired": "Select an activity type",
36
+ "customers.activityComposer.weekPreviewTitle": "This week",
33
37
  "customers.activityLog.direction.to": "to",
34
38
  "customers.activityLog.direction.with": "with",
35
39
  "customers.activityLog.emptyDescription": "Try broadening the date range or removing some filters.",
@@ -201,6 +205,7 @@
201
205
  "customers.companies.detail.actions.manageTags": "Edit tags",
202
206
  "customers.companies.detail.actions.more": "More",
203
207
  "customers.companies.detail.actions.save": "Save",
208
+ "customers.companies.detail.actions.sendMessage": "Send message",
204
209
  "customers.companies.detail.activeDeal": "ACTIVE DEAL",
205
210
  "customers.companies.detail.activities.add": "Log activity",
206
211
  "customers.companies.detail.activities.loading": "Loading activities…",
@@ -583,6 +588,7 @@
583
588
  "customers.deals.detail.actions.delete": "Delete",
584
589
  "customers.deals.detail.actions.moveStage": "Move stage",
585
590
  "customers.deals.detail.actions.save": "Save",
591
+ "customers.deals.detail.actions.sendMessage": "Send message",
586
592
  "customers.deals.detail.activities.linkEntityDescription": "Activities on a deal still need a customer record for timeline ownership.",
587
593
  "customers.deals.detail.activities.linkEntityTitle": "Link a person or company first",
588
594
  "customers.deals.detail.activities.selectEntityDescription": "Pick the person or company that should own new deal activities and follow-ups.",
@@ -1043,6 +1049,7 @@
1043
1049
  "customers.people.detail.actions.manageTags": "Edit tags",
1044
1050
  "customers.people.detail.actions.more": "More",
1045
1051
  "customers.people.detail.actions.save": "Save",
1052
+ "customers.people.detail.actions.sendMessage": "Send message",
1046
1053
  "customers.people.detail.activities.add": "Add activity",
1047
1054
  "customers.people.detail.activities.addTitle": "Add activity",
1048
1055
  "customers.people.detail.activities.bodyPlaceholder": "Describe the interaction",
@@ -1707,6 +1714,8 @@
1707
1714
  "customers.roles.changeUser": "Change user",
1708
1715
  "customers.roles.choosePerson": "Choose person",
1709
1716
  "customers.roles.configureRoleTypes": "Configure role types",
1717
+ "customers.roles.defaultEntityName.company": "this company",
1718
+ "customers.roles.defaultEntityName.person": "this person",
1710
1719
  "customers.roles.dialog.assign": "Assign role",
1711
1720
  "customers.roles.dialog.change": "Change",
1712
1721
  "customers.roles.dialog.conflict": "Conflict: {{roles}}",
@@ -1716,6 +1725,7 @@
1716
1725
  "customers.roles.dialog.footerNote": "One person per role · can be changed at any time",
1717
1726
  "customers.roles.dialog.loadMore": "Load more",
1718
1727
  "customers.roles.dialog.loadingMore": "Loading more...",
1728
+ "customers.roles.dialog.manageRoleTypes": "Manage role types",
1719
1729
  "customers.roles.dialog.next": "Next",
1720
1730
  "customers.roles.dialog.noAvailableRoles": "All available role types are already assigned.",
1721
1731
  "customers.roles.dialog.noResults": "No matching team members found.",
@@ -1736,6 +1746,8 @@
1736
1746
  "customers.roles.emptySlot": "Not assigned",
1737
1747
  "customers.roles.emptyState": "No roles assigned yet. Click below to assign a person.",
1738
1748
  "customers.roles.groupTitle": "Roles",
1749
+ "customers.roles.groupTitle.company": "Roles at {{name}}",
1750
+ "customers.roles.groupTitle.person": "My roles with {{name}}",
1739
1751
  "customers.roles.loadFailed": "Failed to load role assignments.",
1740
1752
  "customers.roles.loading": "Loading roles...",
1741
1753
  "customers.roles.moreActions": "More actions",
@@ -13,8 +13,10 @@
13
13
  "customers.activities.yearSeparator": "",
14
14
  "customers.activityComposer.cancel": "Cancel",
15
15
  "customers.activityComposer.dateLabel": "Date",
16
+ "customers.activityComposer.descriptionLabel": "Descripción",
16
17
  "customers.activityComposer.descriptionPlaceholder": "What happened?",
17
18
  "customers.activityComposer.error": "Failed to save activity",
19
+ "customers.activityComposer.hideWeekPreview": "Ocultar vista semanal",
18
20
  "customers.activityComposer.hint": "Cmd+Enter to save",
19
21
  "customers.activityComposer.save": "Save activity",
20
22
  "customers.activityComposer.saveActivity": "Save activity",
@@ -22,6 +24,7 @@
22
24
  "customers.activityComposer.saving": "Saving...",
23
25
  "customers.activityComposer.schedule": "Schedule",
24
26
  "customers.activityComposer.scheduledLabel": "Scheduled for",
27
+ "customers.activityComposer.showWeekPreview": "Mostrar vista semanal",
25
28
  "customers.activityComposer.title": "Log activity",
26
29
  "customers.activityComposer.today": "Today",
27
30
  "customers.activityComposer.types.call": "Call",
@@ -30,6 +33,7 @@
30
33
  "customers.activityComposer.types.note": "Note",
31
34
  "customers.activityComposer.validation.descriptionRequired": "Description is required",
32
35
  "customers.activityComposer.validation.typeRequired": "Select an activity type",
36
+ "customers.activityComposer.weekPreviewTitle": "Esta semana",
33
37
  "customers.activityLog.direction.to": "to",
34
38
  "customers.activityLog.direction.with": "with",
35
39
  "customers.activityLog.emptyDescription": "Try broadening the date range or removing some filters.",
@@ -201,6 +205,7 @@
201
205
  "customers.companies.detail.actions.manageTags": "Editar etiquetas",
202
206
  "customers.companies.detail.actions.more": "Más",
203
207
  "customers.companies.detail.actions.save": "Save",
208
+ "customers.companies.detail.actions.sendMessage": "Enviar mensaje",
204
209
  "customers.companies.detail.activeDeal": "DEAL ACTIVO",
205
210
  "customers.companies.detail.activities.add": "Registrar actividad",
206
211
  "customers.companies.detail.activities.loading": "Cargando actividades…",
@@ -583,6 +588,7 @@
583
588
  "customers.deals.detail.actions.delete": "Delete",
584
589
  "customers.deals.detail.actions.moveStage": "Mover etapa",
585
590
  "customers.deals.detail.actions.save": "Save",
591
+ "customers.deals.detail.actions.sendMessage": "Enviar mensaje",
586
592
  "customers.deals.detail.activities.linkEntityDescription": "Activities on a deal still need a customer record for timeline ownership.",
587
593
  "customers.deals.detail.activities.linkEntityTitle": "Link a person or company first",
588
594
  "customers.deals.detail.activities.selectEntityDescription": "Pick the person or company that should own new deal activities and follow-ups.",
@@ -968,7 +974,7 @@
968
974
  "customers.linking.primary.badge": "Principal",
969
975
  "customers.linking.primary.current": "Principal",
970
976
  "customers.linking.primary.help": "Elige qué registro vinculado debe tratarse como principal.",
971
- "customers.linking.primary.set": "Definir principal",
977
+ "customers.linking.primary.set": "Establecer como principal",
972
978
  "customers.linking.primary.setAction": "Marcar como principal",
973
979
  "customers.linking.resultsCount": "{{count}} resultados",
974
980
  "customers.linking.searchResults": "Resultados de la búsqueda",
@@ -1043,6 +1049,7 @@
1043
1049
  "customers.people.detail.actions.manageTags": "Editar etiquetas",
1044
1050
  "customers.people.detail.actions.more": "More",
1045
1051
  "customers.people.detail.actions.save": "Save",
1052
+ "customers.people.detail.actions.sendMessage": "Enviar mensaje",
1046
1053
  "customers.people.detail.activities.add": "Agregar actividad",
1047
1054
  "customers.people.detail.activities.addTitle": "Agregar actividad",
1048
1055
  "customers.people.detail.activities.bodyPlaceholder": "Describe la interacción",
@@ -1707,6 +1714,8 @@
1707
1714
  "customers.roles.changeUser": "Change user",
1708
1715
  "customers.roles.choosePerson": "Elegir persona",
1709
1716
  "customers.roles.configureRoleTypes": "Configurar tipos de rol",
1717
+ "customers.roles.defaultEntityName.company": "esta empresa",
1718
+ "customers.roles.defaultEntityName.person": "esta persona",
1710
1719
  "customers.roles.dialog.assign": "Assign role",
1711
1720
  "customers.roles.dialog.change": "Change",
1712
1721
  "customers.roles.dialog.conflict": "Conflict: {{roles}}",
@@ -1716,6 +1725,7 @@
1716
1725
  "customers.roles.dialog.footerNote": "One person per role · can be changed at any time",
1717
1726
  "customers.roles.dialog.loadMore": "Cargar más",
1718
1727
  "customers.roles.dialog.loadingMore": "Cargando más...",
1728
+ "customers.roles.dialog.manageRoleTypes": "Gestionar tipos de rol",
1719
1729
  "customers.roles.dialog.next": "Next",
1720
1730
  "customers.roles.dialog.noAvailableRoles": "All available role types are already assigned.",
1721
1731
  "customers.roles.dialog.noResults": "No matching team members found.",
@@ -1736,6 +1746,8 @@
1736
1746
  "customers.roles.emptySlot": "Not assigned",
1737
1747
  "customers.roles.emptyState": "Todavía no hay roles asignados. Haz clic abajo para asignar una persona.",
1738
1748
  "customers.roles.groupTitle": "Roles",
1749
+ "customers.roles.groupTitle.company": "Roles en {{name}}",
1750
+ "customers.roles.groupTitle.person": "Mis roles con {{name}}",
1739
1751
  "customers.roles.loadFailed": "No se pudieron cargar las asignaciones de rol.",
1740
1752
  "customers.roles.loading": "Loading roles...",
1741
1753
  "customers.roles.moreActions": "More actions",
@@ -13,8 +13,10 @@
13
13
  "customers.activities.yearSeparator": "{year}",
14
14
  "customers.activityComposer.cancel": "Anuluj",
15
15
  "customers.activityComposer.dateLabel": "Data",
16
+ "customers.activityComposer.descriptionLabel": "Opis",
16
17
  "customers.activityComposer.descriptionPlaceholder": "Co się wydarzyło?",
17
18
  "customers.activityComposer.error": "Nie udało się zapisać aktywności",
19
+ "customers.activityComposer.hideWeekPreview": "Ukryj podgląd tygodnia",
18
20
  "customers.activityComposer.hint": "Cmd+Enter aby zapisać",
19
21
  "customers.activityComposer.save": "Zapisz aktywność",
20
22
  "customers.activityComposer.saveActivity": "Zapisz aktywność",
@@ -22,6 +24,7 @@
22
24
  "customers.activityComposer.saving": "Zapisywanie...",
23
25
  "customers.activityComposer.schedule": "Schedule",
24
26
  "customers.activityComposer.scheduledLabel": "Zaplanowano na",
27
+ "customers.activityComposer.showWeekPreview": "Pokaż podgląd tygodnia",
25
28
  "customers.activityComposer.title": "Zaloguj aktywność",
26
29
  "customers.activityComposer.today": "Today",
27
30
  "customers.activityComposer.types.call": "Telefon",
@@ -30,6 +33,7 @@
30
33
  "customers.activityComposer.types.note": "Notatka",
31
34
  "customers.activityComposer.validation.descriptionRequired": "Opis jest wymagany",
32
35
  "customers.activityComposer.validation.typeRequired": "Select an activity type",
36
+ "customers.activityComposer.weekPreviewTitle": "Ten tydzień",
33
37
  "customers.activityLog.direction.to": "do",
34
38
  "customers.activityLog.direction.with": "z",
35
39
  "customers.activityLog.emptyDescription": "Poszerz zakres dat albo usuń część filtrów.",
@@ -201,6 +205,7 @@
201
205
  "customers.companies.detail.actions.manageTags": "Edytuj tagi",
202
206
  "customers.companies.detail.actions.more": "Więcej",
203
207
  "customers.companies.detail.actions.save": "Zapisz",
208
+ "customers.companies.detail.actions.sendMessage": "Wyślij wiadomość",
204
209
  "customers.companies.detail.activeDeal": "AKTYWNY DEAL",
205
210
  "customers.companies.detail.activities.add": "Zaloguj aktywność",
206
211
  "customers.companies.detail.activities.loading": "Ładowanie aktywności…",
@@ -583,6 +588,7 @@
583
588
  "customers.deals.detail.actions.delete": "Delete",
584
589
  "customers.deals.detail.actions.moveStage": "Zmień etap",
585
590
  "customers.deals.detail.actions.save": "Save",
591
+ "customers.deals.detail.actions.sendMessage": "Wyślij wiadomość",
586
592
  "customers.deals.detail.activities.linkEntityDescription": "Activities on a deal still need a customer record for timeline ownership.",
587
593
  "customers.deals.detail.activities.linkEntityTitle": "Link a person or company first",
588
594
  "customers.deals.detail.activities.selectEntityDescription": "Pick the person or company that should own new deal activities and follow-ups.",
@@ -968,7 +974,7 @@
968
974
  "customers.linking.primary.badge": "Główny",
969
975
  "customers.linking.primary.current": "Główny",
970
976
  "customers.linking.primary.help": "Wybierz, który powiązany rekord ma być traktowany jako główny.",
971
- "customers.linking.primary.set": "Ustaw główny",
977
+ "customers.linking.primary.set": "Ustaw jako główny",
972
978
  "customers.linking.primary.setAction": "Ustaw jako główny",
973
979
  "customers.linking.resultsCount": "{{count}} wyników",
974
980
  "customers.linking.searchResults": "Wyniki wyszukiwania",
@@ -1043,6 +1049,7 @@
1043
1049
  "customers.people.detail.actions.manageTags": "Edytuj tagi",
1044
1050
  "customers.people.detail.actions.more": "More",
1045
1051
  "customers.people.detail.actions.save": "Zapisz",
1052
+ "customers.people.detail.actions.sendMessage": "Wyślij wiadomość",
1046
1053
  "customers.people.detail.activities.add": "Dodaj aktywność",
1047
1054
  "customers.people.detail.activities.addTitle": "Dodaj aktywność",
1048
1055
  "customers.people.detail.activities.bodyPlaceholder": "Opisz interakcję",
@@ -1707,6 +1714,8 @@
1707
1714
  "customers.roles.changeUser": "Change user",
1708
1715
  "customers.roles.choosePerson": "Wybierz osobę",
1709
1716
  "customers.roles.configureRoleTypes": "Skonfiguruj typy ról",
1717
+ "customers.roles.defaultEntityName.company": "tej firmie",
1718
+ "customers.roles.defaultEntityName.person": "tej osobie",
1710
1719
  "customers.roles.dialog.assign": "Assign role",
1711
1720
  "customers.roles.dialog.change": "Change",
1712
1721
  "customers.roles.dialog.conflict": "Conflict: {{roles}}",
@@ -1716,6 +1725,7 @@
1716
1725
  "customers.roles.dialog.footerNote": "One person per role · can be changed at any time",
1717
1726
  "customers.roles.dialog.loadMore": "Załaduj więcej",
1718
1727
  "customers.roles.dialog.loadingMore": "Ładowanie...",
1728
+ "customers.roles.dialog.manageRoleTypes": "Zarządzaj typami ról",
1719
1729
  "customers.roles.dialog.next": "Next",
1720
1730
  "customers.roles.dialog.noAvailableRoles": "All available role types are already assigned.",
1721
1731
  "customers.roles.dialog.noResults": "No matching team members found.",
@@ -1736,6 +1746,8 @@
1736
1746
  "customers.roles.emptySlot": "Not assigned",
1737
1747
  "customers.roles.emptyState": "Brak przypisanych ról. Kliknij poniżej aby przypisać osobę.",
1738
1748
  "customers.roles.groupTitle": "Role",
1749
+ "customers.roles.groupTitle.company": "Role w {{name}}",
1750
+ "customers.roles.groupTitle.person": "Moje role z {{name}}",
1739
1751
  "customers.roles.loadFailed": "Nie udało się załadować przypisań ról.",
1740
1752
  "customers.roles.loading": "Ładowanie ról...",
1741
1753
  "customers.roles.moreActions": "More actions",
@@ -0,0 +1,16 @@
1
+ export function deriveDisplayNameFromEmail(email: string | null | undefined): string | null {
2
+ if (typeof email !== 'string') return null
3
+ const trimmed = email.trim()
4
+ if (!trimmed.length) return null
5
+ const atIndex = trimmed.indexOf('@')
6
+ const localPart = (atIndex >= 0 ? trimmed.slice(0, atIndex) : trimmed).trim()
7
+ if (!localPart.length) return null
8
+ const segments = localPart
9
+ .split(/[._\-+]+/)
10
+ .map((part) => part.trim())
11
+ .filter((part) => part.length > 0)
12
+ if (segments.length === 0) return null
13
+ return segments
14
+ .map((part) => part.charAt(0).toLocaleUpperCase() + part.slice(1))
15
+ .join(' ')
16
+ }
@@ -63,14 +63,8 @@ function mergeAdditiveRecord<T extends Record<string, unknown>>(base: T, candida
63
63
  }
64
64
  }
65
65
 
66
- // `loadCustomFieldValues` returns keys prefixed with `cf_` (the CRUD-factory projection shape).
67
- // The canonical `InteractionRecord.customValues` contract is unprefixed (e.g. `severity`,
68
- // `priority`, `description`) and every downstream consumer — the UI hooks, the todo/interaction
69
- // compatibility helpers, and the example-customers-sync outbound worker — reads the unprefixed
70
- // form. Normalize at the read-model boundary so the two shapes can't drift again.
71
66
  function normalizeInteractionCustomValues(values: Record<string, unknown> | null | undefined): Record<string, unknown> | null {
72
- const normalized = normalizeCustomFieldResponse(values)
73
- return normalized ?? null
67
+ return normalizeCustomFieldResponse(values) ?? null
74
68
  }
75
69
 
76
70
  async function resolveUserFeatures(
@@ -0,0 +1,38 @@
1
+ import type { NextResponse } from 'next/server'
2
+ import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
3
+
4
+ export type OperationLogEntry = {
5
+ undoToken?: string | null
6
+ id?: string | null
7
+ commandId?: string | null
8
+ actionLabel?: string | null
9
+ resourceKind?: string | null
10
+ resourceId?: string | null
11
+ createdAt?: Date | null
12
+ }
13
+
14
+ export type OperationMetadataFallback = {
15
+ resourceKind: string
16
+ resourceId: string | null
17
+ }
18
+
19
+ export function withOperationMetadata(
20
+ response: NextResponse,
21
+ logEntry: OperationLogEntry | null | undefined,
22
+ fallback: OperationMetadataFallback,
23
+ ): NextResponse {
24
+ if (!logEntry?.undoToken || !logEntry.id || !logEntry.commandId) return response
25
+ response.headers.set(
26
+ 'x-om-operation',
27
+ serializeOperationMetadata({
28
+ id: logEntry.id,
29
+ undoToken: logEntry.undoToken,
30
+ commandId: logEntry.commandId,
31
+ actionLabel: logEntry.actionLabel ?? null,
32
+ resourceKind: logEntry.resourceKind ?? fallback.resourceKind,
33
+ resourceId: logEntry.resourceId ?? fallback.resourceId,
34
+ executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),
35
+ }),
36
+ )
37
+ return response
38
+ }
@@ -16,8 +16,8 @@ import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
16
16
  import { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'
17
17
  import { getMessageUiComponentRegistry } from './utils/typeUiRegistry'
18
18
  import { DefaultMessageListItem } from './defaults/DefaultMessageListItem'
19
-
20
- type MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'
19
+ import { toErrorMessage } from './message-detail/utils'
20
+ import { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'
21
21
 
22
22
  type MessageListItem = {
23
23
  id: string
@@ -63,29 +63,6 @@ type UserListItem = {
63
63
  name?: string | null
64
64
  }
65
65
 
66
- function toErrorMessage(payload: unknown): string | null {
67
- if (!payload) return null
68
- if (typeof payload === 'string') return payload
69
- if (Array.isArray(payload)) {
70
- for (const item of payload) {
71
- const nested = toErrorMessage(item)
72
- if (nested) return nested
73
- }
74
- return null
75
- }
76
- if (typeof payload === 'object') {
77
- const record = payload as Record<string, unknown>
78
- return (
79
- toErrorMessage(record.error)
80
- ?? toErrorMessage(record.message)
81
- ?? toErrorMessage(record.detail)
82
- ?? toErrorMessage(record.details)
83
- ?? null
84
- )
85
- }
86
- return null
87
- }
88
-
89
66
  export function MessagesInboxPageClient() {
90
67
  const router = useRouter()
91
68
  const t = useT()
@@ -108,6 +85,12 @@ export function MessagesInboxPageClient() {
108
85
  const pageSize = 20
109
86
  const folderMenuRef = React.useRef<HTMLDivElement | null>(null)
110
87
  const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])
88
+ const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({
89
+ folder,
90
+ page,
91
+ search,
92
+ filterValues,
93
+ })
111
94
 
112
95
  const listQuery = useQuery({
113
96
  queryKey: [
@@ -393,6 +376,8 @@ export function MessagesInboxPageClient() {
393
376
  title={t('messages.title', 'Messages')}
394
377
  columns={columns}
395
378
  data={rows}
379
+ bulkActions={bulkActions}
380
+ selectionScopeKey={selectionScopeKey}
396
381
  searchValue={search}
397
382
  onSearchChange={(value) => {
398
383
  setSearch(value)
@@ -409,6 +394,7 @@ export function MessagesInboxPageClient() {
409
394
  setFilterValues({})
410
395
  setPage(1)
411
396
  }}
397
+ injectionContext={injectionContext}
412
398
  isLoading={listQuery.isLoading || listQuery.isFetching}
413
399
  pagination={{
414
400
  page,
@@ -443,12 +429,14 @@ export function MessagesInboxPageClient() {
443
429
  const Icon = option.icon
444
430
  const isActive = option.id === folder
445
431
  return (
446
- <button
432
+ <Button
447
433
  key={option.id}
448
434
  type="button"
435
+ variant="ghost"
436
+ size="sm"
449
437
  role="menuitemradio"
450
438
  aria-checked={isActive}
451
- className={`flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent ${isActive ? 'bg-accent/60' : ''}`}
439
+ className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}
452
440
  onClick={() => {
453
441
  setFolder(option.id)
454
442
  setPage(1)
@@ -457,7 +445,7 @@ export function MessagesInboxPageClient() {
457
445
  >
458
446
  <Icon className="h-4 w-4" aria-hidden />
459
447
  <span>{option.label}</span>
460
- </button>
448
+ </Button>
461
449
  )
462
450
  })}
463
451
  </div>
@@ -471,9 +459,9 @@ export function MessagesInboxPageClient() {
471
459
  onRowClick={(row) => {
472
460
  router.push(`/backend/messages/${row.id}`)
473
461
  }}
474
- perspective={{ tableId: 'messages.inbox' }}
475
462
  embedded
476
463
  />
464
+ {ConfirmDialogElement}
477
465
  </div>
478
466
  )
479
467
  }