@open-mercato/core 0.10.1-develop.7761.1.9dfaf470ed → 0.10.1-develop.7762.1.657bb7236b

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 (80) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +1 -0
  3. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  4. package/dist/modules/customers/api/companies/[id]/route.js +3 -2
  5. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  6. package/dist/modules/customers/api/deals/[id]/route.js +11 -12
  7. package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
  8. package/dist/modules/customers/api/people/[id]/route.js +3 -2
  9. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  10. package/dist/modules/customers/backend/customers/companies/create/page.js +6 -0
  11. package/dist/modules/customers/backend/customers/companies/create/page.js.map +2 -2
  12. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +25 -25
  13. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  14. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.js +8 -27
  15. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.js.map +2 -2
  16. package/dist/modules/customers/backend/customers/deals/[id]/page.js +175 -171
  17. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  18. package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js +25 -24
  19. package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map +2 -2
  20. package/dist/modules/customers/backend/customers/people/create/page.js +9 -0
  21. package/dist/modules/customers/backend/customers/people/create/page.js.map +2 -2
  22. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +140 -144
  23. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  24. package/dist/modules/customers/components/detail/CompanyDetailHeader.js +2 -0
  25. package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
  26. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +13 -4
  27. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  28. package/dist/modules/customers/components/detail/CreatePersonDialog.js +13 -3
  29. package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
  30. package/dist/modules/customers/components/detail/CustomerDetailSidebar.js +29 -0
  31. package/dist/modules/customers/components/detail/CustomerDetailSidebar.js.map +7 -0
  32. package/dist/modules/customers/components/detail/DealDetailHeader.js +4 -2
  33. package/dist/modules/customers/components/detail/DealDetailHeader.js.map +2 -2
  34. package/dist/modules/customers/components/detail/DealDetailTabs.js +9 -1
  35. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  36. package/dist/modules/customers/components/detail/DealForm.js +5 -0
  37. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  38. package/dist/modules/customers/components/detail/PersonDetailHeader.js +2 -0
  39. package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
  40. package/dist/modules/customers/components/detail/PersonDetailTabs.js +9 -1
  41. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  42. package/dist/modules/customers/components/detail/create/CreateDealForm.js +160 -176
  43. package/dist/modules/customers/components/detail/create/CreateDealForm.js.map +2 -2
  44. package/dist/modules/customers/components/detail/useCustomerInjectedTabs.js +47 -0
  45. package/dist/modules/customers/components/detail/useCustomerInjectedTabs.js.map +7 -0
  46. package/dist/modules/customers/components/formConfig.js +3 -0
  47. package/dist/modules/customers/components/formConfig.js.map +2 -2
  48. package/dist/modules/customers/extension-points.js +14 -4
  49. package/dist/modules/customers/extension-points.js.map +2 -2
  50. package/dist/modules/customers/lib/detailResponseEnrichment.js +38 -0
  51. package/dist/modules/customers/lib/detailResponseEnrichment.js.map +7 -0
  52. package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js +4 -4
  53. package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js.map +2 -2
  54. package/package.json +7 -7
  55. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -0
  56. package/src/modules/customers/api/companies/[id]/route.ts +3 -2
  57. package/src/modules/customers/api/deals/[id]/route.ts +12 -12
  58. package/src/modules/customers/api/people/[id]/route.ts +3 -2
  59. package/src/modules/customers/backend/customers/companies/create/page.tsx +6 -0
  60. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +27 -30
  61. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.tsx +9 -44
  62. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -3
  63. package/src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx +25 -27
  64. package/src/modules/customers/backend/customers/people/create/page.tsx +9 -0
  65. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +21 -33
  66. package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +3 -0
  67. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +15 -4
  68. package/src/modules/customers/components/detail/CreatePersonDialog.tsx +15 -4
  69. package/src/modules/customers/components/detail/CustomerDetailSidebar.tsx +41 -0
  70. package/src/modules/customers/components/detail/DealDetailHeader.tsx +4 -1
  71. package/src/modules/customers/components/detail/DealDetailTabs.tsx +11 -1
  72. package/src/modules/customers/components/detail/DealForm.tsx +5 -0
  73. package/src/modules/customers/components/detail/PersonDetailHeader.tsx +3 -0
  74. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +11 -1
  75. package/src/modules/customers/components/detail/create/CreateDealForm.tsx +126 -209
  76. package/src/modules/customers/components/detail/useCustomerInjectedTabs.tsx +64 -0
  77. package/src/modules/customers/components/formConfig.tsx +3 -0
  78. package/src/modules/customers/extension-points.ts +14 -4
  79. package/src/modules/customers/lib/detailResponseEnrichment.ts +53 -0
  80. package/src/modules/integrations/widgets/injection/external-ids/widget.client.tsx +4 -4
@@ -5,11 +5,12 @@ import {
5
5
  defineModuleExtensionPoints,
6
6
  injectionExtensionHost
7
7
  } from "@open-mercato/shared/modules/widgets/extension-points";
