@open-mercato/core 0.4.5-develop-610fbb24ec → 0.4.5-develop-811deeb983

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 (141) hide show
  1. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +17 -2
  2. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  3. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
  4. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  5. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +30 -0
  6. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  7. package/dist/modules/catalog/data/validators.js +4 -3
  8. package/dist/modules/catalog/data/validators.js.map +2 -2
  9. package/dist/modules/catalog/lib/messageObjectPreviews.js +146 -0
  10. package/dist/modules/catalog/lib/messageObjectPreviews.js.map +7 -0
  11. package/dist/modules/catalog/message-objects.js +95 -0
  12. package/dist/modules/catalog/message-objects.js.map +7 -0
  13. package/dist/modules/currencies/backend/currencies/[id]/page.js +21 -0
  14. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  15. package/dist/modules/currencies/lib/messageObjectPreviews.js +51 -0
  16. package/dist/modules/currencies/lib/messageObjectPreviews.js.map +7 -0
  17. package/dist/modules/currencies/message-objects.js +41 -0
  18. package/dist/modules/currencies/message-objects.js.map +7 -0
  19. package/dist/modules/customers/backend/customers/companies/[id]/page.js +20 -0
  20. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  21. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -1
  22. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  23. package/dist/modules/customers/backend/customers/people/[id]/page.js +20 -0
  24. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  25. package/dist/modules/customers/components/detail/CompanyHighlights.js +18 -14
  26. package/dist/modules/customers/components/detail/CompanyHighlights.js.map +2 -2
  27. package/dist/modules/customers/components/detail/PersonHighlights.js +18 -14
  28. package/dist/modules/customers/components/detail/PersonHighlights.js.map +2 -2
  29. package/dist/modules/customers/lib/messageObjectPreviews.js +41 -5
  30. package/dist/modules/customers/lib/messageObjectPreviews.js.map +2 -2
  31. package/dist/modules/customers/message-objects.js +31 -11
  32. package/dist/modules/customers/message-objects.js.map +2 -2
  33. package/dist/modules/messages/commands/messages.js +3 -0
  34. package/dist/modules/messages/commands/messages.js.map +2 -2
  35. package/dist/modules/messages/components/message-detail/panels/objects-panel.js +6 -1
  36. package/dist/modules/messages/components/message-detail/panels/objects-panel.js.map +2 -2
  37. package/dist/modules/messages/components/message-detail/panels/thread-panel.js +4 -1
  38. package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
  39. package/dist/modules/messages/frontend/messages/view/[token]/page.js +1 -0
  40. package/dist/modules/messages/frontend/messages/view/[token]/page.js.map +2 -2
  41. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -7
  42. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  43. package/dist/modules/resources/lib/messageObjectPreviews.js +43 -0
  44. package/dist/modules/resources/lib/messageObjectPreviews.js.map +7 -0
  45. package/dist/modules/resources/message-objects.js +37 -0
  46. package/dist/modules/resources/message-objects.js.map +7 -0
  47. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +19 -0
  48. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  49. package/dist/modules/sales/backend/sales/documents/[id]/page.js +23 -2
  50. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  51. package/dist/modules/sales/backend/sales/quotes/[id]/page.js +1 -1
  52. package/dist/modules/sales/backend/sales/quotes/[id]/page.js.map +2 -2
  53. package/dist/modules/sales/lib/messageObjectPreviews.js +49 -4
  54. package/dist/modules/sales/lib/messageObjectPreviews.js.map +2 -2
  55. package/dist/modules/sales/message-objects.js +44 -2
  56. package/dist/modules/sales/message-objects.js.map +2 -2
  57. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js +59 -30
  58. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js.map +2 -2
  59. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js +1 -1
  60. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js.map +1 -1
  61. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +8 -30
  62. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  63. package/dist/modules/staff/backend/staff/my-availability/page.js +13 -0
  64. package/dist/modules/staff/backend/staff/my-availability/page.js.map +2 -2
  65. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +8 -31
  66. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  67. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +32 -10
  68. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  69. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +14 -1
  70. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  71. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +14 -1
  72. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  73. package/dist/modules/staff/components/TeamForm.js +4 -2
  74. package/dist/modules/staff/components/TeamForm.js.map +2 -2
  75. package/dist/modules/staff/components/TeamRoleForm.js +4 -2
  76. package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
  77. package/dist/modules/staff/lib/messageObjectPreviews.js +111 -2
  78. package/dist/modules/staff/lib/messageObjectPreviews.js.map +2 -2
  79. package/dist/modules/staff/message-objects.js +79 -8
  80. package/dist/modules/staff/message-objects.js.map +2 -2
  81. package/package.json +3 -3
  82. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +19 -5
  83. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +14 -0
  84. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +40 -0
  85. package/src/modules/catalog/data/validators.ts +47 -45
  86. package/src/modules/catalog/lib/messageObjectPreviews.ts +176 -0
  87. package/src/modules/catalog/message-objects.ts +102 -0
  88. package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -0
  89. package/src/modules/currencies/lib/messageObjectPreviews.ts +65 -0
  90. package/src/modules/currencies/message-objects.ts +40 -0
  91. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +19 -0
  92. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +13 -0
  93. package/src/modules/customers/backend/customers/people/[id]/page.tsx +19 -0
  94. package/src/modules/customers/components/detail/CompanyHighlights.tsx +14 -9
  95. package/src/modules/customers/components/detail/PersonHighlights.tsx +14 -9
  96. package/src/modules/customers/lib/messageObjectPreviews.ts +43 -3
  97. package/src/modules/customers/message-objects.ts +31 -11
  98. package/src/modules/customers/migrations/.snapshot-open-mercato.json +236 -0
  99. package/src/modules/customers/migrations/.snapshot-openmercato.json +236 -0
  100. package/src/modules/messages/commands/messages.ts +4 -0
  101. package/src/modules/messages/components/message-detail/panels/objects-panel.tsx +8 -1
  102. package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +3 -0
  103. package/src/modules/messages/frontend/messages/view/[token]/page.tsx +1 -0
  104. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +20 -4
  105. package/src/modules/resources/lib/messageObjectPreviews.ts +55 -0
  106. package/src/modules/resources/message-objects.ts +36 -0
  107. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +18 -0
  108. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +23 -0
  109. package/src/modules/sales/backend/sales/quotes/[id]/page.tsx +1 -1
  110. package/src/modules/sales/lib/messageObjectPreviews.ts +54 -4
  111. package/src/modules/sales/message-objects.ts +44 -2
  112. package/src/modules/sales/widgets/messages/SalesDocumentMessageDetail.tsx +72 -34
  113. package/src/modules/sales/widgets/messages/SalesDocumentMessagePreview.tsx +1 -1
  114. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +7 -29
  115. package/src/modules/staff/backend/staff/my-availability/page.tsx +14 -0
  116. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +8 -30
  117. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +28 -7
  118. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +12 -0
  119. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +12 -0
  120. package/src/modules/staff/components/TeamForm.tsx +3 -0
  121. package/src/modules/staff/components/TeamRoleForm.tsx +3 -0
  122. package/src/modules/staff/lib/messageObjectPreviews.ts +133 -2
  123. package/src/modules/staff/message-objects.ts +79 -8
  124. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js +0 -51
  125. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js.map +0 -7
  126. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js +0 -35
  127. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js.map +0 -7
  128. package/dist/modules/customers/widgets/messages/index.js +0 -7
  129. package/dist/modules/customers/widgets/messages/index.js.map +0 -7
  130. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js +0 -51
  131. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js.map +0 -7
  132. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js +0 -34
  133. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js.map +0 -7
  134. package/dist/modules/staff/widgets/messages/index.js +0 -7
  135. package/dist/modules/staff/widgets/messages/index.js.map +0 -7
  136. package/src/modules/customers/widgets/messages/CustomerMessageObjectDetail.tsx +0 -57
  137. package/src/modules/customers/widgets/messages/CustomerMessageObjectPreview.tsx +0 -49
  138. package/src/modules/customers/widgets/messages/index.ts +0 -2
  139. package/src/modules/staff/widgets/messages/StaffMessageObjectDetail.tsx +0 -57
  140. package/src/modules/staff/widgets/messages/StaffMessageObjectPreview.tsx +0 -44
  141. package/src/modules/staff/widgets/messages/index.ts +0 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/staff/message-objects.ts"],
