@open-mercato/core 0.6.6-develop.6314.1.c7b8291aa2 → 0.6.6-develop.6330.1.a261878aa8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +7 -12
  3. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  4. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +5 -2
  5. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  6. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -1
  7. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  8. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +2 -1
  9. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +2 -2
  10. package/dist/modules/customers/ai-tools/_shared.js +158 -0
  11. package/dist/modules/customers/ai-tools/_shared.js.map +7 -0
  12. package/dist/modules/customers/ai-tools/companies-pack.js +15 -121
  13. package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
  14. package/dist/modules/customers/ai-tools/people-pack.js +9 -107
  15. package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
  16. package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -0
  17. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  18. package/dist/modules/customers/components/calendar/EventPeekPopover.js +12 -1
  19. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
  20. package/dist/modules/customers/components/calendar/TimeGrid.js +3 -0
  21. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  22. package/dist/modules/customers/components/calendar/types.js.map +1 -1
  23. package/dist/modules/customers/components/detail/EntityTagsDialog.js +76 -67
  24. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +3 -3
  25. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +4 -3
  26. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  27. package/dist/modules/data_sync/api/runs.js +1 -0
  28. package/dist/modules/data_sync/api/runs.js.map +2 -2
  29. package/dist/modules/data_sync/backend/data-sync/page.js +3 -1
  30. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  31. package/dist/modules/data_sync/data/validators.js +1 -0
  32. package/dist/modules/data_sync/data/validators.js.map +2 -2
  33. package/dist/modules/data_sync/lib/sync-run-service.js +20 -0
  34. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  35. package/dist/modules/entities/api/records.js +11 -1
  36. package/dist/modules/entities/api/records.js.map +2 -2
  37. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +17 -10
  38. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  39. package/dist/modules/messages/api/unread-count/route.js +32 -0
  40. package/dist/modules/messages/api/unread-count/route.js.map +2 -2
  41. package/dist/modules/query_index/components/QueryIndexesTable.js +13 -5
  42. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  43. package/dist/modules/shipping_carriers/i18n/en.js +11 -1
  44. package/dist/modules/shipping_carriers/i18n/en.js.map +2 -2
  45. package/dist/modules/shipping_carriers/i18n/pl.js +11 -1
  46. package/dist/modules/shipping_carriers/i18n/pl.js.map +2 -2
  47. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js +25 -0
  48. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js.map +7 -0
  49. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +3 -17
  50. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +2 -2
  51. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +19 -14
  52. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
  53. package/package.json +7 -7
  54. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +7 -10
  55. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -7
  56. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +3 -2
  57. package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +3 -2
  58. package/src/modules/customers/ai-tools/_shared.ts +270 -0
  59. package/src/modules/customers/ai-tools/companies-pack.ts +17 -157
  60. package/src/modules/customers/ai-tools/people-pack.ts +11 -133
  61. package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -0
  62. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +29 -11
  63. package/src/modules/customers/components/calendar/TimeGrid.tsx +3 -0
  64. package/src/modules/customers/components/calendar/types.ts +1 -0
  65. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +89 -71
  66. package/src/modules/customers/i18n/de.json +1 -0
  67. package/src/modules/customers/i18n/en.json +1 -0
  68. package/src/modules/customers/i18n/es.json +1 -0
  69. package/src/modules/customers/i18n/pl.json +1 -0
  70. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +6 -3
  71. package/src/modules/data_sync/api/runs.ts +1 -0
  72. package/src/modules/data_sync/backend/data-sync/page.tsx +3 -1
  73. package/src/modules/data_sync/data/validators.ts +1 -0
  74. package/src/modules/data_sync/i18n/de.json +1 -0
  75. package/src/modules/data_sync/i18n/en.json +1 -0
  76. package/src/modules/data_sync/i18n/es.json +1 -0
  77. package/src/modules/data_sync/i18n/pl.json +1 -0
  78. package/src/modules/data_sync/lib/sync-run-service.ts +23 -0
  79. package/src/modules/entities/api/records.ts +17 -1
  80. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +25 -12
  81. package/src/modules/messages/api/unread-count/route.ts +40 -0
  82. package/src/modules/query_index/components/QueryIndexesTable.tsx +17 -15
  83. package/src/modules/shipping_carriers/i18n/de.json +11 -1
  84. package/src/modules/shipping_carriers/i18n/en.json +11 -1
  85. package/src/modules/shipping_carriers/i18n/en.ts +10 -0
  86. package/src/modules/shipping_carriers/i18n/es.json +11 -1
  87. package/src/modules/shipping_carriers/i18n/pl.json +11 -1
  88. package/src/modules/shipping_carriers/i18n/pl.ts +10 -0
  89. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx +35 -0
  90. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +3 -22
  91. package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +30 -13