8
- const detailHost = (spotId, source) => injectionExtensionHost({
8
+ const detailHost = (spotId, source, dataContract) => injectionExtensionHost({
9
9
  family: "detail",
10
10
  spotId,
11
11
  supported: ["render-widget"],
12
12
  contextContract: "customers.detail.v1",
13
+ dataContract,
13
14
  source
14
15
  });
15
16
  const extensionPoints = defineModuleExtensionPoints({
@@ -19,9 +20,9 @@ const extensionPoints = defineModuleExtensionPoints({
19
20
  dealsTable: dataTableExtensionHost({ tableId: "customers.deals.list", source: "backend/customers/deals/page.tsx" }),
20
21
  peopleTable: dataTableExtensionHost({ tableId: "customers.people.list", source: "backend/customers/people/page.tsx" }),
21
22
  todosTable: dataTableExtensionHost({ tableId: "customers.todos.list", source: "components/CustomerTodosTable.tsx" }),
22
- companyForm: crudFormExtensionHost({ entityId: "customers.company", spotId: "crud-form:customers.company", source: "backend/customers/companies-v2/[id]/page.tsx" }),
23
- dealForm: crudFormExtensionHost({ entityId: "customers.deal", spotId: "crud-form:customers.deal", source: "backend/customers/deals/[id]/page.tsx" }),
24
- personForm: crudFormExtensionHost({ entityId: "customers.person", spotId: "crud-form:customers.person", source: "backend/customers/people-v2/[id]/page.tsx" }),
23
+ companyForm: crudFormExtensionHost({ entityId: "customers.company", spotId: "crud-form:customers.company", source: ["backend/customers/companies-v2/[id]/page.tsx", "backend/customers/companies/create/page.tsx"] }),
24
+ dealForm: crudFormExtensionHost({ entityId: "customers.deal", spotId: "crud-form:customers.deal", source: ["backend/customers/deals/[id]/page.tsx", "components/detail/create/CreateDealForm.tsx", "backend/customers/deals/pipeline/components/QuickDealDialog.tsx"] }),
25
+ personForm: crudFormExtensionHost({ entityId: "customers.person", spotId: "crud-form:customers.person", source: ["backend/customers/people-v2/[id]/page.tsx", "backend/customers/people/create/page.tsx", "components/detail/CreatePersonDialog.tsx"] }),
25
26
  calendarEventTypes: injectionExtensionHost({
26
27
  family: "generic",
27
28
  spotId: "calendar:customers.event-types",
@@ -34,13 +35,22 @@ const extensionPoints = defineModuleExtensionPoints({
34
35
  propsContract: "customers.calendar.event-type-panel.v1",
35
36
  source: "components/calendar/editor/EventTypePanel.tsx"
36
37
  }),
38
+ companyHeaderActions: detailHost("detail:customers.company:header-actions", "backend/customers/companies-v2/[id]/page.tsx", "customers.detail.overview.v1"),
39
+ companySidebar: detailHost("detail:customers.company:sidebar", "backend/customers/companies-v2/[id]/page.tsx", "customers.detail.overview.v1"),
40
+ companyTabs: detailHost("detail:customers.company:tabs", "backend/customers/companies-v2/[id]/page.tsx", "customers.detail.overview.v1"),
37
41
  companyHeader: detailHost("detail:customers.company:header", "backend/customers/companies-v2/[id]/page.tsx"),
38
42
  companyStatusBadges: detailHost("detail:customers.company:status-badges", "backend/customers/companies-v2/[id]/page.tsx"),
39
43
  companyFooter: detailHost("detail:customers.company:footer", "backend/customers/companies-v2/[id]/page.tsx"),
40
44
  companyLegacyDetails: detailHost("customers.company.detail:details", "backend/customers/companies/[id]/page.tsx"),
45
+ dealHeaderActions: detailHost("detail:customers.deal:header-actions", "backend/customers/deals/[id]/page.tsx", "customers.detail.overview.v1"),
46
+ dealSidebar: detailHost("detail:customers.deal:sidebar", "backend/customers/deals/[id]/page.tsx", "customers.detail.overview.v1"),
47
+ dealTabs: detailHost("detail:customers.deal:tabs", "backend/customers/deals/[id]/hooks/useDealInjectedTabs.tsx", "customers.detail.overview.v1"),
41
48
  dealHeader: detailHost("detail:customers.deal:header", "backend/customers/deals/[id]/page.tsx"),
42
49
  dealStatusBadges: detailHost("detail:customers.deal:status-badges", "backend/customers/deals/[id]/page.tsx"),
43
50
  dealFooter: detailHost("detail:customers.deal:footer", "backend/customers/deals/[id]/page.tsx"),
51
+ personHeaderActions: detailHost("detail:customers.person:header-actions", "backend/customers/people-v2/[id]/page.tsx", "customers.detail.overview.v1"),
52
+ personSidebar: detailHost("detail:customers.person:sidebar", "backend/customers/people-v2/[id]/page.tsx", "customers.detail.overview.v1"),
53
+ personTabs: detailHost("detail:customers.person:tabs", "backend/customers/people-v2/[id]/page.tsx", "customers.detail.overview.v1"),
44
54
  personHeader: detailHost("detail:customers.person:header", "backend/customers/people-v2/[id]/page.tsx"),
45
55
  personStatusBadges: detailHost("detail:customers.person:status-badges", "backend/customers/people-v2/[id]/page.tsx"),
46
56
  personFooter: detailHost("detail:customers.person:footer", "backend/customers/people-v2/[id]/page.tsx"),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/customers/extension-points.ts"],
4
- "sourcesContent": ["import {\n componentExtensionHost,\n crudFormExtensionHost,\n dataTableExtensionHost,\n defineModuleExtensionPoints,\n injectionExtensionHost,\n} from '@open-mercato/shared/modules/widgets/extension-points'\n\nconst detailHost = (spotId: string, source: string) => injectionExtensionHost({\n family: 'detail',\n spotId,\n supported: ['render-widget'],\n contextContract: 'customers.detail.v1',\n source,\n})\n\nexport const extensionPoints = defineModuleExtensionPoints({\n moduleId: 'customers',\n hosts: {\n companiesTable: dataTableExtensionHost({ tableId: 'customers.companies.list', source: 'backend/customers/companies/page.tsx' }),\n dealsTable: dataTableExtensionHost({ tableId: 'customers.deals.list', source: 'backend/customers/deals/page.tsx' }),\n peopleTable: dataTableExtensionHost({ tableId: 'customers.people.list', source: 'backend/customers/people/page.tsx' }),\n todosTable: dataTableExtensionHost({ tableId: 'customers.todos.list', source: 'components/CustomerTodosTable.tsx' }),\n companyForm: crudFormExtensionHost({ entityId: 'customers.company', spotId: 'crud-form:customers.company', source: 'backend/customers/companies-v2/[id]/page.tsx' }),\n dealForm: crudFormExtensionHost({ entityId: 'customers.deal', spotId: 'crud-form:customers.deal', source: 'backend/customers/deals/[id]/page.tsx' }),\n personForm: crudFormExtensionHost({ entityId: 'customers.person', spotId: 'crud-form:customers.person', source: 'backend/customers/people-v2/[id]/page.tsx' }),\n calendarEventTypes: injectionExtensionHost({\n family: 'generic',\n spotId: 'calendar:customers.event-types',\n supported: ['headless-widget', 'registry-contribution'],\n dataContract: 'customers.calendar.event-types.v1',\n source: 'lib/calendar/eventTypeResolver.ts',\n }),\n calendarEventTypePanel: componentExtensionHost({\n componentId: 'section:customers.calendar-event-editor.type-panel',\n propsContract: 'customers.calendar.event-type-panel.v1',\n source: 'components/calendar/editor/EventTypePanel.tsx',\n }),\n companyHeader: detailHost('detail:customers.company:header', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyStatusBadges: detailHost('detail:customers.company:status-badges', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyFooter: detailHost('detail:customers.company:footer', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyLegacyDetails: detailHost('customers.company.detail:details', 'backend/customers/companies/[id]/page.tsx'),\n dealHeader: detailHost('detail:customers.deal:header', 'backend/customers/deals/[id]/page.tsx'),\n dealStatusBadges: detailHost('detail:customers.deal:status-badges', 'backend/customers/deals/[id]/page.tsx'),\n dealFooter: detailHost('detail:customers.deal:footer', 'backend/customers/deals/[id]/page.tsx'),\n personHeader: detailHost('detail:customers.person:header', 'backend/customers/people-v2/[id]/page.tsx'),\n personStatusBadges: detailHost('detail:customers.person:status-badges', 'backend/customers/people-v2/[id]/page.tsx'),\n personFooter: detailHost('detail:customers.person:footer', 'backend/customers/people-v2/[id]/page.tsx'),\n personLegacyDetails: detailHost('customers.person.detail:details', 'backend/customers/people/[id]/page.tsx'),\n },\n})\n\nexport default extensionPoints\n"],
5
- "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,aAAa,CAAC,QAAgB,WAAmB,uBAAuB;AAAA,EAC5E,QAAQ;AAAA,EACR;AAAA,EACA,WAAW,CAAC,eAAe;AAAA,EAC3B,iBAAiB;AAAA,EACjB;AACF,CAAC;AAEM,MAAM,kBAAkB,4BAA4B;AAAA,EACzD,UAAU;AAAA,EACV,OAAO;AAAA,IACL,gBAAgB,uBAAuB,EAAE,SAAS,4BAA4B,QAAQ,uCAAuC,CAAC;AAAA,IAC9H,YAAY,uBAAuB,EAAE,SAAS,wBAAwB,QAAQ,mCAAmC,CAAC;AAAA,IAClH,aAAa,uBAAuB,EAAE,SAAS,yBAAyB,QAAQ,oCAAoC,CAAC;AAAA,IACrH,YAAY,uBAAuB,EAAE,SAAS,wBAAwB,QAAQ,oCAAoC,CAAC;AAAA,IACnH,aAAa,sBAAsB,EAAE,UAAU,qBAAqB,QAAQ,+BAA+B,QAAQ,+CAA+C,CAAC;AAAA,IACnK,UAAU,sBAAsB,EAAE,UAAU,kBAAkB,QAAQ,4BAA4B,QAAQ,wCAAwC,CAAC;AAAA,IACnJ,YAAY,sBAAsB,EAAE,UAAU,oBAAoB,QAAQ,8BAA8B,QAAQ,4CAA4C,CAAC;AAAA,IAC7J,oBAAoB,uBAAuB;AAAA,MACzC,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,WAAW,CAAC,mBAAmB,uBAAuB;AAAA,MACtD,cAAc;AAAA,MACd,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,wBAAwB,uBAAuB;AAAA,MAC7C,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,eAAe,WAAW,mCAAmC,8CAA8C;AAAA,IAC3G,qBAAqB,WAAW,0CAA0C,8CAA8C;AAAA,IACxH,eAAe,WAAW,mCAAmC,8CAA8C;AAAA,IAC3G,sBAAsB,WAAW,oCAAoC,2CAA2C;AAAA,IAChH,YAAY,WAAW,gCAAgC,uCAAuC;AAAA,IAC9F,kBAAkB,WAAW,uCAAuC,uCAAuC;AAAA,IAC3G,YAAY,WAAW,gCAAgC,uCAAuC;AAAA,IAC9F,cAAc,WAAW,kCAAkC,2CAA2C;AAAA,IACtG,oBAAoB,WAAW,yCAAyC,2CAA2C;AAAA,IACnH,cAAc,WAAW,kCAAkC,2CAA2C;AAAA,IACtG,qBAAqB,WAAW,mCAAmC,wCAAwC;AAAA,EAC7G;AACF,CAAC;AAED,IAAO,2BAAQ;",
4
+ "sourcesContent": ["import {\n componentExtensionHost,\n crudFormExtensionHost,\n dataTableExtensionHost,\n defineModuleExtensionPoints,\n injectionExtensionHost,\n} from '@open-mercato/shared/modules/widgets/extension-points'\n\nconst detailHost = (spotId: string, source: string, dataContract?: string) => injectionExtensionHost({\n family: 'detail',\n spotId,\n supported: ['render-widget'],\n contextContract: 'customers.detail.v1',\n dataContract,\n source,\n})\n\nexport const extensionPoints = defineModuleExtensionPoints({\n moduleId: 'customers',\n hosts: {\n companiesTable: dataTableExtensionHost({ tableId: 'customers.companies.list', source: 'backend/customers/companies/page.tsx' }),\n dealsTable: dataTableExtensionHost({ tableId: 'customers.deals.list', source: 'backend/customers/deals/page.tsx' }),\n peopleTable: dataTableExtensionHost({ tableId: 'customers.people.list', source: 'backend/customers/people/page.tsx' }),\n todosTable: dataTableExtensionHost({ tableId: 'customers.todos.list', source: 'components/CustomerTodosTable.tsx' }),\n companyForm: crudFormExtensionHost({ entityId: 'customers.company', spotId: 'crud-form:customers.company', source: ['backend/customers/companies-v2/[id]/page.tsx', 'backend/customers/companies/create/page.tsx'] }),\n dealForm: crudFormExtensionHost({ entityId: 'customers.deal', spotId: 'crud-form:customers.deal', source: ['backend/customers/deals/[id]/page.tsx', 'components/detail/create/CreateDealForm.tsx', 'backend/customers/deals/pipeline/components/QuickDealDialog.tsx'] }),\n personForm: crudFormExtensionHost({ entityId: 'customers.person', spotId: 'crud-form:customers.person', source: ['backend/customers/people-v2/[id]/page.tsx', 'backend/customers/people/create/page.tsx', 'components/detail/CreatePersonDialog.tsx'] }),\n calendarEventTypes: injectionExtensionHost({\n family: 'generic',\n spotId: 'calendar:customers.event-types',\n supported: ['headless-widget', 'registry-contribution'],\n dataContract: 'customers.calendar.event-types.v1',\n source: 'lib/calendar/eventTypeResolver.ts',\n }),\n calendarEventTypePanel: componentExtensionHost({\n componentId: 'section:customers.calendar-event-editor.type-panel',\n propsContract: 'customers.calendar.event-type-panel.v1',\n source: 'components/calendar/editor/EventTypePanel.tsx',\n }),\n companyHeaderActions: detailHost('detail:customers.company:header-actions', 'backend/customers/companies-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n companySidebar: detailHost('detail:customers.company:sidebar', 'backend/customers/companies-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n companyTabs: detailHost('detail:customers.company:tabs', 'backend/customers/companies-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n companyHeader: detailHost('detail:customers.company:header', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyStatusBadges: detailHost('detail:customers.company:status-badges', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyFooter: detailHost('detail:customers.company:footer', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyLegacyDetails: detailHost('customers.company.detail:details', 'backend/customers/companies/[id]/page.tsx'),\n dealHeaderActions: detailHost('detail:customers.deal:header-actions', 'backend/customers/deals/[id]/page.tsx', 'customers.detail.overview.v1'),\n dealSidebar: detailHost('detail:customers.deal:sidebar', 'backend/customers/deals/[id]/page.tsx', 'customers.detail.overview.v1'),\n dealTabs: detailHost('detail:customers.deal:tabs', 'backend/customers/deals/[id]/hooks/useDealInjectedTabs.tsx', 'customers.detail.overview.v1'),\n dealHeader: detailHost('detail:customers.deal:header', 'backend/customers/deals/[id]/page.tsx'),\n dealStatusBadges: detailHost('detail:customers.deal:status-badges', 'backend/customers/deals/[id]/page.tsx'),\n dealFooter: detailHost('detail:customers.deal:footer', 'backend/customers/deals/[id]/page.tsx'),\n personHeaderActions: detailHost('detail:customers.person:header-actions', 'backend/customers/people-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n personSidebar: detailHost('detail:customers.person:sidebar', 'backend/customers/people-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n personTabs: detailHost('detail:customers.person:tabs', 'backend/customers/people-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n personHeader: detailHost('detail:customers.person:header', 'backend/customers/people-v2/[id]/page.tsx'),\n personStatusBadges: detailHost('detail:customers.person:status-badges', 'backend/customers/people-v2/[id]/page.tsx'),\n personFooter: detailHost('detail:customers.person:footer', 'backend/customers/people-v2/[id]/page.tsx'),\n personLegacyDetails: detailHost('customers.person.detail:details', 'backend/customers/people/[id]/page.tsx'),\n },\n})\n\nexport default extensionPoints\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,aAAa,CAAC,QAAgB,QAAgB,iBAA0B,uBAAuB;AAAA,EACnG,QAAQ;AAAA,EACR;AAAA,EACA,WAAW,CAAC,eAAe;AAAA,EAC3B,iBAAiB;AAAA,EACjB;AAAA,EACA;AACF,CAAC;AAEM,MAAM,kBAAkB,4BAA4B;AAAA,EACzD,UAAU;AAAA,EACV,OAAO;AAAA,IACL,gBAAgB,uBAAuB,EAAE,SAAS,4BAA4B,QAAQ,uCAAuC,CAAC;AAAA,IAC9H,YAAY,uBAAuB,EAAE,SAAS,wBAAwB,QAAQ,mCAAmC,CAAC;AAAA,IAClH,aAAa,uBAAuB,EAAE,SAAS,yBAAyB,QAAQ,oCAAoC,CAAC;AAAA,IACrH,YAAY,uBAAuB,EAAE,SAAS,wBAAwB,QAAQ,oCAAoC,CAAC;AAAA,IACnH,aAAa,sBAAsB,EAAE,UAAU,qBAAqB,QAAQ,+BAA+B,QAAQ,CAAC,gDAAgD,6CAA6C,EAAE,CAAC;AAAA,IACpN,UAAU,sBAAsB,EAAE,UAAU,kBAAkB,QAAQ,4BAA4B,QAAQ,CAAC,yCAAyC,+CAA+C,iEAAiE,EAAE,CAAC;AAAA,IACvQ,YAAY,sBAAsB,EAAE,UAAU,oBAAoB,QAAQ,8BAA8B,QAAQ,CAAC,6CAA6C,4CAA4C,0CAA0C,EAAE,CAAC;AAAA,IACvP,oBAAoB,uBAAuB;AAAA,MACzC,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,WAAW,CAAC,mBAAmB,uBAAuB;AAAA,MACtD,cAAc;AAAA,MACd,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,wBAAwB,uBAAuB;AAAA,MAC7C,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,sBAAsB,WAAW,2CAA2C,gDAAgD,8BAA8B;AAAA,IAC1J,gBAAgB,WAAW,oCAAoC,gDAAgD,8BAA8B;AAAA,IAC7I,aAAa,WAAW,iCAAiC,gDAAgD,8BAA8B;AAAA,IACvI,eAAe,WAAW,mCAAmC,8CAA8C;AAAA,IAC3G,qBAAqB,WAAW,0CAA0C,8CAA8C;AAAA,IACxH,eAAe,WAAW,mCAAmC,8CAA8C;AAAA,IAC3G,sBAAsB,WAAW,oCAAoC,2CAA2C;AAAA,IAChH,mBAAmB,WAAW,wCAAwC,yCAAyC,8BAA8B;AAAA,IAC7I,aAAa,WAAW,iCAAiC,yCAAyC,8BAA8B;AAAA,IAChI,UAAU,WAAW,8BAA8B,8DAA8D,8BAA8B;AAAA,IAC/I,YAAY,WAAW,gCAAgC,uCAAuC;AAAA,IAC9F,kBAAkB,WAAW,uCAAuC,uCAAuC;AAAA,IAC3G,YAAY,WAAW,gCAAgC,uCAAuC;AAAA,IAC9F,qBAAqB,WAAW,0CAA0C,6CAA6C,8BAA8B;AAAA,IACrJ,eAAe,WAAW,mCAAmC,6CAA6C,8BAA8B;AAAA,IACxI,YAAY,WAAW,gCAAgC,6CAA6C,8BAA8B;AAAA,IAClI,cAAc,WAAW,kCAAkC,2CAA2C;AAAA,IACtG,oBAAoB,WAAW,yCAAyC,2CAA2C;AAAA,IACnH,cAAc,WAAW,kCAAkC,2CAA2C;AAAA,IACtG,qBAAqB,WAAW,mCAAmC,wCAAwC;AAAA,EAC7G;AACF,CAAC;AAED,IAAO,2BAAQ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,38 @@
1
+ import { getEnrichersForEntity } from "@open-mercato/shared/lib/crud/enricher-registry";
2
+ import { applyResponseEnricherToRecord } from "@open-mercato/shared/lib/crud/enricher-runner";
3
+ import { extractResponseEnricherNamespaces } from "@open-mercato/shared/lib/crud/response-enricher-namespaces";
4
+ async function enrichCustomerDetailResponse(payload, recordKey, context) {
5
+ if (getEnrichersForEntity(`customers.${recordKey}`).length === 0) return payload;
6
+ const clonedPayload = structuredClone(payload);
7
+ const mainRecord = clonedPayload[recordKey];
8
+ if (!context.tenantId || !context.organizationId || !mainRecord || typeof mainRecord !== "object" || Array.isArray(mainRecord)) {
9
+ return clonedPayload;
10
+ }
11
+ let userFeatures;
12
+ try {
13
+ const rbac = context.container.resolve("rbacService");
14
+ if (rbac?.getGrantedFeatures) {
15
+ userFeatures = await rbac.getGrantedFeatures(context.auth.sub, {
16
+ tenantId: context.tenantId,
17
+ organizationId: context.organizationId
18
+ });
19
+ }
20
+ } catch {
21
+ userFeatures = void 0;
22
+ }
23
+ const record = mainRecord;
24
+ const result = await applyResponseEnricherToRecord(structuredClone(record), `customers.${recordKey}`, {
25
+ tenantId: context.tenantId,
26
+ organizationId: context.organizationId,
27
+ userId: context.auth.sub,
28
+ em: context.em,
29
+ container: context.container,
30
+ userFeatures
31
+ });
32
+ const namespaces = extractResponseEnricherNamespaces(result.record);
33
+ return { ...clonedPayload, ...namespaces, [recordKey]: { ...record, ...namespaces } };
34
+ }
35
+ export {
36
+ enrichCustomerDetailResponse
37
+ };
38
+ //# sourceMappingURL=detailResponseEnrichment.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/lib/detailResponseEnrichment.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { getEnrichersForEntity } from '@open-mercato/shared/lib/crud/enricher-registry'\nimport { applyResponseEnricherToRecord } from '@open-mercato/shared/lib/crud/enricher-runner'\nimport { extractResponseEnricherNamespaces } from '@open-mercato/shared/lib/crud/response-enricher-namespaces'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\ntype DetailRecordKey = 'person' | 'company' | 'deal'\n\ntype DetailEnrichmentContext = {\n auth: NonNullable<AuthContext>\n container: AppContainer\n em: EntityManager\n tenantId: string | null\n organizationId: string | null\n}\n\nexport async function enrichCustomerDetailResponse<T extends Record<string, unknown>>(\n payload: T,\n recordKey: DetailRecordKey,\n context: DetailEnrichmentContext,\n): Promise<T> {\n if (getEnrichersForEntity(`customers.${recordKey}`).length === 0) return payload\n const clonedPayload = structuredClone(payload)\n const mainRecord = clonedPayload[recordKey]\n if (!context.tenantId || !context.organizationId || !mainRecord || typeof mainRecord !== 'object' || Array.isArray(mainRecord)) {\n return clonedPayload\n }\n let userFeatures: string[] | undefined\n try {\n const rbac = context.container.resolve<RbacService>('rbacService')\n if (rbac?.getGrantedFeatures) {\n userFeatures = await rbac.getGrantedFeatures(context.auth.sub, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n } catch {\n userFeatures = undefined\n }\n const record = mainRecord as Record<string, unknown>\n const result = await applyResponseEnricherToRecord(structuredClone(record), `customers.${recordKey}`, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: context.auth.sub,\n em: context.em,\n container: context.container,\n userFeatures,\n })\n const namespaces = extractResponseEnricherNamespaces(result.record)\n return { ...clonedPayload, ...namespaces, [recordKey]: { ...record, ...namespaces } }\n}\n"],
5
+ "mappings": "AAGA,SAAS,6BAA6B;AACtC,SAAS,qCAAqC;AAC9C,SAAS,yCAAyC;AAalD,eAAsB,6BACpB,SACA,WACA,SACY;AACZ,MAAI,sBAAsB,aAAa,SAAS,EAAE,EAAE,WAAW,EAAG,QAAO;AACzE,QAAM,gBAAgB,gBAAgB,OAAO;AAC7C,QAAM,aAAa,cAAc,SAAS;AAC1C,MAAI,CAAC,QAAQ,YAAY,CAAC,QAAQ,kBAAkB,CAAC,cAAc,OAAO,eAAe,YAAY,MAAM,QAAQ,UAAU,GAAG;AAC9H,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,QAAQ,UAAU,QAAqB,aAAa;AACjE,QAAI,MAAM,oBAAoB;AAC5B,qBAAe,MAAM,KAAK,mBAAmB,QAAQ,KAAK,KAAK;AAAA,QAC7D,UAAU,QAAQ;AAAA,QAClB,gBAAgB,QAAQ;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AACN,mBAAe;AAAA,EACjB;AACA,QAAM,SAAS;AACf,QAAM,SAAS,MAAM,8BAA8B,gBAAgB,MAAM,GAAG,aAAa,SAAS,IAAI;AAAA,IACpG,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,IACxB,QAAQ,QAAQ,KAAK;AAAA,IACrB,IAAI,QAAQ;AAAA,IACZ,WAAW,QAAQ;AAAA,IACnB;AAAA,EACF,CAAC;AACD,QAAM,aAAa,kCAAkC,OAAO,MAAM;AAClE,SAAO,EAAE,GAAG,eAAe,GAAG,YAAY,CAAC,SAAS,GAAG,EAAE,GAAG,QAAQ,GAAG,WAAW,EAAE;AACtF;",
6
+ "names": []
7
+ }
@@ -13,7 +13,7 @@ function SyncStatusBadge({ status, lastSynced }) {
13
13
  const t = useT();
14
14
  const config = SYNC_STATUS_STYLES[status];
15
15
  const label = t(config.label, status);
16
- return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
16
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
17
17
  /* @__PURE__ */ jsx("span", { className: cn("inline-block size-1.5 rounded-full", config.dot), "aria-hidden": "true" }),
18
18
  /* @__PURE__ */ jsx("span", { children: label }),
19
19
  lastSynced && /* @__PURE__ */ jsx("span", { title: lastSynced, children: new Date(lastSynced).toLocaleDateString() })
@@ -36,16 +36,16 @@ function ExternalIdsWidget({ data }) {
36
36
  {
37
37
  className: "flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2",
38
38
  children: [
39
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
39
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: [
40
40
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: getIntegrationTitle(integrationId) }),
41
- /* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground", children: mapping.externalId }),
41
+ /* @__PURE__ */ jsx("code", { className: "min-w-0 max-w-full break-all rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground", children: mapping.externalId }),
42
42
  mapping.externalUrl && /* @__PURE__ */ jsx(
43
43
  "a",
44
44
  {
45
45
  href: mapping.externalUrl,
46
46
  target: "_blank",
47
47
  rel: "noopener noreferrer",
48
- className: "text-muted-foreground hover:text-foreground transition-colors",
48
+ className: "shrink-0 text-muted-foreground hover:text-foreground transition-colors",
49
49
  "aria-label": t("integrations.externalIds.openExternal", "Open in external system"),
50
50
  children: /* @__PURE__ */ jsx(ExternalLinkIcon, {})
51
51
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/integrations/widgets/injection/external-ids/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { ExternalIdMapping } from '@open-mercato/shared/modules/integrations/types'\nimport { getIntegrationTitle } from '@open-mercato/shared/modules/integrations/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype IntegrationsData = Record<string, ExternalIdMapping>\n\nconst SYNC_STATUS_STYLES: Record<ExternalIdMapping['syncStatus'], { dot: string; label: string }> = {\n synced: { dot: 'bg-status-success-icon', label: 'integrations.syncStatus.synced' },\n pending: { dot: 'bg-status-warning-icon', label: 'integrations.syncStatus.pending' },\n error: { dot: 'bg-status-error-icon', label: 'integrations.syncStatus.error' },\n not_synced: { dot: 'bg-status-neutral-icon', label: 'integrations.syncStatus.notSynced' },\n}\n\nfunction SyncStatusBadge({ status, lastSynced }: { status: ExternalIdMapping['syncStatus']; lastSynced?: string }) {\n const t = useT()\n const config = SYNC_STATUS_STYLES[status]\n const label = t(config.label, status)\n\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <span className={cn('inline-block size-1.5 rounded-full', config.dot)} aria-hidden=\"true\" />\n <span>{label}</span>\n {lastSynced && (\n <span title={lastSynced}>\n {new Date(lastSynced).toLocaleDateString()}\n </span>\n )}\n </span>\n )\n}\n\nfunction ExternalLinkIcon() {\n return (\n <svg className=\"size-3.5\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path d=\"M6.5 3.5H3a1 1 0 0 0-1 1V13a1 1 0 0 0 1 1h8.5a1 1 0 0 0 1-1V9.5\" />\n <path d=\"M9.5 2h4.5v4.5M14 2 7.5 8.5\" />\n </svg>\n )\n}\n\nexport default function ExternalIdsWidget({ data }: InjectionWidgetComponentProps) {\n const t = useT()\n const integrations = (data as Record<string, unknown>)?._integrations as IntegrationsData | undefined\n\n if (!integrations || Object.keys(integrations).length === 0) return null\n\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <h3 className=\"mb-3 text-sm font-medium\">\n {t('integrations.externalIds.title', 'External IDs')}\n </h3>\n <div className=\"space-y-2\">\n {Object.entries(integrations).map(([integrationId, mapping]) => (\n <div\n key={integrationId}\n className=\"flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <span className=\"text-sm font-medium truncate\">\n {getIntegrationTitle(integrationId)}\n </span>\n <code className=\"rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground\">\n {mapping.externalId}\n </code>\n {mapping.externalUrl && (\n <a\n href={mapping.externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={t('integrations.externalIds.openExternal', 'Open in external system')}\n >\n <ExternalLinkIcon />\n </a>\n )}\n </div>\n <SyncStatusBadge status={mapping.syncStatus} lastSynced={mapping.lastSyncedAt} />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\n"],
5
- "mappings": ";AAwBI,SACE,KADF;AAnBJ,SAAS,2BAA2B;AACpC,SAAS,YAAY;AACrB,SAAS,UAAU;AAInB,MAAM,qBAA8F;AAAA,EAClG,QAAQ,EAAE,KAAK,0BAA0B,OAAO,iCAAiC;AAAA,EACjF,SAAS,EAAE,KAAK,0BAA0B,OAAO,kCAAkC;AAAA,EACnF,OAAO,EAAE,KAAK,wBAAwB,OAAO,gCAAgC;AAAA,EAC7E,YAAY,EAAE,KAAK,0BAA0B,OAAO,oCAAoC;AAC1F;AAEA,SAAS,gBAAgB,EAAE,QAAQ,WAAW,GAAqE;AACjH,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,mBAAmB,MAAM;AACxC,QAAM,QAAQ,EAAE,OAAO,OAAO,MAAM;AAEpC,SACE,qBAAC,UAAK,WAAU,gEACd;AAAA,wBAAC,UAAK,WAAW,GAAG,sCAAsC,OAAO,GAAG,GAAG,eAAY,QAAO;AAAA,IAC1F,oBAAC,UAAM,iBAAM;AAAA,IACZ,cACC,oBAAC,UAAK,OAAO,YACV,cAAI,KAAK,UAAU,EAAE,mBAAmB,GAC3C;AAAA,KAEJ;AAEJ;AAEA,SAAS,mBAAmB;AAC1B,SACE,qBAAC,SAAI,WAAU,YAAW,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAC1F;AAAA,wBAAC,UAAK,GAAE,mEAAkE;AAAA,IAC1E,oBAAC,UAAK,GAAE,+BAA8B;AAAA,KACxC;AAEJ;AAEe,SAAR,kBAAmC,EAAE,KAAK,GAAkC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,eAAgB,MAAkC;AAExD,MAAI,CAAC,gBAAgB,OAAO,KAAK,YAAY,EAAE,WAAW,EAAG,QAAO;AAEpE,SACE,qBAAC,SAAI,WAAU,iCACb;AAAA,wBAAC,QAAG,WAAU,4BACX,YAAE,kCAAkC,cAAc,GACrD;AAAA,IACA,oBAAC,SAAI,WAAU,aACZ,iBAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,eAAe,OAAO,MACxD;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,+BAAC,SAAI,WAAU,mCACb;AAAA,gCAAC,UAAK,WAAU,gCACb,8BAAoB,aAAa,GACpC;AAAA,YACA,oBAAC,UAAK,WAAU,0EACb,kBAAQ,YACX;AAAA,YACC,QAAQ,eACP;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM,QAAQ;AAAA,gBACd,QAAO;AAAA,gBACP,KAAI;AAAA,gBACJ,WAAU;AAAA,gBACV,cAAY,EAAE,yCAAyC,yBAAyB;AAAA,gBAEhF,8BAAC,oBAAiB;AAAA;AAAA,YACpB;AAAA,aAEJ;AAAA,UACA,oBAAC,mBAAgB,QAAQ,QAAQ,YAAY,YAAY,QAAQ,cAAc;AAAA;AAAA;AAAA,MAtB1E;AAAA,IAuBP,CACD,GACH;AAAA,KACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { ExternalIdMapping } from '@open-mercato/shared/modules/integrations/types'\nimport { getIntegrationTitle } from '@open-mercato/shared/modules/integrations/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype IntegrationsData = Record<string, ExternalIdMapping>\n\nconst SYNC_STATUS_STYLES: Record<ExternalIdMapping['syncStatus'], { dot: string; label: string }> = {\n synced: { dot: 'bg-status-success-icon', label: 'integrations.syncStatus.synced' },\n pending: { dot: 'bg-status-warning-icon', label: 'integrations.syncStatus.pending' },\n error: { dot: 'bg-status-error-icon', label: 'integrations.syncStatus.error' },\n not_synced: { dot: 'bg-status-neutral-icon', label: 'integrations.syncStatus.notSynced' },\n}\n\nfunction SyncStatusBadge({ status, lastSynced }: { status: ExternalIdMapping['syncStatus']; lastSynced?: string }) {\n const t = useT()\n const config = SYNC_STATUS_STYLES[status]\n const label = t(config.label, status)\n\n return (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <span className={cn('inline-block size-1.5 rounded-full', config.dot)} aria-hidden=\"true\" />\n <span>{label}</span>\n {lastSynced && (\n <span title={lastSynced}>\n {new Date(lastSynced).toLocaleDateString()}\n </span>\n )}\n </span>\n )\n}\n\nfunction ExternalLinkIcon() {\n return (\n <svg className=\"size-3.5\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path d=\"M6.5 3.5H3a1 1 0 0 0-1 1V13a1 1 0 0 0 1 1h8.5a1 1 0 0 0 1-1V9.5\" />\n <path d=\"M9.5 2h4.5v4.5M14 2 7.5 8.5\" />\n </svg>\n )\n}\n\nexport default function ExternalIdsWidget({ data }: InjectionWidgetComponentProps) {\n const t = useT()\n const integrations = (data as Record<string, unknown>)?._integrations as IntegrationsData | undefined\n\n if (!integrations || Object.keys(integrations).length === 0) return null\n\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <h3 className=\"mb-3 text-sm font-medium\">\n {t('integrations.externalIds.title', 'External IDs')}\n </h3>\n <div className=\"space-y-2\">\n {Object.entries(integrations).map(([integrationId, mapping]) => (\n <div\n key={integrationId}\n className=\"flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2\"\n >\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\">\n <span className=\"text-sm font-medium truncate\">\n {getIntegrationTitle(integrationId)}\n </span>\n <code className=\"min-w-0 max-w-full break-all rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground\">\n {mapping.externalId}\n </code>\n {mapping.externalUrl && (\n <a\n href={mapping.externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"shrink-0 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={t('integrations.externalIds.openExternal', 'Open in external system')}\n >\n <ExternalLinkIcon />\n </a>\n )}\n </div>\n <SyncStatusBadge status={mapping.syncStatus} lastSynced={mapping.lastSyncedAt} />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\n"],
5
+ "mappings": ";AAwBI,SACE,KADF;AAnBJ,SAAS,2BAA2B;AACpC,SAAS,YAAY;AACrB,SAAS,UAAU;AAInB,MAAM,qBAA8F;AAAA,EAClG,QAAQ,EAAE,KAAK,0BAA0B,OAAO,iCAAiC;AAAA,EACjF,SAAS,EAAE,KAAK,0BAA0B,OAAO,kCAAkC;AAAA,EACnF,OAAO,EAAE,KAAK,wBAAwB,OAAO,gCAAgC;AAAA,EAC7E,YAAY,EAAE,KAAK,0BAA0B,OAAO,oCAAoC;AAC1F;AAEA,SAAS,gBAAgB,EAAE,QAAQ,WAAW,GAAqE;AACjH,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,mBAAmB,MAAM;AACxC,QAAM,QAAQ,EAAE,OAAO,OAAO,MAAM;AAEpC,SACE,qBAAC,UAAK,WAAU,yEACd;AAAA,wBAAC,UAAK,WAAW,GAAG,sCAAsC,OAAO,GAAG,GAAG,eAAY,QAAO;AAAA,IAC1F,oBAAC,UAAM,iBAAM;AAAA,IACZ,cACC,oBAAC,UAAK,OAAO,YACV,cAAI,KAAK,UAAU,EAAE,mBAAmB,GAC3C;AAAA,KAEJ;AAEJ;AAEA,SAAS,mBAAmB;AAC1B,SACE,qBAAC,SAAI,WAAU,YAAW,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAC1F;AAAA,wBAAC,UAAK,GAAE,mEAAkE;AAAA,IAC1E,oBAAC,UAAK,GAAE,+BAA8B;AAAA,KACxC;AAEJ;AAEe,SAAR,kBAAmC,EAAE,KAAK,GAAkC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,eAAgB,MAAkC;AAExD,MAAI,CAAC,gBAAgB,OAAO,KAAK,YAAY,EAAE,WAAW,EAAG,QAAO;AAEpE,SACE,qBAAC,SAAI,WAAU,iCACb;AAAA,wBAAC,QAAG,WAAU,4BACX,YAAE,kCAAkC,cAAc,GACrD;AAAA,IACA,oBAAC,SAAI,WAAU,aACZ,iBAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,eAAe,OAAO,MACxD;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,+BAAC,SAAI,WAAU,oDACb;AAAA,gCAAC,UAAK,WAAU,gCACb,8BAAoB,aAAa,GACpC;AAAA,YACA,oBAAC,UAAK,WAAU,uGACb,kBAAQ,YACX;AAAA,YACC,QAAQ,eACP;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM,QAAQ;AAAA,gBACd,QAAO;AAAA,gBACP,KAAI;AAAA,gBACJ,WAAU;AAAA,gBACV,cAAY,EAAE,yCAAyC,yBAAyB;AAAA,gBAEhF,8BAAC,oBAAiB;AAAA;AAAA,YACpB;AAAA,aAEJ;AAAA,UACA,oBAAC,mBAAgB,QAAQ,QAAQ,YAAY,YAAY,QAAQ,cAAc;AAAA;AAAA;AAAA,MAtB1E;AAAA,IAuBP,CACD,GACH;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.10.1-develop.7761.1.9dfaf470ed",
3
+ "version": "0.10.1-develop.7762.1.657bb7236b",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -266,16 +266,16 @@
266
266
  "zod": "^4.4.3"
267
267
  },
268
268
  "peerDependencies": {
269
- "@open-mercato/ai-assistant": "0.10.1-develop.7761.1.9dfaf470ed",
270
- "@open-mercato/shared": "0.10.1-develop.7761.1.9dfaf470ed",
271
- "@open-mercato/ui": "0.10.1-develop.7761.1.9dfaf470ed",
269
+ "@open-mercato/ai-assistant": "0.10.1-develop.7762.1.657bb7236b",
270
+ "@open-mercato/shared": "0.10.1-develop.7762.1.657bb7236b",
271
+ "@open-mercato/ui": "0.10.1-develop.7762.1.657bb7236b",
272
272
  "react": "^19.0.0",
273
273
  "react-dom": "^19.0.0"
274
274
  },
275
275
  "devDependencies": {
276
- "@open-mercato/ai-assistant": "0.10.1-develop.7761.1.9dfaf470ed",
277
- "@open-mercato/shared": "0.10.1-develop.7761.1.9dfaf470ed",
278
- "@open-mercato/ui": "0.10.1-develop.7761.1.9dfaf470ed",
276
+ "@open-mercato/ai-assistant": "0.10.1-develop.7762.1.657bb7236b",
277
+ "@open-mercato/shared": "0.10.1-develop.7762.1.657bb7236b",
278
+ "@open-mercato/ui": "0.10.1-develop.7762.1.657bb7236b",
279
279
  "@testing-library/dom": "^10.4.1",
280
280
  "@testing-library/jest-dom": "^7.0.1",
281
281
  "@testing-library/react": "^16.3.3",
@@ -325,6 +325,8 @@ export default function AccountStatusWidget({ context }: AccountStatusProps) {
325
325
  queryClient.invalidateQueries({ queryKey: ['customer-account-pending-invitation', personEntityId] })
326
326
  }
327
327
 
328
+ if (!personEntityId) return null
329
+
328
330
  if (isLoading) {
329
331
  return <div className="text-sm text-muted-foreground">{t('common.loading', 'Loading...')}</div>
330
332
  }
@@ -1,3 +1,4 @@
1
+ import { enrichCustomerDetailResponse } from '../../../lib/detailResponseEnrichment'
1
2
  import { NextResponse } from 'next/server'
2
3
  import { z } from 'zod'
3
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
@@ -524,7 +525,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
524
525
  : null
525
526
  if (cache && cacheKey) {
526
527
  const cached = await runWithCacheTenant(cacheTenantId, () => cache.get(cacheKey))
527
- if (cached) return NextResponse.json(cached)
528
+ if (cached) return NextResponse.json(await enrichCustomerDetailResponse(cached as Record<string, unknown>, 'company', { auth, container, em, tenantId: companyScope.tenantId, organizationId: companyScope.organizationId }))
528
529
  }
529
530
 
530
531
  const shouldLoadCanonicalInteractions = includeInteractions || includeActivities || includeTodos
@@ -1211,7 +1212,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
1211
1212
  }
1212
1213
  }
1213
1214
 
1214
- return NextResponse.json(payload)
1215
+ return NextResponse.json(await enrichCustomerDetailResponse(payload, 'company', { auth, container, em, tenantId: companyScope.tenantId, organizationId: companyScope.organizationId }))
1215
1216
  }
1216
1217
 
1217
1218
  const companyDetailQuerySchema = z.object({
@@ -1,3 +1,4 @@
1
+ import { enrichCustomerDetailResponse } from '../../../lib/detailResponseEnrichment'
1
2
  import { NextResponse } from 'next/server'
2
3
  import { z } from 'zod'
3
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
@@ -491,20 +492,11 @@ export async function GET(request: Request, context: { params?: Record<string, u
491
492
  })
492
493
  : null
493
494
 
495
+ let cachedBody: Record<string, unknown> | null = null
494
496
  if (detailCache && detailCacheKey) {
495
497
  try {
496
498
  const cached = await runWithCacheTenant(cacheTenantId, () => detailCache.get(detailCacheKey))
497
- if (cached && typeof cached === 'object') {
498
- const viewerInfo = await resolveViewer()
499
- return NextResponse.json({
500
- ...(cached as Record<string, unknown>),
501
- viewer: {
502
- userId: viewerUserId,
503
- name: viewerInfo.name,
504
- email: viewerInfo.email,
505
- },
506
- })
507
- }
499
+ if (cached && typeof cached === 'object') cachedBody = cached as Record<string, unknown>
508
500
  } catch (err) {
509
501
  // A cache-backend read error must degrade to a fresh DB read, never a 500.
510
502
  debugCrudCache('get', {
@@ -514,6 +506,14 @@ export async function GET(request: Request, context: { params?: Record<string, u
514
506
  })
515
507
  }
516
508
  }
509
+ if (cachedBody) {
510
+ const viewerInfo = await resolveViewer()
511
+ return NextResponse.json(await enrichCustomerDetailResponse({
512
+ ...cachedBody,
513
+ viewer: { userId: viewerUserId, name: viewerInfo.name, email: viewerInfo.email },
514
+ }, 'deal', { auth, container, em, tenantId: cacheTenantId, organizationId: cacheOrganizationId }))
515
+ }
516
+
517
517
  // Issue #3175: the reads below only depend on the already-resolved deal +
518
518
  // organization scope + decryption scope, so they are grouped into Promise.all
519
519
  // batches instead of being awaited one after another. True dependencies stay
@@ -872,7 +872,7 @@ export async function GET(request: Request, context: { params?: Record<string, u
872
872
  }
873
873
  }
874
874
 
875
- return NextResponse.json({ ...cacheableBody, viewer })
875
+ return NextResponse.json(await enrichCustomerDetailResponse({ ...cacheableBody, viewer }, 'deal', { auth, container, em, tenantId: cacheTenantId, organizationId: cacheOrganizationId }))
876
876
  }
877
877
 
878
878
  const dealDetailQuerySchema = z.object({
@@ -1,3 +1,4 @@
1
+ import { enrichCustomerDetailResponse } from '../../../lib/detailResponseEnrichment'
1
2
  import { NextResponse } from 'next/server'
2
3
  import { z } from 'zod'
3
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
@@ -579,7 +580,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
579
580
  statusCode = 200
580
581
  profileMeta = { cacheHit: true, include: Array.from(includeTokens), interactionMode }
581
582
  profiler.mark('cache_hit')
582
- return NextResponse.json(cached)
583
+ return NextResponse.json(await enrichCustomerDetailResponse(cached as Record<string, unknown>, 'person', { auth, container, em, tenantId: personDetailTenantId, organizationId: personDetailOrganizationId }))
583
584
  }
584
585
  }
585
586
 
@@ -1134,7 +1135,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
1134
1135
  } catch {}
1135
1136
  }
1136
1137
 
1137
- const response = NextResponse.json(payload)
1138
+ const response = NextResponse.json(await enrichCustomerDetailResponse(payload, 'person', { auth, container, em, tenantId: personDetailTenantId, organizationId: personDetailOrganizationId }))
1138
1139
  statusCode = 200
1139
1140
  profileMeta = {
1140
1141
  include: Array.from(includeTokens),
@@ -45,6 +45,8 @@ export default function CreateCompanyPage() {
45
45
  initialValues={{ addresses: [] as CompanyFormValues['addresses'] }}
46
46
  injectionSpotId={extensionPoints.hosts.companyForm.spotId}
47
47
  legacyInjectionSpotId={COMPANY_CREATE_LEGACY_INJECTION_SPOT_ID}
48
+ entityId="customers.company"
49
+ resourceKind="customers.company"
48
50
  entityIds={[E.customers.customer_entity, E.customers.customer_company_profile]}
49
51
  submitLabel={t('customers.companies.form.submit')}
50
52
  cancelHref={returnTo ?? '/backend/customers/companies'}
@@ -125,6 +127,10 @@ export default function CreateCompanyPage() {
125
127
  }
126
128
 
127
129
  flash(t('customers.companies.form.success'), 'success')
130
+ return newId ? { resourceId: newId } : undefined
131
+ }}
132
+ onSubmitSuccess={async (_values, result) => {
133
+ const newId = result?.resourceId
128
134
  if (returnTo) router.push(returnTo)
129
135
  else if (newId) router.push(`/backend/customers/companies-v2/${newId}`)
130
136
  else router.push('/backend/customers/companies')
@@ -15,7 +15,9 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
15
15
  import { Button } from '@open-mercato/ui/primitives/button'
16
16
  import { AttachmentsSection, ErrorMessage, LoadingMessage, RecordNotFoundState, type SectionAction } from '@open-mercato/ui/backend/detail'
17
17
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
18
- import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
18
+ import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
19
+ import { useCustomerInjectedTabs } from '../../../../components/detail/useCustomerInjectedTabs'
20
+ import { CustomerDetailSidebar } from '../../../../components/detail/CustomerDetailSidebar'
19
21
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
20
22
  import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
21
23
  import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
@@ -32,7 +34,7 @@ import type { TagsSectionController } from '@open-mercato/ui/backend/detail'
32
34
  import { coerceDisplayName } from '../../../../lib/displayName'
33
35
  import { isDetailNotFoundStatus } from '@open-mercato/core/modules/customers/lib/detailHelpers'
34
36
  import { CompanyDetailHeader } from '../../../../components/detail/CompanyDetailHeader'
35
- import { CompanyDetailTabs, resolveLegacyTab, type CompanyTabId } from '../../../../components/detail/CompanyDetailTabs'
37
+ import { CompanyDetailTabs, COMPANY_DETAIL_TAB_IDS, resolveLegacyTab, type CompanyTabId } from '../../../../components/detail/CompanyDetailTabs'
36
38
  import { useDealsAccess } from '../../../../components/detail/useDealsAccess'
37
39
  import { CompanyKpiBar } from '../../../../components/detail/CompanyKpiBar'
38
40
  import { ScheduleActivityDialog, type ScheduleActivityEditData } from '../../../../components/detail/ScheduleActivityDialog'
@@ -192,9 +194,11 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
192
194
  resourceKind: 'customers.company',
193
195
  resourceId: currentCompanyId ?? (id ?? undefined),
194
196
  data,
197
+ isDirty,
198
+ isSaving,
195
199
  retryLastMutation,
196
200
  }),
197
- [currentCompanyId, data, id, mutationContextId, retryLastMutation],
201
+ [currentCompanyId, data, isDirty, isSaving, id, mutationContextId, retryLastMutation],
198
202
  )
199
203
  const runMutationWithContext = React.useCallback(
200
204
  async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {
@@ -295,36 +299,19 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
295
299
  setScheduleDialogOpen(true)
296
300
  }, [])
297
301
 
298
- // Injected tabs from UMES
299
- const { widgets: injectedTabWidgets } = useInjectionWidgets('detail:customers.company:tabs', {
302
+ const { injectedTabs, injectedTabMap, loading: injectedTabsLoading } = useCustomerInjectedTabs({
303
+ spotId: extensionPoints.hosts.companyTabs.spotId,
300
304
  context: injectionContext,
301
- triggerOnLoad: true,
305
+ data,
306
+ onDataChange: setData,
307
+ nativeTabIds: COMPANY_DETAIL_TAB_IDS,
302
308
  })
303
309
 
304
- const injectedTabs = React.useMemo(
305
- () =>
306
- (injectedTabWidgets ?? [])
307
- .filter((widget) => (widget.placement?.kind ?? 'tab') === 'tab')
308
- .map((widget) => {
309
- const tabId = widget.placement?.groupId ?? widget.widgetId
310
- const label = widget.placement?.groupLabel
311
- ? t(widget.placement.groupLabel, widget.placement.groupLabel)
312
- : widget.module.metadata.title ?? tabId
313
- const priority = typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0
314
- const render = () => (
315
- <widget.module.Widget
316
- context={injectionContext}
317
- data={data}
318
- onDataChange={(next: unknown) => setData(next as CompanyOverview)}
319
- />
320
- )
321
- return { id: tabId, label, priority, render }
322
- })
323
- .sort((a, b) => b.priority - a.priority),
324
- [data, injectedTabWidgets, injectionContext, t],
325
- )
326
-
327
- const injectedTabMap = React.useMemo(() => new Map(injectedTabs.map((tab) => [tab.id, tab.render])), [injectedTabs])
310
+ const injectedTabIds = React.useMemo(() => injectedTabs.map((tab) => tab.id), [injectedTabs])
311
+ React.useEffect(() => {
312
+ const requested = resolveLegacyTab(searchParams?.get('tab'), injectedTabIds)
313
+ setActiveTab(requested === 'deals' && isDealsAccessReady && !canViewDeals ? 'people' : requested)
314
+ }, [searchParams, injectedTabIds, isDealsAccessReady, canViewDeals])
328
315
 
329
316
  // Tags
330
317
  const handleTagsChange = React.useCallback((nextTags: TagSummary[]) => {
@@ -492,6 +479,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
492
479
 
493
480
  {/* Persistent company header */}
494
481
  <CompanyDetailHeader
482
+ actions={<InjectionSpot spotId={extensionPoints.hosts.companyHeaderActions.spotId} context={injectionContext} data={data} disabled={isSaving} />}
495
483
  data={data}
496
484
  onTagsChange={handleTagsChange}
497
485
  tagsSectionControllerRef={tagsSectionControllerRef}
@@ -506,6 +494,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
506
494
  <CompanyKpiBar data={data} />
507
495
 
508
496
  {/* Two-zone layout: zone1 = form, zone2 = tabs */}
497
+ <CustomerDetailSidebar spotId={extensionPoints.hosts.companySidebar.spotId} context={injectionContext} data={data} onDataChange={setData} disabled={isSaving}>
509
498
  <CollapsibleZoneLayout
510
499
  pageType="company-v2"
511
500
  entityName={companyName}
@@ -517,6 +506,10 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
517
506
  embedded
518
507
  trackDirtyWhenEmbedded
519
508
  injectionSpotId={extensionPoints.hosts.companyForm.spotId}
509
+ entityId="customers.company"
510
+ resourceKind="customers.company"
511
+ resourceId={companyId}
512
+ injectionGroupAliases={{ details: 'identity', profile: 'businessProfile' }}
520
513
  entityIds={[E.customers.customer_entity, E.customers.customer_company_profile]}
521
514
  schema={formSchema}
522
515
  fields={formFields}
@@ -536,6 +529,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
536
529
  <CompanyDetailTabs
537
530
  activeTab={activeTab}
538
531
  onTabChange={handleTabChange}
532
+ isLoadingInjectedTabs={injectedTabsLoading}
539
533
  injectedTabs={injectedTabs.map((tab) => ({ id: tab.id, label: tab.label }))}
540
534
  peopleCount={data.counts?.people ?? 0}
541
535
  dealsCount={dealCount}
@@ -621,7 +615,10 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
621
615
  }
622
616
  />
623
617
 
618
+ </CustomerDetailSidebar>
619
+
624
620
  {/* UMES footer injection */}
621
+
625
622
  <InjectionSpot spotId={extensionPoints.hosts.companyFooter.spotId} context={injectionContext} data={data} />
626
623
 
627
624
  {/* Schedule Activity Dialog */}
@@ -1,60 +1,25 @@
1
1
  import * as React from 'react'
2
- import { useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
2
+ import { useCustomerInjectedTabs } from '../../../../../components/detail/useCustomerInjectedTabs'
3
+ import { DEAL_DETAIL_TAB_IDS } from '../../../../../components/detail/DealDetailTabs'
4
+ import { extensionPoints } from '../../../../../extension-points'
3
5
  import type { DealDetailPayload } from './types'
4
6
 
5
- type InjectedTab = {
6
- id: string
7
- label: string
8
- priority: number
9
- render: () => React.ReactNode
10
- }
11
-
12
7
  type UseDealInjectedTabsOptions = {
13
8
  injectionContext: unknown
14
9
  data: DealDetailPayload | null
15
10
  setData: React.Dispatch<React.SetStateAction<DealDetailPayload | null>>
16
11
  }
17
12
 
18
- type UseDealInjectedTabsResult = {
19
- injectedTabs: InjectedTab[]
20
- injectedTabMap: Map<string, () => React.ReactNode>
21
- }
22
-
23
13
  export function useDealInjectedTabs({
24
14
  injectionContext,
25
15
  data,
26
16
  setData,
27
- }: UseDealInjectedTabsOptions): UseDealInjectedTabsResult {
28
- const { widgets: injectedTabWidgets } = useInjectionWidgets('detail:customers.deal:tabs', {
17
+ }: UseDealInjectedTabsOptions) {
18
+ return useCustomerInjectedTabs({
19
+ spotId: extensionPoints.hosts.dealTabs.spotId,
29
20
  context: injectionContext,
30
- triggerOnLoad: true,
21
+ data,
22
+ onDataChange: setData,
23
+ nativeTabIds: DEAL_DETAIL_TAB_IDS,
31
24
  })
32
-
33
- const injectedTabs = React.useMemo<InjectedTab[]>(
34
- () =>
35
- (injectedTabWidgets ?? [])
36
- .filter((widget) => (widget.placement?.kind ?? 'tab') === 'tab')
37
- .map((widget) => {
38
- const tabId = widget.placement?.groupId ?? widget.widgetId
39
- const label = widget.placement?.groupLabel ?? widget.module.metadata.title ?? tabId
40
- const priority = typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0
41
- const render = () => (
42
- <widget.module.Widget
43
- context={injectionContext}
44
- data={data}
45
- onDataChange={(next: unknown) => setData(next as DealDetailPayload)}
46
- />
47
- )
48
- return { id: tabId, label, priority, render }
49
- })
50
- .sort((left, right) => right.priority - left.priority),
51
- [data, injectedTabWidgets, injectionContext, setData],
52
- )
53
-
54
- const injectedTabMap = React.useMemo(
55
- () => new Map(injectedTabs.map((tab) => [tab.id, tab.render])),
56
- [injectedTabs],
57
- )
58
-
59
- return { injectedTabs, injectedTabMap }
60
25
  }
@@ -10,6 +10,7 @@ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
10
10
  import { Button } from '@open-mercato/ui/primitives/button'
11
11
  import { AttachmentsSection, ErrorMessage, LoadingMessage, NotesSection, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
12
12
  import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
13
+ import { CustomerDetailSidebar } from '../../../../components/detail/CustomerDetailSidebar'
13
14
  import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
14
15
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
15
16
  import { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
@@ -100,7 +101,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
100
101
  [detailTranslator, runMutationWithContext],
101
102
  )
102
103
 
103
- const { injectedTabs, injectedTabMap } = useDealInjectedTabs({
104
+ const { injectedTabs, injectedTabMap, loading: injectedTabsLoading } = useDealInjectedTabs({
104
105
  injectionContext,
105
106
  data,
106
107
  setData,
@@ -428,8 +429,6 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
428
429
  const zone2Content = (
429
430
  <div className="rounded-[10px] border border-border bg-card px-5 py-5">
430
431
  {(() => {
431
- const injected = injectedTabMap.get(activeTab)
432
- if (injected) return injected()
433
432
 
434
433
  if (activeTab === 'activities') {
435
434
  const activityEntitySelection = activityEntities.length > 1 ? (
@@ -618,6 +617,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
618
617
 
619
618
  return null
620
619
  })()}
620
+ {injectedTabMap.get(activeTab)?.()}
621
621
  </div>
622
622
  )
623
623
 
@@ -628,6 +628,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
628
628
  <InjectionSpot spotId={extensionPoints.hosts.dealHeader.spotId} context={injectionContext} data={data} />
629
629
 
630
630
  <DealDetailHeader
631
+ actions={<InjectionSpot spotId={extensionPoints.hosts.dealHeaderActions.spotId} context={{ ...injectionContext, isDirty, isSaving }} data={data} disabled={isSaving} />}
631
632
  deal={data.deal}
632
633
  owner={data.owner}
633
634
  people={data.people}
@@ -661,7 +662,9 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
661
662
  )}
662
663
  />
663
664
 
665
+ <CustomerDetailSidebar spotId={extensionPoints.hosts.dealSidebar.spotId} context={injectionContext} data={data} onDataChange={setData} disabled={isSaving}>
664
666
  <DealDetailTabs
667
+ isLoadingInjectedTabs={injectedTabsLoading}
665
668
  activeTab={activeTab}
666
669
  onTabChange={handleTabChange}
667
670
  injectedTabs={injectedTabs.map((tab) => ({ id: tab.id, label: tab.label }))}
@@ -678,6 +681,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
678
681
  zone2={zone2Content}
679
682
  />
680
683
  </DealDetailTabs>
684
+ </CustomerDetailSidebar>
681
685
 
682
686
  <InjectionSpot spotId={extensionPoints.hosts.dealFooter.spotId} context={injectionContext} data={data} />
683
687
  </div>