4
- "sourcesContent": ["import type { MessageObjectTypeDefinition } from '@open-mercato/shared/modules/messages/types'\nimport { LeaveRequestDetail } from './components/LeaveRequestDetail'\nimport { LeaveRequestPreview } from './components/LeaveRequestPreview'\nimport { StaffMessageObjectDetail } from './widgets/messages/StaffMessageObjectDetail'\nimport { StaffMessageObjectPreview } from './widgets/messages/StaffMessageObjectPreview'\n\nexport const messageObjectTypes: MessageObjectTypeDefinition[] = [\n {\n module: 'staff',\n entityType: 'leave_request',\n messageTypes: ['default', 'messages.defaultWithObjects', 'staff.leave_request_approval', 'staff.leave_request_status'],\n entityId: 'staff:staff_leave_request',\n optionLabelField: 'id',\n optionSubtitleField: 'status',\n labelKey: 'staff.leaveRequests.page.title',\n icon: 'calendar-clock',\n PreviewComponent: LeaveRequestPreview,\n DetailComponent: LeaveRequestDetail,\n actions: [\n {\n id: 'approve',\n labelKey: 'staff.notifications.leaveRequest.actions.approve',\n variant: 'default',\n commandId: 'staff.leave-requests.accept',\n icon: 'check',\n },\n {\n id: 'reject',\n labelKey: 'staff.notifications.leaveRequest.actions.reject',\n variant: 'destructive',\n commandId: 'staff.leave-requests.reject',\n icon: 'x',\n },\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/staff/leave-requests/{entityId}',\n icon: 'external-link',\n isTerminal: false,\n },\n ],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return {\n title: 'Leave request',\n subtitle: entityId,\n }\n }\n const { loadLeaveRequestPreview } = await import('./lib/messageObjectPreviews')\n return loadLeaveRequestPreview(entityId, ctx)\n },\n },\n {\n module: 'staff',\n entityType: 'team',\n messageTypes: ['default', 'messages.defaultWithObjects'],\n entityId: 'staff:staff_team',\n optionLabelField: 'name',\n optionSubtitleField: 'description',\n labelKey: 'staff.teams.page.title',\n icon: 'users',\n PreviewComponent: StaffMessageObjectPreview,\n DetailComponent: StaffMessageObjectDetail,\n actions: [],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return {\n title: 'Team',\n subtitle: entityId,\n }\n }\n const { loadTeamPreview } = await import('./lib/messageObjectPreviews')\n return loadTeamPreview(entityId, ctx)\n },\n },\n {\n module: 'staff',\n entityType: 'team_member',\n messageTypes: ['default', 'messages.defaultWithObjects'],\n entityId: 'staff:staff_team_member',\n optionLabelField: 'displayName',\n optionSubtitleField: 'email',\n labelKey: 'staff.teamMembers.page.title',\n icon: 'user-round',\n PreviewComponent: StaffMessageObjectPreview,\n DetailComponent: StaffMessageObjectDetail,\n actions: [],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return {\n title: 'Team member',\n subtitle: entityId,\n }\n }\n const { loadTeamMemberPreview } = await import('./lib/messageObjectPreviews')\n return loadTeamMemberPreview(entityId, ctx)\n },\n },\n]\n\nexport default messageObjectTypes\n"],
5
- "mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;AACzC,SAAS,iCAAiC;AAEnC,MAAM,qBAAoD;AAAA,EAC/D;AAAA,IACE,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc,CAAC,WAAW,+BAA+B,gCAAgC,4BAA4B;AAAA,IACrH,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,UAAU;AAAA,IACV,MAAM;AAAA,IACN,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,WAAW;AAAA,QACX,MAAM;AAAA,MACR;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,WAAW;AAAA,QACX,MAAM;AAAA,MACR;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF;AAAA,IACA,aAAa,OAAO,UAAU,QAAQ;AACpC,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,QACZ;AAAA,MACF;AACA,YAAM,EAAE,wBAAwB,IAAI,MAAM,OAAO,6BAA6B;AAC9E,aAAO,wBAAwB,UAAU,GAAG;AAAA,IAC9C;AAAA,EACF;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc,CAAC,WAAW,6BAA6B;AAAA,IACvD,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,UAAU;AAAA,IACV,MAAM;AAAA,IACN,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,SAAS,CAAC;AAAA,IACV,aAAa,OAAO,UAAU,QAAQ;AACpC,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,QACZ;AAAA,MACF;AACA,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,6BAA6B;AACtE,aAAO,gBAAgB,UAAU,GAAG;AAAA,IACtC;AAAA,EACF;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc,CAAC,WAAW,6BAA6B;AAAA,IACvD,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,UAAU;AAAA,IACV,MAAM;AAAA,IACN,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,SAAS,CAAC;AAAA,IACV,aAAa,OAAO,UAAU,QAAQ;AACpC,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,QACZ;AAAA,MACF;AACA,YAAM,EAAE,sBAAsB,IAAI,MAAM,OAAO,6BAA6B;AAC5E,aAAO,sBAAsB,UAAU,GAAG;AAAA,IAC5C;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
4
+ "sourcesContent": ["import type { MessageObjectTypeDefinition } from '@open-mercato/shared/modules/messages/types'\nimport { MessageObjectDetail, MessageObjectPreview } from '@open-mercato/ui/backend/messages'\nimport { LeaveRequestDetail } from './components/LeaveRequestDetail'\nimport { LeaveRequestPreview } from './components/LeaveRequestPreview'\n\nexport const messageObjectTypes: MessageObjectTypeDefinition[] = [\n {\n module: 'staff',\n entityType: 'leave_request',\n messageTypes: ['default', 'messages.defaultWithObjects', 'staff.leave_request_approval', 'staff.leave_request_status'],\n entityId: 'staff:staff_leave_request',\n optionLabelField: 'id',\n optionSubtitleField: 'status',\n labelKey: 'staff.leaveRequests.page.title',\n icon: 'calendar-clock',\n PreviewComponent: LeaveRequestPreview,\n DetailComponent: LeaveRequestDetail,\n actions: [\n {\n id: 'approve',\n labelKey: 'staff.notifications.leaveRequest.actions.approve',\n variant: 'default',\n commandId: 'staff.leave-requests.accept',\n icon: 'check',\n },\n {\n id: 'reject',\n labelKey: 'staff.notifications.leaveRequest.actions.reject',\n variant: 'destructive',\n commandId: 'staff.leave-requests.reject',\n icon: 'x',\n },\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/staff/leave-requests/{entityId}',\n icon: 'external-link',\n isTerminal: false,\n },\n ],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return {\n title: 'Leave request',\n subtitle: entityId,\n }\n }\n const { loadLeaveRequestPreview } = await import('./lib/messageObjectPreviews')\n return loadLeaveRequestPreview(entityId, ctx)\n },\n },\n {\n module: 'staff',\n entityType: 'team',\n messageTypes: ['default', 'messages.defaultWithObjects'],\n entityId: 'staff:staff_team',\n optionLabelField: 'name',\n optionSubtitleField: 'description',\n labelKey: 'staff.teams.page.title',\n icon: 'users',\n PreviewComponent: MessageObjectPreview,\n DetailComponent: MessageObjectDetail,\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/staff/teams/{entityId}/edit',\n icon: 'external-link',\n isTerminal: false,\n },\n ],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return {\n title: 'Team',\n subtitle: entityId,\n }\n }\n const { loadTeamPreview } = await import('./lib/messageObjectPreviews')\n return loadTeamPreview(entityId, ctx)\n },\n },\n {\n module: 'staff',\n entityType: 'team_member',\n messageTypes: ['default', 'messages.defaultWithObjects'],\n entityId: 'staff:staff_team_member',\n optionLabelField: 'displayName',\n optionSubtitleField: 'email',\n labelKey: 'staff.teamMembers.page.title',\n icon: 'user-round',\n PreviewComponent: MessageObjectPreview,\n DetailComponent: MessageObjectDetail,\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/staff/team-members/{entityId}',\n icon: 'external-link',\n isTerminal: false,\n },\n ],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return {\n title: 'Team member',\n subtitle: entityId,\n }\n }\n const { loadTeamMemberPreview } = await import('./lib/messageObjectPreviews')\n return loadTeamMemberPreview(entityId, ctx)\n },\n },\n {\n module: 'staff',\n entityType: 'team_role',\n messageTypes: ['default', 'messages.defaultWithObjects'],\n entityId: 'staff:staff_team_role',\n optionLabelField: 'name',\n optionSubtitleField: 'description',\n labelKey: 'staff.messageObjects.teamRole.title',\n icon: 'shield',\n PreviewComponent: MessageObjectPreview,\n DetailComponent: MessageObjectDetail,\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/staff/team-roles/{entityId}/edit',\n },\n ],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return { title: 'Team role', subtitle: entityId }\n }\n const { loadStaffTeamRolePreview } = await import('./lib/messageObjectPreviews')\n return loadStaffTeamRolePreview(entityId, ctx)\n },\n },\n {\n module: 'staff',\n entityType: 'my_availability',\n messageTypes: ['default', 'messages.defaultWithObjects'],\n entityId: 'planner:planner_availability_rule_set',\n optionLabelField: 'name',\n optionSubtitleField: 'description',\n labelKey: 'staff.messageObjects.myAvailability.title',\n icon: 'calendar-clock',\n PreviewComponent: MessageObjectPreview,\n DetailComponent: MessageObjectDetail,\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/staff/my-availability',\n },\n ],\n loadPreview: async (entityId, ctx) => {\n if (typeof window !== 'undefined') {\n return { title: 'My availability', subtitle: entityId }\n }\n const { loadStaffAvailabilityPreview } = await import('./lib/messageObjectPreviews')\n return loadStaffAvailabilityPreview(entityId, ctx)\n },\n },\n]\n\nexport default messageObjectTypes\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.4.5-develop-610fbb24ec",
3
+ "version": "0.4.5-develop-811deeb983",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -207,7 +207,8 @@
207
207
  }