@@ -19,6 +19,12 @@ import {
19
19
  type AiToolExecutionContext,
20
20
  } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
21
21
  import { assertTenantScope, type CustomersAiToolDefinition, type CustomersToolContext } from './types'
22
+ import {
23
+ buildRelatedRecords,
24
+ toCustomerListSummary,
25
+ toIso,
26
+ type CustomerRelatedRecords,
27
+ } from './_shared'
22
28
 
23
29
  const listCompaniesInput = z
24
30
  .object({
@@ -108,27 +114,13 @@ const listCompaniesTool = defineApiBackedAiTool<
108
114
  const data = (response.data ?? {}) as ListCompaniesApiResponse
109
115
  const rawItems: ListCompaniesApiItem[] = Array.isArray(data.items) ? data.items : []
110
116
  return {
111
- items: rawItems.map((row) => {
112
- const createdAtRaw = row.created_at ?? row.createdAt ?? null
113
- const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
114
- return {
115
- id: row.id,
116
- displayName: row.display_name ?? row.displayName ?? null,
117
- primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
118
- primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
119
- status: row.status ?? null,
120
- lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
121
- source: row.source ?? null,
122
- ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
123
- organizationId: row.organization_id ?? row.organizationId ?? null,
124
- tenantId: row.tenant_id ?? row.tenantId ?? null,
125
- domain: row.domain ?? null,
126
- websiteUrl: row.website_url ?? row.websiteUrl ?? null,
127
- industry: row.industry ?? null,
128
- sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,
129
- createdAt,
130
- }
131
- }),
117
+ items: rawItems.map((row) => ({
118
+ ...toCustomerListSummary(row),
119
+ domain: row.domain ?? null,
120
+ websiteUrl: row.website_url ?? row.websiteUrl ?? null,
121
+ industry: row.industry ?? null,
122
+ sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,
123
+ })),
132
124
  total: typeof data.total === 'number' ? data.total : 0,
133
125
  limit,
134
126
  offset,
@@ -146,13 +138,6 @@ const getCompanyInput = z.object({
146
138
 
147
139
  type GetCompanyInput = z.infer<typeof getCompanyInput>
148
140
 
149
- function toIsoCompany(value: unknown): string | null {
150
- if (!value) return null
151
- const dt = value instanceof Date ? value : new Date(String(value))
152
- if (Number.isNaN(dt.getTime())) return null
153
- return dt.toISOString()
154
- }
155
-
156
141
  const getCompanyTool: CustomersAiToolDefinition = {
157
142
  name: 'customers.get_company',
158
143
  displayName: 'Get company',
@@ -193,134 +178,9 @@ const getCompanyTool: CustomersAiToolDefinition = {
193
178
  const profileRow = (data.profile ?? null) as Record<string, unknown> | null
194
179
  const customFields = (data.customFields ?? {}) as Record<string, unknown>
195
180
 
196
- let related: Record<string, unknown> | null = null
181
+ let related: CustomerRelatedRecords | null = null
197
182
  if (includeRelated) {
198
- const addresses = Array.isArray(data.addresses) ? (data.addresses as Array<Record<string, unknown>>) : []
199
- const activities = Array.isArray(data.activities) ? (data.activities as Array<Record<string, unknown>>) : []
200
- const notes = Array.isArray(data.comments) ? (data.comments as Array<Record<string, unknown>>) : []
201
- const todos = Array.isArray(data.todos) ? (data.todos as Array<Record<string, unknown>>) : []
202
- const interactions = Array.isArray(data.interactions) ? (data.interactions as Array<Record<string, unknown>>) : []
203
- const tagsRows = Array.isArray(data.tags) ? (data.tags as Array<Record<string, unknown>>) : []
204
- const dealsRows = Array.isArray(data.deals) ? (data.deals as Array<Record<string, unknown>>) : []
205
- const peopleRows = Array.isArray(data.people) ? (data.people as Array<Record<string, unknown>>) : []
206
- related = {
207
- addresses: addresses.map((address) => ({
208
- id: address.id,
209
- name: address.name ?? null,
210
- purpose: address.purpose ?? null,
211
- addressLine1: address.addressLine1 ?? null,
212
- addressLine2: address.addressLine2 ?? null,
213
- city: address.city ?? null,
214
- region: address.region ?? null,
215
- postalCode: address.postalCode ?? null,
216
- country: address.country ?? null,
217
- isPrimary: !!address.isPrimary,
218
- })),
219
- activities: activities.map((activity) => ({
220
- id: activity.id,
221
- activityType: activity.activityType,
222
- subject: activity.subject ?? null,
223
- body: activity.body ?? null,
224
- occurredAt: toIsoCompany(activity.occurredAt),
225
- createdAt: toIsoCompany(activity.createdAt),
226
- })),
227
- notes: notes.map((comment) => ({
228
- id: comment.id,
229
- body: comment.body,
230
- authorUserId: comment.authorUserId ?? null,
231
- createdAt: toIsoCompany(comment.createdAt),
232
- })),
233
- tasks: todos.map((task) => ({
234
- id: task.id,
235
- todoId: task.todoId ?? task.id,
236
- todoSource: task.todoSource ?? null,
237
- createdAt: toIsoCompany(task.createdAt),
238
- })),
239
- interactions: interactions.map((interaction) => ({
240
- id: interaction.id,
241
- interactionType: interaction.interactionType,
242
- title: interaction.title ?? null,
243
- status: interaction.status,
244
- scheduledAt: toIsoCompany(interaction.scheduledAt),
245
- occurredAt: toIsoCompany(interaction.occurredAt),
246
- })),
247
- tags: tagsRows
248
- .map((tag) => {
249
- if (!tag || typeof tag !== 'object') return null
250
- const id = typeof tag.id === 'string' ? tag.id : null
251
- const label = typeof tag.label === 'string' ? tag.label : null
252
- if (!id || !label) return null
253
- const slug = typeof tag.slug === 'string' ? tag.slug : label
254
- const color = typeof tag.color === 'string' ? tag.color : null
255
- return { id, slug, label, color }
256
- })
257
- .filter(
258
- (entry): entry is { id: string; slug: string; label: string; color: string | null } =>
259
- entry !== null,
260
- ),
261
- deals: dealsRows
262
- .map((deal) => {
263
- if (!deal || typeof deal !== 'object') return null
264
- const id = typeof deal.id === 'string' ? deal.id : null
265
- if (!id) return null
266
- return {
267
- id,
268
- title: typeof deal.title === 'string' ? deal.title : '',
269
- status: typeof deal.status === 'string' ? deal.status : null,
270
- pipelineStageId:
271
- typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
272
- valueAmount:
273
- typeof deal.valueAmount === 'string'
274
- ? deal.valueAmount
275
- : deal.valueAmount === null || deal.valueAmount === undefined
276
- ? null
277
- : String(deal.valueAmount),
278
- valueCurrency:
279
- typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
280
- }
281
- })
282
- .filter(
283
- (
284
- value,
285
- ): value is {
286
- id: string
287
- title: string
288
- status: string | null
289
- pipelineStageId: string | null
290
- valueAmount: string | null
291
- valueCurrency: string | null
292
- } => value !== null,
293
- ),
294
- people: peopleRows
295
- .map((person) => {
296
- if (!person || typeof person !== 'object') return null
297
- const id = typeof person.id === 'string' ? person.id : null
298
- const displayName = typeof person.displayName === 'string' ? person.displayName : null
299
- if (!id || !displayName) return null
300
- return {
301
- id,
302
- displayName,
303
- primaryEmail:
304
- typeof person.primaryEmail === 'string' ? person.primaryEmail : null,
305
- primaryPhone:
306
- typeof person.primaryPhone === 'string' ? person.primaryPhone : null,
307
- jobTitle: typeof person.jobTitle === 'string' ? person.jobTitle : null,
308
- department: typeof person.department === 'string' ? person.department : null,
309
- }
310
- })
311
- .filter(
312
- (
313
- value,
314
- ): value is {
315
- id: string
316
- displayName: string
317
- primaryEmail: string | null
318
- primaryPhone: string | null
319
- jobTitle: string | null
320
- department: string | null
321
- } => value !== null,
322
- ),
323
- }
183
+ related = buildRelatedRecords(data, { includePeople: true })
324
184
  }
325
185
  return {
326
186
  found: true as const,
@@ -336,8 +196,8 @@ const getCompanyTool: CustomersAiToolDefinition = {
336
196
  ownerUserId: companyRow.ownerUserId ?? null,
337
197
  organizationId: companyRow.organizationId ?? null,
338
198
  tenantId: companyRow.tenantId ?? null,
339
- createdAt: toIsoCompany(companyRow.createdAt),
340
- updatedAt: toIsoCompany(companyRow.updatedAt),
199
+ createdAt: toIso(companyRow.createdAt),
200
+ updatedAt: toIso(companyRow.updatedAt),
341
201
  },
342
202
  profile: profileRow
343
203
  ? {
@@ -17,7 +17,6 @@
17
17
  * documented aggregate detail route). Tool name, schema, requiredFeatures,
18
18
  * and output shape are unchanged.
19
19
  */
20
- import type { EntityManager } from '@mikro-orm/postgresql'
21
20
  import { z } from 'zod'
22
21
  import { defineApiBackedAiTool } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool'
23
22
  import {
@@ -30,20 +29,17 @@ import {
30
29
  CustomerPersonProfile,
31
30
  } from '../data/entities'
32
31
  import { assertTenantScope, type CustomersAiToolDefinition, type CustomersToolContext } from './types'
32
+ import {
33
+ buildRelatedRecords,
34
+ buildScope,
35
+ resolveEm,
36
+ toCustomerListSummary,
37
+ toIso,
38
+ type CustomerRelatedRecords,
39
+ } from './_shared'
33
40
 
34
41
  const NIL_UUID = '00000000-0000-0000-0000-000000000000'
35
42
 
36
- function resolveEm(ctx: CustomersToolContext | AiToolExecutionContext): EntityManager {
37
- return ctx.container.resolve<EntityManager>('em')
38
- }
39
-
40
- function buildScope(ctx: CustomersToolContext | AiToolExecutionContext, tenantId: string) {
41
- return {
42
- tenantId,
43
- organizationId: ctx.organizationId,
44
- }
45
- }
46
-
47
43
  const listPeopleInput = z
48
44
  .object({
49
45
  q: z.string().trim().optional().describe('Optional search text matched against display name / email / phone. Omit or leave empty to list all.'),
@@ -146,23 +142,7 @@ const listPeopleTool = defineApiBackedAiTool<ListPeopleInput, ListPeopleApiRespo
146
142
  const data = (response.data ?? {}) as ListPeopleApiResponse
147
143
  const rawItems: ListPeopleApiItem[] = Array.isArray(data.items) ? data.items : []
148
144
  return {
149
- items: rawItems.map((row) => {
150
- const createdAtRaw = row.created_at ?? row.createdAt ?? null
151
- const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
152
- return {
153
- id: row.id,
154
- displayName: row.display_name ?? row.displayName ?? null,
155
- primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
156
- primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
157
- status: row.status ?? null,
158
- lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
159
- source: row.source ?? null,
160
- ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
161
- organizationId: row.organization_id ?? row.organizationId ?? null,
162
- tenantId: row.tenant_id ?? row.tenantId ?? null,
163
- createdAt,
164
- }
165
- }),
145
+ items: rawItems.map((row) => toCustomerListSummary(row)),
166
146
  total: typeof data.total === 'number' ? data.total : 0,
167
147
  limit,
168
148
  offset,
@@ -182,13 +162,6 @@ type GetPersonInput = z.infer<typeof getPersonInput>
182
162
 
183
163
  type ApiPersonDetailRow = Record<string, unknown> | null | undefined
184
164
 
185
- function toIso(value: unknown): string | null {
186
- if (!value) return null
187
- const dt = value instanceof Date ? value : new Date(String(value))
188
- if (Number.isNaN(dt.getTime())) return null
189
- return dt.toISOString()
190
- }
191
-
192
165
  const getPersonTool: CustomersAiToolDefinition = {
193
166
  name: 'customers.get_person',
194
167
  displayName: 'Get person',
@@ -226,104 +199,9 @@ const getPersonTool: CustomersAiToolDefinition = {
226
199
  const profileRow = (data.profile ?? null) as ApiPersonDetailRow
227
200
  const customFields = (data.customFields ?? {}) as Record<string, unknown>
228
201
 
229
- let related: Record<string, unknown> | null = null
202
+ let related: CustomerRelatedRecords | null = null
230
203
  if (includeRelated) {
231
- const addresses = Array.isArray(data.addresses) ? (data.addresses as Array<Record<string, unknown>>) : []
232
- const activities = Array.isArray(data.activities) ? (data.activities as Array<Record<string, unknown>>) : []
233
- const notes = Array.isArray(data.comments) ? (data.comments as Array<Record<string, unknown>>) : []
234
- const todos = Array.isArray(data.todos) ? (data.todos as Array<Record<string, unknown>>) : []
235
- const interactions = Array.isArray(data.interactions) ? (data.interactions as Array<Record<string, unknown>>) : []
236
- const tagsRows = Array.isArray(data.tags) ? (data.tags as Array<Record<string, unknown>>) : []
237
- const dealsRows = Array.isArray(data.deals) ? (data.deals as Array<Record<string, unknown>>) : []
238
- related = {
239
- addresses: addresses.map((address) => ({
240
- id: address.id,
241
- name: address.name ?? null,
242
- purpose: address.purpose ?? null,
243
- addressLine1: address.addressLine1 ?? null,
244
- addressLine2: address.addressLine2 ?? null,
245
- city: address.city ?? null,
246
- region: address.region ?? null,
247
- postalCode: address.postalCode ?? null,
248
- country: address.country ?? null,
249
- isPrimary: !!address.isPrimary,
250
- })),
251
- activities: activities.map((activity) => ({
252
- id: activity.id,
253
- activityType: activity.activityType,
254
- subject: activity.subject ?? null,
255
- body: activity.body ?? null,
256
- occurredAt: toIso(activity.occurredAt),
257
- createdAt: toIso(activity.createdAt),
258
- })),
259
- notes: notes.map((comment) => ({
260
- id: comment.id,
261
- body: comment.body,
262
- authorUserId: comment.authorUserId ?? null,
263
- createdAt: toIso(comment.createdAt),
264
- })),
265
- tasks: todos.map((task) => ({
266
- id: task.id,
267
- todoId: task.todoId ?? task.id,
268
- todoSource: task.todoSource ?? null,
269
- createdAt: toIso(task.createdAt),
270
- })),
271
- interactions: interactions.map((interaction) => ({
272
- id: interaction.id,
273
- interactionType: interaction.interactionType,
274
- title: interaction.title ?? null,
275
- status: interaction.status,
276
- scheduledAt: toIso(interaction.scheduledAt),
277
- occurredAt: toIso(interaction.occurredAt),
278
- })),
279
- tags: tagsRows
280
- .map((tag) => {
281
- if (!tag || typeof tag !== 'object') return null
282
- const id = typeof tag.id === 'string' ? tag.id : null
283
- const label = typeof tag.label === 'string' ? tag.label : null
284
- if (!id || !label) return null
285
- const slug = typeof tag.slug === 'string' ? tag.slug : label
286
- const color = typeof tag.color === 'string' ? tag.color : null
287
- return { id, slug, label, color }
288
- })
289
- .filter(
290
- (entry): entry is { id: string; slug: string; label: string; color: string | null } =>
291
- entry !== null,
292
- ),
293
- deals: dealsRows
294
- .map((deal) => {
295
- if (!deal || typeof deal !== 'object') return null
296
- const id = typeof deal.id === 'string' ? deal.id : null
297
- if (!id) return null
298
- return {
299
- id,
300
- title: typeof deal.title === 'string' ? deal.title : '',
301
- status: typeof deal.status === 'string' ? deal.status : null,
302
- pipelineStageId:
303
- typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
304
- valueAmount:
305
- typeof deal.valueAmount === 'string'
306
- ? deal.valueAmount
307
- : deal.valueAmount === null || deal.valueAmount === undefined
308
- ? null
309
- : String(deal.valueAmount),
310
- valueCurrency:
311
- typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
312
- }
313
- })
314
- .filter(
315
- (
316
- value,
317
- ): value is {
318
- id: string
319
- title: string
320
- status: string | null
321
- pipelineStageId: string | null
322
- valueAmount: string | null
323
- valueCurrency: string | null
324
- } => value !== null,
325
- ),
326
- }
204
+ related = buildRelatedRecords(data)
327
205
  }
328
206
 
329
207
  return {
@@ -499,6 +499,7 @@ export function CalendarScreen() {
499
499
  showWeekends={preferences.showWeekends}
500
500
  showConflicts={preferences.conflictWarnings}
501
501
  aiSummaries={preferences.aiSummaries}
502
+ canManage={canManage}
502
503
  highlightItemId={highlightItemId}
503
504
  onItemClick={openEditEditor}
504
505
  onJoin={handleJoin}
@@ -5,6 +5,7 @@ import { Sparkles } from 'lucide-react'
5
5
  import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
6
6
  import { Button } from '@open-mercato/ui/primitives/button'
7
7
  import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
8
+ import { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'
8
9
  import { formatTimeRange } from './EventBlock'
9
10
  import type { CalendarItem, CalendarPlatform } from './types'
10
11
 
@@ -20,6 +21,7 @@ export type EventPeekPopoverProps = {
20
21
  open: boolean
21
22
  joinUrl: string | null
22
23
  aiSummaries: boolean
24
+ canManage?: boolean
23
25
  onOpenChange(open: boolean): void
24
26
  onJoin(item: CalendarItem): void
25
27
  onEdit(item: CalendarItem): void
@@ -31,6 +33,7 @@ export function EventPeekPopover({
31
33
  open,
32
34
  joinUrl,
33
35
  aiSummaries,
36
+ canManage = true,
34
37
  onOpenChange,
35
38
  onJoin,
36
39
  onEdit,
@@ -81,17 +84,32 @@ export function EventPeekPopover({
81
84
  {t('customers.calendar.peek.join', 'Join')}
82
85
  </Button>
83
86
  ) : null}
84
- <Button
85
- type="button"
86
- size="sm"
87
- variant="secondary"
88
- onClick={() => {
89
- onOpenChange(false)
90
- onEdit(item)
91
- }}
92
- >
93
- {t('customers.calendar.peek.edit', 'Edit')}
94
- </Button>
87
+ {canManage ? (
88
+ <Button
89
+ type="button"
90
+ size="sm"
91
+ variant="secondary"
92
+ onClick={() => {
93
+ onOpenChange(false)
94
+ onEdit(item)
95
+ }}
96
+ >
97
+ {t('customers.calendar.peek.edit', 'Edit')}
98
+ </Button>
99
+ ) : (
100
+ <SimpleTooltip
101
+ content={t(
102
+ 'customers.calendar.peek.editForbidden',
103
+ "You don't have permission to edit events",
104
+ )}
105
+ >
106
+ <span className="inline-flex">
107
+ <Button type="button" size="sm" variant="secondary" disabled className="pointer-events-none">
108
+ {t('customers.calendar.peek.edit', 'Edit')}
109
+ </Button>
110
+ </span>
111
+ </SimpleTooltip>
112
+ )}
95
113
  </div>
96
114
  </div>
97
115
  </PopoverContent>
@@ -160,6 +160,7 @@ export function TimeGrid({
160
160
  showWeekends,
161
161
  showConflicts,
162
162
  aiSummaries,
163
+ canManage = true,
163
164
  highlightItemId,
164
165
  onItemClick,
165
166
  onJoin,
@@ -351,6 +352,7 @@ export function TimeGrid({
351
352
  open={selectedId === item.id}
352
353
  joinUrl={resolveJoinUrl(item.location)}
353
354
  aiSummaries={aiSummaries}
355
+ canManage={canManage}
354
356
  onOpenChange={(open) => setSelectedId(open ? item.id : null)}
355
357
  onJoin={onJoin}
356
358
  onEdit={onItemClick}
@@ -462,6 +464,7 @@ export function TimeGrid({
462
464
  open={selectedId === block.item.id}
463
465
  joinUrl={resolveJoinUrl(block.item.location)}
464
466
  aiSummaries={aiSummaries}
467
+ canManage={canManage}
465
468
  onOpenChange={(open) => setSelectedId(open ? block.item.id : null)}
466
469
  onJoin={onJoin}
467
470
  onEdit={onItemClick}
@@ -87,6 +87,7 @@ export interface TimeGridProps {
87
87
  showWeekends: boolean
88
88
  showConflicts: boolean
89
89
  aiSummaries: boolean
90
+ canManage?: boolean
90
91
  highlightItemId?: string | null
91
92
  onItemClick(item: CalendarItem): void
92
93
  onJoin(item: CalendarItem): void