208
208
  },
209
209
  "dependencies": {
210
- "@open-mercato/shared": "0.4.5-develop-610fbb24ec",
210
+ "@open-mercato/shared": "0.4.5-develop-811deeb983",
211
+ "@types/html-to-text": "^9.0.4",
211
212
  "@types/semver": "^7.5.8",
212
213
  "@xyflow/react": "^12.6.0",
213
214
  "ai": "^6.0.0",
@@ -220,7 +221,6 @@
220
221
  "@testing-library/dom": "^10.4.1",
221
222
  "@testing-library/jest-dom": "^6.9.1",
222
223
  "@testing-library/react": "^16.3.1",
223
- "@types/html-to-text": "^9.0.4",
224
224
  "@types/jest": "^30.0.0",
225
225
  "jest": "^30.2.0",
226
226
  "jest-environment-jsdom": "^30.2.0",
@@ -11,6 +11,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
11
11
  import { E } from '#generated/entities.ids.generated'
12
12
  import { CategorySelect } from '../../../../../components/categories/CategorySelect'
13
13
  import { CategorySlugFieldSync } from '../../../../../components/categories/CategorySlugFieldSync'
14
+ import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
14
15
 
15
16
  type CategoryRow = {
16
17
  id: string
@@ -214,11 +215,24 @@ export default function EditCatalogCategoryPage({ params }: { params?: { id?: st
214
215
  submitLabel={t('catalog.categories.form.action.save', 'Save')}
215
216
  cancelHref="/backend/catalog/categories"
216
217
  successRedirect={`/backend/catalog/categories?flash=${encodeURIComponent(t('catalog.categories.flash.updated', 'Category updated'))}&type=success`}
217
- extraActions={pathLabel ? (
218
- <span className="text-xs text-muted-foreground">
219
- {t('catalog.categories.form.pathLabel', { path: pathLabel })}
220
- </span>
221
- ) : null}
218
+ extraActions={(
219
+ <>
220
+ <SendObjectMessageDialog
221
+ object={{
222
+ entityModule: 'catalog',
223
+ entityType: 'category',
224
+ entityId: categoryId,
225
+ previewData: { title: initialValues?.name ?? categoryId },
226
+ }}
227
+ viewHref={`/backend/catalog/categories/${categoryId}/edit`}
228
+ />
229
+ {pathLabel ? (
230
+ <span className="text-xs text-muted-foreground">
231
+ {t('catalog.categories.form.pathLabel', { path: pathLabel })}
232
+ </span>
233
+ ) : null}
234
+ </>
235
+ )}
222
236
  onSubmit={async (values) => {
223
237
  await submitCategoryUpdate(categoryId, values, t)
224
238
  }}
@@ -106,6 +106,7 @@ import {
106
106
  DialogHeader,
107
107
  DialogTitle,
108
108
  } from "@open-mercato/ui/primitives/dialog";
109
+ import { SendObjectMessageDialog } from "@open-mercato/ui/backend/messages/SendObjectMessageDialog.tsx";
109
110
 
110
111
  const MarkdownEditor = dynamic(() => import("@uiw/react-md-editor"), {
111
112
  ssr: false,
@@ -1204,6 +1205,19 @@ export default function EditCatalogProductPage({
1204
1205
  resourceKind: "catalog.product",
1205
1206
  resourceId: productId ? String(productId) : "",
1206
1207
  }}
1208
+ extraActions={productId ? (
1209
+ <SendObjectMessageDialog
1210
+ object={{
1211
+ entityModule: "catalog",
1212
+ entityType: "product",
1213
+ entityId: productId,
1214
+ previewData: {
1215
+ title: initialValues?.title ?? productId,
1216
+ }
1217
+ }}
1218
+ viewHref={`/backend/catalog/products/${productId}/edit`}
1219
+ />
1220
+ ) : undefined}
1207
1221
  fields={[]}
1208
1222
  groups={groups}
1209
1223
  injectionSpotId="crud-form:catalog.product"
@@ -11,6 +11,7 @@ import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/ap
11
11
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
12
12
  import { useT } from '@open-mercato/shared/lib/i18n/context'
13
13
  import { E } from '#generated/entities.ids.generated'
14
+ import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
14
15
  import {
15
16
  type VariantFormValues,
16
17
  type VariantPriceDraft,
@@ -61,6 +62,21 @@ type AttachmentListResponse = {
61
62
  items?: ProductMediaItem[]
62
63
  }
63
64
 
65
+ function resolveVariantPriceLabel(prices: Record<string, VariantPriceDraft> | undefined): string | null {
66
+ if (!prices || typeof prices !== 'object') return null
67
+ const entries = Object.values(prices)
68
+ for (const entry of entries) {
69
+ const amount = typeof entry?.amount === 'string' ? entry.amount.trim() : ''
70
+ if (!amount) continue
71
+ const currencyCode =
72
+ typeof entry.currencyCode === 'string' && entry.currencyCode.trim().length
73
+ ? entry.currencyCode.trim().toUpperCase()
74
+ : null
75
+ return currencyCode ? `${currencyCode} ${amount}` : amount
76
+ }
77
+ return null
78
+ }
79
+
64
80
  export default function EditVariantPage({ params }: { params?: { productId?: string; variantId?: string } }) {
65
81
  const router = useRouter()
66
82
  const t = useT()
@@ -407,6 +423,30 @@ export default function EditVariantPage({ params }: { params?: { productId?: str
407
423
  title={formTitle}
408
424
  backHref={productVariantsHref}
409
425
  versionHistory={{ resourceKind: 'catalog.variant', resourceId: variantId ? String(variantId) : '' }}
426
+ extraActions={(
427
+ <SendObjectMessageDialog
428
+ object={{
429
+ entityModule: 'catalog',
430
+ entityType: 'variant',
431
+ entityId: variantId,
432
+ previewData: {
433
+ title:
434
+ (typeof initialValues?.name === 'string' && initialValues.name.trim().length
435
+ ? initialValues.name
436
+ : variantId),
437
+ metadata: {
438
+ [t('catalog.variants.form.skuLabel')]:
439
+ (typeof initialValues?.sku === 'string' && initialValues.sku.trim().length
440
+ ? initialValues.sku
441
+ : '-'),
442
+ [t('catalog.variants.form.pricesLabel')]:
443
+ resolveVariantPriceLabel(initialValues?.prices) ?? '-',
444
+ },
445
+ },
446
+ }}
447
+ viewHref={`/backend/catalog/products/${currentProductId}/variants/${variantId}`}
448
+ />
449
+ )}
410
450
  fields={[]}
411
451
  groups={groups}
412
452
  entityId={E.catalog.catalog_product_variant}
@@ -159,57 +159,59 @@ function productUomCrossFieldRefinement(
159
159
  }
160
160
  }
161
161
 
162
- export const productCreateSchema = scoped
163
- .extend({
164
- title: z.string().trim().min(1).max(255),
165
- subtitle: z.string().trim().max(255).optional(),
166
- description: z.string().trim().max(4000).optional(),
167
- sku: skuSchema.optional(),
168
- handle: handleSchema.optional(),
169
- taxRateId: uuid().nullable().optional(),
170
- taxRate: z.coerce.number().min(0).max(100).optional().nullable(),
171
- productType: productTypeSchema.default('simple'),
172
- statusEntryId: uuid().optional(),
173
- primaryCurrencyCode: currencyCodeSchema.optional(),
174
- defaultUnit: z.string().trim().max(50).optional().nullable(),
175
- defaultSalesUnit: z.string().trim().max(50).optional().nullable(),
176
- defaultSalesUnitQuantity: z.coerce.number().positive().optional(),
177
- uomRoundingScale: z.coerce.number().int().min(0).max(6).optional(),
178
- uomRoundingMode: uomRoundingModeSchema.optional(),
179
- unitPriceEnabled: z.boolean().optional(),
180
- unitPriceReferenceUnit: unitPriceReferenceUnitSchema.nullable().optional(),
181
- unitPriceBaseQuantity: z.coerce.number().positive().optional(),
182
- unitPrice: unitPriceConfigSchema.optional(),
183
- defaultMediaId: uuid().optional().nullable(),
184
- defaultMediaUrl: z.string().trim().max(500).optional().nullable(),
185
- weightValue: z.coerce.number().min(0).optional().nullable(),
186
- weightUnit: z.string().trim().max(25).optional().nullable(),
187
- dimensions: z
188
- .object({
189
- width: z.coerce.number().min(0).optional(),
190
- height: z.coerce.number().min(0).optional(),
191
- depth: z.coerce.number().min(0).optional(),
192
- unit: z.string().trim().max(25).optional(),
193
- })
194
- .optional()
195
- .nullable(),
196
- optionSchemaId: uuid().nullable().optional(),
197
- optionSchema: optionSchema.optional(),
198
- customFieldsetCode: slugSchema.nullable().optional(),
199
- isConfigurable: z.boolean().optional(),
200
- isActive: z.boolean().optional(),
201
- metadata: metadataSchema,
202
- offers: z.array(offerInputSchema.omit({ id: true })).optional(),
203
- categoryIds: z.array(uuid()).max(100).optional(),
204
- tags: z.array(tagLabelSchema).max(100).optional(),
205
- })
162
+ // Base schema without refinements (used for .partial() in update schema)
163
+ const productBaseSchema = scoped.extend({
164
+ title: z.string().trim().min(1).max(255),
165
+ subtitle: z.string().trim().max(255).optional(),
166
+ description: z.string().trim().max(4000).optional(),
167
+ sku: skuSchema.optional(),
168
+ handle: handleSchema.optional(),
169
+ taxRateId: uuid().nullable().optional(),
170
+ taxRate: z.coerce.number().min(0).max(100).optional().nullable(),
171
+ productType: productTypeSchema.default('simple'),
172
+ statusEntryId: uuid().optional(),
173
+ primaryCurrencyCode: currencyCodeSchema.optional(),
174
+ defaultUnit: z.string().trim().max(50).optional().nullable(),
175
+ defaultSalesUnit: z.string().trim().max(50).optional().nullable(),
176
+ defaultSalesUnitQuantity: z.coerce.number().positive().optional(),
177
+ uomRoundingScale: z.coerce.number().int().min(0).max(6).optional(),
178
+ uomRoundingMode: uomRoundingModeSchema.optional(),
179
+ unitPriceEnabled: z.boolean().optional(),
180
+ unitPriceReferenceUnit: unitPriceReferenceUnitSchema.nullable().optional(),
181
+ unitPriceBaseQuantity: z.coerce.number().positive().optional(),
182
+ unitPrice: unitPriceConfigSchema.optional(),
183
+ defaultMediaId: uuid().optional().nullable(),
184
+ defaultMediaUrl: z.string().trim().max(500).optional().nullable(),
185
+ weightValue: z.coerce.number().min(0).optional().nullable(),
186
+ weightUnit: z.string().trim().max(25).optional().nullable(),
187
+ dimensions: z
188
+ .object({
189
+ width: z.coerce.number().min(0).optional(),
190
+ height: z.coerce.number().min(0).optional(),
191
+ depth: z.coerce.number().min(0).optional(),
192
+ unit: z.string().trim().max(25).optional(),
193
+ })
194
+ .optional()
195
+ .nullable(),
196
+ optionSchemaId: uuid().nullable().optional(),
197
+ optionSchema: optionSchema.optional(),
198
+ customFieldsetCode: slugSchema.nullable().optional(),
199
+ isConfigurable: z.boolean().optional(),
200
+ isActive: z.boolean().optional(),
201
+ metadata: metadataSchema,
202
+ offers: z.array(offerInputSchema.omit({ id: true })).optional(),
203
+ categoryIds: z.array(uuid()).max(100).optional(),
204
+ tags: z.array(tagLabelSchema).max(100).optional(),
205
+ })
206
+
207
+ export const productCreateSchema = productBaseSchema
206
208
  .superRefine(productUomCrossFieldRefinement)
207
209
 
208
210
  export const productUpdateSchema = z
209
211
  .object({
210
212
  id: uuid(),
211
213
  })
212
- .merge(productCreateSchema.partial())
214
+ .merge(productBaseSchema.partial())
213
215
  .extend({
214
216
  productType: productTypeSchema.optional(),
215
217
  })
@@ -0,0 +1,176 @@
1
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
2
+ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
4
+ import type { ObjectPreviewData } from '@open-mercato/shared/modules/messages/types'
5
+ import type { EntityManager } from '@mikro-orm/postgresql'
6
+ import { CatalogProduct, CatalogProductCategory, CatalogProductPrice, CatalogProductVariant } from '../data/entities'
7
+
8
+ type PreviewContext = {
9
+ tenantId: string
10
+ organizationId?: string | null
11
+ }
12
+
13
+ async function resolveEm() {
14
+ const { resolve } = await createRequestContainer()
15
+ return resolve('em') as EntityManager
16
+ }
17
+
18
+ function formatVariantPrice(amount: string | null | undefined, currencyCode: string | null | undefined): string | null {
19
+ if (!amount) return null
20
+ const value = Number(amount)
21
+ if (!Number.isFinite(value)) return currencyCode ? `${currencyCode.toUpperCase()} ${amount}` : amount
22
+ if (!currencyCode) return value.toLocaleString()
23
+ try {
24
+ return new Intl.NumberFormat(undefined, {
25
+ style: 'currency',
26
+ currency: currencyCode.toUpperCase(),
27
+ }).format(value)
28
+ } catch {
29
+ return `${currencyCode.toUpperCase()} ${value.toLocaleString()}`
30
+ }
31
+ }
32
+
33
+ export async function loadCatalogProductPreview(
34
+ entityId: string,
35
+ ctx: PreviewContext,
36
+ ): Promise<ObjectPreviewData> {
37
+ const { t } = await resolveTranslations()
38
+ const defaultTitle = t('catalog.messageObjects.product.title')
39
+
40
+ if (!ctx.organizationId) {
41
+ return { title: defaultTitle, subtitle: entityId }
42
+ }
43
+
44
+ const em = await resolveEm()
45
+ const entity = await findOneWithDecryption(
46
+ em,
47
+ CatalogProduct,
48
+ {
49
+ id: entityId,
50
+ tenantId: ctx.tenantId,
51
+ organizationId: ctx.organizationId,
52
+ deletedAt: null,
53
+ },
54
+ undefined,
55
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
56
+ )
57
+
58
+ if (!entity) {
59
+ return {
60
+ title: defaultTitle,
61
+ subtitle: entityId,
62
+ status: t('customers.messageObjects.notFound'),
63
+ statusColor: 'gray',
64
+ }
65
+ }
66
+
67
+ return {
68
+ title: entity.title,
69
+ subtitle: entity.subtitle ?? undefined,
70
+ }
71
+ }
72
+
73
+ export async function loadCatalogVariantPreview(
74
+ entityId: string,
75
+ ctx: PreviewContext,
76
+ ): Promise<ObjectPreviewData> {
77
+ const { t } = await resolveTranslations()
78
+ const defaultTitle = t('catalog.variants.form.editTitle')
79
+
80
+ if (!ctx.organizationId) {
81
+ return { title: defaultTitle, subtitle: entityId }
82
+ }
83
+
84
+ const em = await resolveEm()
85
+ const variant = await findOneWithDecryption(
86
+ em,
87
+ CatalogProductVariant,
88
+ {
89
+ id: entityId,
90
+ tenantId: ctx.tenantId,
91
+ organizationId: ctx.organizationId,
92
+ deletedAt: null,
93
+ },
94
+ undefined,
95
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
96
+ )
97
+
98
+ if (!variant) {
99
+ return {
100
+ title: defaultTitle,
101
+ subtitle: entityId,
102
+ status: t('catalog.variants.form.errors.notFound'),
103
+ statusColor: 'gray',
104
+ }
105
+ }
106
+
107
+ const prices = await findWithDecryption(
108
+ em,
109
+ CatalogProductPrice,
110
+ {
111
+ variant,
112
+ tenantId: ctx.tenantId,
113
+ organizationId: ctx.organizationId,
114
+ },
115
+ { orderBy: { createdAt: 'ASC' }, limit: 10 },
116
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
117
+ )
118
+
119
+ const firstPrice = prices.find((entry) => Boolean(entry.unitPriceGross || entry.unitPriceNet))
120
+ const priceLabel = formatVariantPrice(
121
+ firstPrice?.unitPriceGross ?? firstPrice?.unitPriceNet ?? null,
122
+ firstPrice?.currencyCode ?? null,
123
+ )
124
+
125
+ const metadata: Record<string, string> = {}
126
+ const skuLabel = t('catalog.variants.form.skuLabel')
127
+ const pricesLabel = t('catalog.variants.form.pricesLabel')
128
+ if (variant.sku && variant.sku.trim().length > 0) metadata[skuLabel] = variant.sku
129
+ if (priceLabel) metadata[pricesLabel] = priceLabel
130
+
131
+ return {
132
+ title: variant.name ?? '',
133
+ subtitle: variant.sku ?? undefined,
134
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
135
+ }
136
+ }
137
+
138
+ export async function loadCatalogCategoryPreview(
139
+ entityId: string,
140
+ ctx: PreviewContext,
141
+ ): Promise<ObjectPreviewData> {
142
+ const { t } = await resolveTranslations()
143
+ const defaultTitle = t('catalog.messageObjects.category.title')
144
+
145
+ if (!ctx.organizationId) {
146
+ return { title: defaultTitle, subtitle: entityId }
147
+ }
148
+
149
+ const em = await resolveEm()
150
+ const entity = await findOneWithDecryption(
151
+ em,
152
+ CatalogProductCategory,
153
+ {
154
+ id: entityId,
155
+ tenantId: ctx.tenantId,
156
+ organizationId: ctx.organizationId,
157
+ deletedAt: null,
158
+ },
159
+ undefined,
160
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
161
+ )
162
+
163
+ if (!entity) {
164
+ return {
165
+ title: defaultTitle,
166
+ subtitle: entityId,
167
+ status: t('customers.messageObjects.notFound'),
168
+ statusColor: 'gray',
169
+ }
170
+ }
171
+
172
+ return {
173
+ title: entity.name,
174
+ subtitle: entity.description ?? undefined,
175
+ }
176
+ }
@@ -0,0 +1,102 @@
1
+ import type { MessageObjectTypeDefinition } from '@open-mercato/shared/modules/messages/types'
2
+ import { MessageObjectDetail, MessageObjectPreview } from '@open-mercato/ui/backend/messages'
3
+
4
+ const objectMessageTypes = ['default', 'messages.defaultWithObjects']
5
+
6
+ export const messageObjectTypes: MessageObjectTypeDefinition[] = [
7
+ {
8
+ module: 'catalog',
9
+ entityType: 'product',
10
+ messageTypes: objectMessageTypes,
11
+ entityId: 'catalog:catalog_product',
12
+ optionLabelField: 'title',
13
+ optionSubtitleField: 'subtitle',
14
+ labelKey: 'catalog.messageObjects.product.title',
15
+ icon: 'package',
16
+ PreviewComponent: MessageObjectPreview,
17
+ DetailComponent: MessageObjectDetail,
18
+ actions: [
19
+ {
20
+ id: 'view',
21
+ labelKey: 'common.view',
22
+ variant: 'outline',
23
+ href: '/backend/catalog/products/{entityId}',
24
+ },
25
+ ],
26
+ loadPreview: async (entityId, ctx) => {
27
+ if (typeof window !== 'undefined') {
28
+ return { title: 'Product', subtitle: entityId }
29
+ }
30
+ const previews = await import('./lib/messageObjectPreviews')
31
+ const productLoader = (
32
+ previews as typeof previews & {
33
+ loadCatalogProductPreview?: (id: string, previewCtx: typeof ctx) => Promise<{ title: string; subtitle?: string }>
34
+ }
35
+ ).loadCatalogProductPreview
36
+ if (productLoader) return productLoader(entityId, ctx)
37
+ return previews.loadCatalogCategoryPreview(entityId, ctx)
38
+ },
39
+ },
40
+ {
41
+ module: 'catalog',
42
+ entityType: 'variant',
43
+ messageTypes: objectMessageTypes,
44
+ entityId: 'catalog:catalog_product_variant',
45
+ optionLabelField: 'name',
46
+ optionSubtitleField: 'sku',
47
+ labelKey: 'catalog.variants.form.editTitle',
48
+ icon: 'package-plus',
49
+ PreviewComponent: MessageObjectPreview,
50
+ DetailComponent: MessageObjectDetail,
51
+ actions: [
52
+ {
53
+ id: 'view',
54
+ labelKey: 'common.view',
55
+ variant: 'outline',
56
+ href: '/backend/catalog/products',
57
+ },
58
+ ],
59
+ loadPreview: async (entityId, ctx) => {
60
+ if (typeof window !== 'undefined') {
61
+ return { title: 'Variant', subtitle: entityId }
62
+ }
63
+ const previews = await import('./lib/messageObjectPreviews')
64
+ const variantLoader = (
65
+ previews as typeof previews & {
66
+ loadCatalogVariantPreview?: (id: string, previewCtx: typeof ctx) => Promise<{ title: string; subtitle?: string }>
67
+ }
68
+ ).loadCatalogVariantPreview
69
+ if (variantLoader) return variantLoader(entityId, ctx)
70
+ return previews.loadCatalogProductPreview(entityId, ctx)
71
+ },
72
+ },
73
+ {
74
+ module: 'catalog',
75
+ entityType: 'category',
76
+ messageTypes: objectMessageTypes,
77
+ entityId: 'catalog:catalog_product_category',
78
+ optionLabelField: 'name',
79
+ optionSubtitleField: 'description',
80
+ labelKey: 'catalog.messageObjects.category.title',
81
+ icon: 'tag',
82
+ PreviewComponent: MessageObjectPreview,
83
+ DetailComponent: MessageObjectDetail,
84
+ actions: [
85
+ {
86
+ id: 'view',
87
+ labelKey: 'common.view',
88
+ variant: 'outline',
89
+ href: '/backend/catalog/categories/{entityId}/edit',
90
+ },
91
+ ],
92
+ loadPreview: async (entityId, ctx) => {
93
+ if (typeof window !== 'undefined') {
94
+ return { title: 'Category', subtitle: entityId }
95
+ }
96
+ const { loadCatalogCategoryPreview } = await import('./lib/messageObjectPreviews')
97
+ return loadCatalogCategoryPreview(entityId, ctx)
98
+ },
99
+ },
100
+ ]
101
+
102
+ export default messageObjectTypes
@@ -9,6 +9,7 @@ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors
9
9
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
10
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
11
11
  import { useT } from '@open-mercato/shared/lib/i18n/context'
12
+ import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
12
13
  import { DataLoader } from '@open-mercato/ui/primitives/DataLoader'
13
14
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
14
15
 
@@ -180,6 +181,25 @@ export default function EditCurrencyPage({ params }: { params?: { id?: string }
180
181
  title={t('currencies.edit.title')}
181
182
  backHref="/backend/currencies"
182
183
  versionHistory={{ resourceKind: 'currencies.currency', resourceId: currency.id }}
184
+ extraActions={(
185
+ <SendObjectMessageDialog
186
+ object={{
187
+ entityModule: 'currencies',
188
+ entityType: 'currency',
189
+ entityId: currency.id,
190
+ previewData: {
191
+ title: currency.name,
192
+ subtitle: currency.code,
193
+ metadata: {
194
+ [t('currencies.form.field.code')]: currency.code,
195
+ [t('currencies.form.field.name')]: currency.name,
196
+ [t('currencies.form.field.symbol')]: currency.symbol || '-',
197
+ },
198
+ },
199
+ }}
200
+ viewHref={`/backend/currencies/${currency.id}`}
201
+ />
202
+ )}
183
203
  fields={[]}
184
204
  groups={groups}
185
205
  initialValues={{