@open-mercato/core 0.4.11-develop.1309.4b37381a7a → 0.4.11-develop.1347.c693e6dfee
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/customers/api/companies/[id]/route.js +3 -2
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js +59 -91
- package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +115 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/route.js +3 -2
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +14 -134
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/customers/backend/customer-tasks/page.js +10 -0
- package/dist/modules/customers/backend/customer-tasks/page.js.map +7 -0
- package/dist/modules/customers/backend/customer-tasks/page.meta.js +25 -0
- package/dist/modules/customers/backend/customer-tasks/page.meta.js.map +7 -0
- package/dist/modules/customers/commands/interactions.js +40 -4
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/components/CustomerTodosTable.js +77 -47
- package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +4 -4
- package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +2 -1
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
- package/dist/modules/customers/components/detail/utils.js +3 -0
- package/dist/modules/customers/components/detail/utils.js.map +2 -2
- package/dist/modules/customers/data/entities.js +2 -2
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +2 -2
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/lib/interactionCompatibility.js +12 -2
- package/dist/modules/customers/lib/interactionCompatibility.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +167 -4
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260401172819.js +45 -0
- package/dist/modules/customers/migrations/Migration20260401172819.js.map +7 -0
- package/dist/modules/customers/search.js +3 -2
- package/dist/modules/customers/search.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +10 -2
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +2 -2
- package/package.json +3 -3
- package/src/modules/customers/api/companies/[id]/route.ts +6 -5
- package/src/modules/customers/api/dashboard/widgets/customer-todos/route.ts +69 -126
- package/src/modules/customers/api/interactions/tasks/route.ts +122 -0
- package/src/modules/customers/api/people/[id]/route.ts +3 -2
- package/src/modules/customers/api/todos/route.ts +13 -181
- package/src/modules/customers/backend/customer-tasks/page.meta.ts +23 -0
- package/src/modules/customers/backend/customer-tasks/page.tsx +12 -0
- package/src/modules/customers/commands/interactions.ts +50 -2
- package/src/modules/customers/components/CustomerTodosTable.tsx +91 -66
- package/src/modules/customers/components/detail/TasksSection.tsx +8 -8
- package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +2 -1
- package/src/modules/customers/components/detail/types.ts +6 -0
- package/src/modules/customers/components/detail/utils.ts +3 -0
- package/src/modules/customers/data/entities.ts +2 -2
- package/src/modules/customers/data/validators.ts +2 -2
- package/src/modules/customers/lib/interactionCompatibility.ts +16 -0
- package/src/modules/customers/lib/todoCompatibility.ts +229 -10
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +1 -1
- package/src/modules/customers/migrations/Migration20260401172819.ts +45 -0
- package/src/modules/customers/search.ts +3 -2
- package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +24 -23
|
@@ -53,6 +53,26 @@ const interactionCrudEvents: CrudEventsConfig = {
|
|
|
53
53
|
id: ctx.identifiers.id,
|
|
54
54
|
organizationId: ctx.identifiers.organizationId,
|
|
55
55
|
tenantId: ctx.identifiers.tenantId,
|
|
56
|
+
entityId:
|
|
57
|
+
ctx.entity && typeof ctx.entity === 'object' && 'entity' in (ctx.entity as Record<string, unknown>)
|
|
58
|
+
? (() => {
|
|
59
|
+
const entityRef = (ctx.entity as CustomerInteraction).entity
|
|
60
|
+
return typeof entityRef === 'string' ? entityRef : entityRef?.id ?? null
|
|
61
|
+
})()
|
|
62
|
+
: null,
|
|
63
|
+
interactionType:
|
|
64
|
+
ctx.entity && typeof ctx.entity === 'object' && 'interactionType' in (ctx.entity as Record<string, unknown>)
|
|
65
|
+
? (ctx.entity as CustomerInteraction).interactionType
|
|
66
|
+
: null,
|
|
67
|
+
status:
|
|
68
|
+
ctx.entity && typeof ctx.entity === 'object' && 'status' in (ctx.entity as Record<string, unknown>)
|
|
69
|
+
? (ctx.entity as CustomerInteraction).status
|
|
70
|
+
: null,
|
|
71
|
+
source:
|
|
72
|
+
ctx.entity && typeof ctx.entity === 'object' && 'source' in (ctx.entity as Record<string, unknown>)
|
|
73
|
+
? (ctx.entity as CustomerInteraction).source ?? null
|
|
74
|
+
: null,
|
|
75
|
+
...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),
|
|
56
76
|
}),
|
|
57
77
|
}
|
|
58
78
|
|
|
@@ -167,8 +187,12 @@ async function emitInteractionRevertedEvent(
|
|
|
167
187
|
id: interaction.id,
|
|
168
188
|
organizationId: interaction.organizationId,
|
|
169
189
|
tenantId: interaction.tenantId,
|
|
190
|
+
entityId: interaction.entityId,
|
|
191
|
+
interactionType: interaction.interactionType,
|
|
192
|
+
source: interaction.source ?? null,
|
|
170
193
|
status: interaction.status,
|
|
171
194
|
occurredAt: interaction.occurredAt?.toISOString() ?? null,
|
|
195
|
+
...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),
|
|
172
196
|
})
|
|
173
197
|
}
|
|
174
198
|
|
|
@@ -287,6 +311,7 @@ const createInteractionCommand: CommandHandler<InteractionCreateInput, { interac
|
|
|
287
311
|
organizationId: interaction.organizationId,
|
|
288
312
|
tenantId: interaction.tenantId,
|
|
289
313
|
},
|
|
314
|
+
syncOrigin: ctx.syncOrigin,
|
|
290
315
|
indexer: interactionCrudIndexer,
|
|
291
316
|
events: interactionCrudEvents,
|
|
292
317
|
})
|
|
@@ -413,6 +438,7 @@ const updateInteractionCommand: CommandHandler<InteractionUpdateInput, { interac
|
|
|
413
438
|
organizationId: interaction.organizationId,
|
|
414
439
|
tenantId: interaction.tenantId,
|
|
415
440
|
},
|
|
441
|
+
syncOrigin: ctx.syncOrigin,
|
|
416
442
|
indexer: interactionCrudIndexer,
|
|
417
443
|
events: interactionCrudEvents,
|
|
418
444
|
})
|
|
@@ -532,6 +558,7 @@ const updateInteractionCommand: CommandHandler<InteractionUpdateInput, { interac
|
|
|
532
558
|
organizationId: interaction.organizationId,
|
|
533
559
|
tenantId: interaction.tenantId,
|
|
534
560
|
},
|
|
561
|
+
syncOrigin: ctx.syncOrigin,
|
|
535
562
|
indexer: interactionCrudIndexer,
|
|
536
563
|
events: interactionCrudEvents,
|
|
537
564
|
})
|
|
@@ -587,10 +614,19 @@ const completeInteractionCommand: CommandHandler<InteractionCompleteInput, { int
|
|
|
587
614
|
action: 'updated',
|
|
588
615
|
entity: interaction,
|
|
589
616
|
identifiers,
|
|
617
|
+
syncOrigin: ctx.syncOrigin,
|
|
590
618
|
indexer: interactionCrudIndexer,
|
|
591
619
|
events: interactionCrudEvents,
|
|
592
620
|
})
|
|
593
|
-
await emitLifecycleEvent(ctx, 'customers.interaction.completed',
|
|
621
|
+
await emitLifecycleEvent(ctx, 'customers.interaction.completed', {
|
|
622
|
+
...identifiers,
|
|
623
|
+
entityId,
|
|
624
|
+
interactionType: interaction.interactionType,
|
|
625
|
+
status: interaction.status,
|
|
626
|
+
source: interaction.source ?? null,
|
|
627
|
+
occurredAt: interaction.occurredAt?.toISOString() ?? null,
|
|
628
|
+
...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),
|
|
629
|
+
})
|
|
594
630
|
await emitNextInteractionUpdatedEvent(ctx, { entityId, nextInteractionId }, identifiers)
|
|
595
631
|
|
|
596
632
|
return { interactionId: interaction.id }
|
|
@@ -653,6 +689,7 @@ const completeInteractionCommand: CommandHandler<InteractionCompleteInput, { int
|
|
|
653
689
|
organizationId: result.interaction.organizationId,
|
|
654
690
|
tenantId: result.interaction.tenantId,
|
|
655
691
|
},
|
|
692
|
+
syncOrigin: ctx.syncOrigin,
|
|
656
693
|
indexer: interactionCrudIndexer,
|
|
657
694
|
events: interactionCrudEvents,
|
|
658
695
|
})
|
|
@@ -708,10 +745,18 @@ const cancelInteractionCommand: CommandHandler<InteractionCancelInput, { interac
|
|
|
708
745
|
action: 'updated',
|
|
709
746
|
entity: interaction,
|
|
710
747
|
identifiers,
|
|
748
|
+
syncOrigin: ctx.syncOrigin,
|
|
711
749
|
indexer: interactionCrudIndexer,
|
|
712
750
|
events: interactionCrudEvents,
|
|
713
751
|
})
|
|
714
|
-
await emitLifecycleEvent(ctx, 'customers.interaction.canceled',
|
|
752
|
+
await emitLifecycleEvent(ctx, 'customers.interaction.canceled', {
|
|
753
|
+
...identifiers,
|
|
754
|
+
entityId,
|
|
755
|
+
interactionType: interaction.interactionType,
|
|
756
|
+
status: interaction.status,
|
|
757
|
+
source: interaction.source ?? null,
|
|
758
|
+
...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),
|
|
759
|
+
})
|
|
715
760
|
await emitNextInteractionUpdatedEvent(ctx, { entityId, nextInteractionId }, identifiers)
|
|
716
761
|
|
|
717
762
|
return { interactionId: interaction.id }
|
|
@@ -773,6 +818,7 @@ const cancelInteractionCommand: CommandHandler<InteractionCancelInput, { interac
|
|
|
773
818
|
organizationId: result.interaction.organizationId,
|
|
774
819
|
tenantId: result.interaction.tenantId,
|
|
775
820
|
},
|
|
821
|
+
syncOrigin: ctx.syncOrigin,
|
|
776
822
|
indexer: interactionCrudIndexer,
|
|
777
823
|
events: interactionCrudEvents,
|
|
778
824
|
})
|
|
@@ -828,6 +874,7 @@ const deleteInteractionCommand: CommandHandler<{ body?: Record<string, unknown>;
|
|
|
828
874
|
organizationId: interaction.organizationId,
|
|
829
875
|
tenantId: interaction.tenantId,
|
|
830
876
|
},
|
|
877
|
+
syncOrigin: ctx.syncOrigin,
|
|
831
878
|
indexer: interactionCrudIndexer,
|
|
832
879
|
events: interactionCrudEvents,
|
|
833
880
|
})
|
|
@@ -939,6 +986,7 @@ const deleteInteractionCommand: CommandHandler<{ body?: Record<string, unknown>;
|
|
|
939
986
|
organizationId: interaction.organizationId,
|
|
940
987
|
tenantId: interaction.tenantId,
|
|
941
988
|
},
|
|
989
|
+
syncOrigin: ctx.syncOrigin,
|
|
942
990
|
indexer: interactionCrudIndexer,
|
|
943
991
|
events: interactionCrudEvents,
|
|
944
992
|
})
|
|
@@ -11,10 +11,10 @@ import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
|
11
11
|
import { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'
|
|
12
12
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
13
13
|
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
14
|
-
import { buildCrudExportUrl } from '@open-mercato/ui/backend/utils/crud'
|
|
15
14
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
16
15
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
17
16
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
17
|
+
import { resolveTodoHref } from './detail/utils'
|
|
18
18
|
|
|
19
19
|
type CustomerTodoItem = {
|
|
20
20
|
id: string
|
|
@@ -31,6 +31,8 @@ type CustomerTodoItem = {
|
|
|
31
31
|
organizationId: string
|
|
32
32
|
tenantId: string
|
|
33
33
|
createdAt: string
|
|
34
|
+
externalHref?: string | null
|
|
35
|
+
_integrations?: Record<string, unknown>
|
|
34
36
|
customer: {
|
|
35
37
|
id: string | null
|
|
36
38
|
displayName: string | null
|
|
@@ -47,6 +49,7 @@ type CustomerTodosResponse = {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
const TASKS_TAB_QUERY = 'tab=tasks'
|
|
52
|
+
const CUSTOMER_TASKS_API_PATH = '/api/customers/interactions/tasks'
|
|
50
53
|
|
|
51
54
|
function buildCustomerHref(item: CustomerTodoItem): string | null {
|
|
52
55
|
const customerId = item.customer?.id
|
|
@@ -59,25 +62,31 @@ function buildCustomerHref(item: CustomerTodoItem): string | null {
|
|
|
59
62
|
return `${base}?${TASKS_TAB_QUERY}`
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
function buildCustomerTasksQueryString(input: {
|
|
66
|
+
page: number
|
|
67
|
+
pageSize: number
|
|
68
|
+
search: string
|
|
69
|
+
all?: boolean
|
|
70
|
+
}): string {
|
|
71
|
+
const usp = new URLSearchParams({
|
|
72
|
+
page: String(input.page),
|
|
73
|
+
pageSize: String(input.pageSize),
|
|
74
|
+
})
|
|
75
|
+
if (input.search.trim().length > 0) usp.set('search', input.search.trim())
|
|
76
|
+
if (input.all) usp.set('all', 'true')
|
|
77
|
+
return usp.toString()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readValueAtPath(record: Record<string, unknown>, path: string): unknown {
|
|
81
|
+
const segments = path.split('.').filter((segment) => segment.length > 0)
|
|
82
|
+
let current: unknown = record
|
|
83
|
+
for (const segment of segments) {
|
|
84
|
+
if (!current || typeof current !== 'object') return null
|
|
85
|
+
current = (current as Record<string, unknown>)[segment]
|
|
86
|
+
}
|
|
87
|
+
return current ?? null
|
|
88
|
+
}
|
|
89
|
+
|
|
81
90
|
export function CustomerTodosTable(): React.JSX.Element {
|
|
82
91
|
const t = useT()
|
|
83
92
|
const router = useRouter()
|
|
@@ -87,14 +96,11 @@ export function CustomerTodosTable(): React.JSX.Element {
|
|
|
87
96
|
const [page, setPage] = React.useState(1)
|
|
88
97
|
const [pageSize] = React.useState(50)
|
|
89
98
|
|
|
90
|
-
const params = React.useMemo(() => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (search.trim().length > 0) usp.set('search', search.trim())
|
|
96
|
-
return usp.toString()
|
|
97
|
-
}, [page, pageSize, search])
|
|
99
|
+
const params = React.useMemo(() => buildCustomerTasksQueryString({
|
|
100
|
+
page,
|
|
101
|
+
pageSize,
|
|
102
|
+
search,
|
|
103
|
+
}), [page, pageSize, search])
|
|
98
104
|
|
|
99
105
|
const columns = React.useMemo<ColumnDef<CustomerTodoItem>[]>(() => [
|
|
100
106
|
{
|
|
@@ -118,10 +124,10 @@ export function CustomerTodosTable(): React.JSX.Element {
|
|
|
118
124
|
header: t('customers.workPlan.customerTodos.table.column.todo'),
|
|
119
125
|
cell: ({ row }) => {
|
|
120
126
|
const title = row.original.todoTitle ?? t('customers.workPlan.customerTodos.table.column.todo.unnamed')
|
|
121
|
-
const
|
|
122
|
-
if (!
|
|
127
|
+
const todoHref = row.original.externalHref ?? resolveTodoHref(row.original.todoSource, row.original.todoId)
|
|
128
|
+
if (!todoHref) return <span className="text-muted-foreground">{title}</span>
|
|
123
129
|
return (
|
|
124
|
-
<Link href={
|
|
130
|
+
<Link href={todoHref} className="underline-offset-2 hover:underline">
|
|
125
131
|
{title}
|
|
126
132
|
</Link>
|
|
127
133
|
)
|
|
@@ -150,15 +156,30 @@ export function CustomerTodosTable(): React.JSX.Element {
|
|
|
150
156
|
.filter((col): col is { field: string; header: string } => !!col)
|
|
151
157
|
}, [columns])
|
|
152
158
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
const buildPreparedExport = React.useCallback((
|
|
160
|
+
exportRows: CustomerTodoItem[],
|
|
161
|
+
exportColumns: Array<{ field: string; header: string }>,
|
|
162
|
+
): PreparedExport => ({
|
|
163
|
+
columns: exportColumns.map((col) => ({ field: col.field, header: col.header })),
|
|
164
|
+
rows: exportRows.map((row) => {
|
|
165
|
+
const record = row as Record<string, unknown>
|
|
166
|
+
return Object.fromEntries(
|
|
167
|
+
exportColumns.map((col) => [col.field, readValueAtPath(record, col.field)]),
|
|
160
168
|
)
|
|
161
|
-
},
|
|
169
|
+
}),
|
|
170
|
+
}), [])
|
|
171
|
+
|
|
172
|
+
const fetchTasks = React.useCallback(async (queryString: string): Promise<CustomerTodosResponse> => {
|
|
173
|
+
return readApiResultOrThrow<CustomerTodosResponse>(
|
|
174
|
+
`${CUSTOMER_TASKS_API_PATH}?${queryString}`,
|
|
175
|
+
undefined,
|
|
176
|
+
{ errorMessage: t('customers.workPlan.customerTodos.table.error.load') },
|
|
177
|
+
)
|
|
178
|
+
}, [t])
|
|
179
|
+
|
|
180
|
+
const { data, isLoading, error, refetch, isFetching } = useQuery<CustomerTodosResponse>({
|
|
181
|
+
queryKey: ['customers-interactions-tasks', params, scopeVersion],
|
|
182
|
+
queryFn: async () => fetchTasks(params),
|
|
162
183
|
placeholderData: keepPreviousData,
|
|
163
184
|
})
|
|
164
185
|
|
|
@@ -168,27 +189,28 @@ export function CustomerTodosTable(): React.JSX.Element {
|
|
|
168
189
|
view: {
|
|
169
190
|
description: t('customers.workPlan.customerTodos.table.export.view'),
|
|
170
191
|
prepare: async (): Promise<{ prepared: PreparedExport; filename: string }> => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
out[col.field] = (row as Record<string, unknown>)[col.field]
|
|
175
|
-
}
|
|
176
|
-
return out
|
|
177
|
-
})
|
|
178
|
-
const prepared: PreparedExport = {
|
|
179
|
-
columns: viewExportColumns.map((col) => ({ field: col.field, header: col.header })),
|
|
180
|
-
rows: rowsForExport,
|
|
192
|
+
return {
|
|
193
|
+
prepared: buildPreparedExport(rows, viewExportColumns),
|
|
194
|
+
filename: 'customer_todos_view',
|
|
181
195
|
}
|
|
182
|
-
return { prepared, filename: 'customer_todos_view' }
|
|
183
196
|
},
|
|
184
197
|
},
|
|
185
198
|
full: {
|
|
186
199
|
description: t('customers.workPlan.customerTodos.table.export.full'),
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
prepare: async (_format: DataTableExportFormat): Promise<{ prepared: PreparedExport; filename: string }> => {
|
|
201
|
+
const fullData = await fetchTasks(buildCustomerTasksQueryString({
|
|
202
|
+
page: 1,
|
|
203
|
+
pageSize,
|
|
204
|
+
search,
|
|
205
|
+
all: true,
|
|
206
|
+
}))
|
|
207
|
+
return {
|
|
208
|
+
prepared: buildPreparedExport(fullData.items, viewExportColumns),
|
|
209
|
+
filename: 'customer_todos_full',
|
|
210
|
+
}
|
|
211
|
+
},
|
|
190
212
|
},
|
|
191
|
-
}), [rows, t, viewExportColumns])
|
|
213
|
+
}), [buildPreparedExport, fetchTasks, pageSize, rows, search, t, viewExportColumns])
|
|
192
214
|
|
|
193
215
|
const handleRefresh = React.useCallback(async () => {
|
|
194
216
|
try {
|
|
@@ -234,18 +256,21 @@ export function CustomerTodosTable(): React.JSX.Element {
|
|
|
234
256
|
perspective={{ tableId: 'customers.todos.list' }}
|
|
235
257
|
rowActions={(row) => {
|
|
236
258
|
const customerLink = buildCustomerHref(row)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
259
|
+
const todoHref = row.externalHref ?? resolveTodoHref(row.todoSource, row.todoId)
|
|
260
|
+
const items = [
|
|
261
|
+
customerLink ? {
|
|
262
|
+
id: 'open-customer',
|
|
263
|
+
label: t('customers.workPlan.customerTodos.table.actions.openCustomer'),
|
|
264
|
+
href: customerLink,
|
|
265
|
+
} : null,
|
|
266
|
+
todoHref ? {
|
|
267
|
+
id: 'open-task',
|
|
268
|
+
label: t('customers.workPlan.customerTodos.table.actions.openTask'),
|
|
269
|
+
href: todoHref,
|
|
270
|
+
} : null,
|
|
271
|
+
].filter((item): item is { id: string; label: string; href: string } => !!item)
|
|
272
|
+
if (!items.length) return null
|
|
273
|
+
return <RowActions items={items} />
|
|
249
274
|
}}
|
|
250
275
|
onRowClick={handleNavigate}
|
|
251
276
|
pagination={{
|
|
@@ -449,7 +449,7 @@ export function TasksSection({
|
|
|
449
449
|
</div>
|
|
450
450
|
) : null}
|
|
451
451
|
{sortedTasks.map((task) => {
|
|
452
|
-
const todoHref = resolveTodoHref(task.todoSource, task.todoId)
|
|
452
|
+
const todoHref = task.externalHref ?? resolveTodoHref(task.todoSource, task.todoId)
|
|
453
453
|
const createdLabel = formatDateTime(task.createdAt) ?? emptyLabel
|
|
454
454
|
const meta = renderTaskMeta(task)
|
|
455
455
|
const title = task.title ?? t('customers.people.detail.tasks.untitled', 'Untitled task')
|
|
@@ -570,15 +570,15 @@ export function TasksSection({
|
|
|
570
570
|
{t('customers.people.detail.tasks.loadingMore', 'Loading…')}
|
|
571
571
|
</div>
|
|
572
572
|
) : null}
|
|
573
|
-
<div className="flex justify-center">
|
|
574
|
-
<Button asChild variant="outline" size="sm">
|
|
575
|
-
<Link href="/backend/customer-tasks">
|
|
576
|
-
{t('customers.people.detail.tasks.viewAll', 'View all tasks')}
|
|
577
|
-
</Link>
|
|
578
|
-
</Button>
|
|
579
|
-
</div>
|
|
580
573
|
</div>
|
|
581
574
|
) : null}
|
|
575
|
+
<div className="flex justify-center">
|
|
576
|
+
<Button asChild variant="outline" size="sm">
|
|
577
|
+
<Link href="/backend/customer-tasks">
|
|
578
|
+
{t('customers.people.detail.tasks.viewAll', 'View all tasks')}
|
|
579
|
+
</Link>
|
|
580
|
+
</Button>
|
|
581
|
+
</div>
|
|
582
582
|
</div>
|
|
583
583
|
|
|
584
584
|
<TaskDialog
|
|
@@ -6,8 +6,9 @@ import { resolveTodoApiPath } from '../utils'
|
|
|
6
6
|
import type { TodoLinkSummary } from '../types'
|
|
7
7
|
import { generateTempId } from '@open-mercato/core/modules/customers/lib/detailHelpers'
|
|
8
8
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
9
|
+
import { CUSTOMER_INTERACTION_TASK_SOURCE } from '../../../lib/interactionCompatibility'
|
|
9
10
|
|
|
10
|
-
const DEFAULT_TODO_SOURCE =
|
|
11
|
+
const DEFAULT_TODO_SOURCE = CUSTOMER_INTERACTION_TASK_SOURCE
|
|
11
12
|
|
|
12
13
|
type CustomerTodoRow = {
|
|
13
14
|
id: string
|
|
@@ -109,6 +109,7 @@ export type TodoLinkSummary = {
|
|
|
109
109
|
dueAt?: string | null
|
|
110
110
|
todoOrganizationId?: string | null
|
|
111
111
|
customValues?: Record<string, unknown> | null
|
|
112
|
+
externalHref?: string | null
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
export type InteractionSummary = {
|
|
@@ -133,6 +134,11 @@ export type InteractionSummary = {
|
|
|
133
134
|
authorEmail?: string | null
|
|
134
135
|
dealTitle?: string | null
|
|
135
136
|
customValues?: Record<string, unknown> | null
|
|
137
|
+
customer?: {
|
|
138
|
+
id: string | null
|
|
139
|
+
displayName: string | null
|
|
140
|
+
kind: string | null
|
|
141
|
+
} | null
|
|
136
142
|
_integrations?: Record<string, unknown>
|
|
137
143
|
createdAt: string
|
|
138
144
|
updatedAt: string
|
|
@@ -39,6 +39,9 @@ export function resolveTodoHref(source: string, todoId: string | null | undefine
|
|
|
39
39
|
if (source === CUSTOMER_INTERACTION_TASK_SOURCE || source === CUSTOMER_INTERACTION_TASK_TYPE) return null
|
|
40
40
|
const [module] = source.split(':')
|
|
41
41
|
if (!module) return null
|
|
42
|
+
if (module === 'example') {
|
|
43
|
+
return `/backend/todos/${encodeURIComponent(todoId)}/edit`
|
|
44
|
+
}
|
|
42
45
|
return `/backend/${module}/todos/${encodeURIComponent(todoId)}/edit`
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -809,8 +809,8 @@ export class CustomerTodoLink {
|
|
|
809
809
|
@Property({ name: 'todo_id', type: 'uuid' })
|
|
810
810
|
todoId!: string
|
|
811
811
|
|
|
812
|
-
@Property({ name: 'todo_source', type: 'text', default: '
|
|
813
|
-
todoSource: string = '
|
|
812
|
+
@Property({ name: 'todo_source', type: 'text', default: 'customers:interaction' })
|
|
813
|
+
todoSource: string = 'customers:interaction'
|
|
814
814
|
|
|
815
815
|
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
816
816
|
createdAt: Date = new Date()
|
|
@@ -282,7 +282,7 @@ export const tagAssignmentSchema = scopedSchema.extend({
|
|
|
282
282
|
export const todoLinkCreateSchema = scopedSchema.extend({
|
|
283
283
|
entityId: uuid(),
|
|
284
284
|
todoId: uuid(),
|
|
285
|
-
todoSource: z.string().min(1).max(120).default('
|
|
285
|
+
todoSource: z.string().min(1).max(120).default('customers:interaction'),
|
|
286
286
|
createdByUserId: uuid().optional(),
|
|
287
287
|
})
|
|
288
288
|
|
|
@@ -291,7 +291,7 @@ export const todoLinkWithTodoCreateSchema = scopedSchema.extend({
|
|
|
291
291
|
title: z.string().min(1).max(200),
|
|
292
292
|
isDone: z.boolean().optional(),
|
|
293
293
|
is_done: z.boolean().optional(),
|
|
294
|
-
todoSource: z.string().min(1).max(120).default('
|
|
294
|
+
todoSource: z.string().min(1).max(120).default('customers:interaction'),
|
|
295
295
|
createdByUserId: uuid().optional(),
|
|
296
296
|
todoCustom: z.record(z.string(), z.any()).optional(),
|
|
297
297
|
custom: z.record(z.string(), z.any()).optional(),
|
|
@@ -5,6 +5,7 @@ export const CUSTOMER_INTERACTION_TASK_SOURCE = 'customers:interaction'
|
|
|
5
5
|
export const CUSTOMER_INTERACTION_TASK_TYPE = 'task'
|
|
6
6
|
export const CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE = 'adapter:activity'
|
|
7
7
|
export const CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE = 'adapter:todo'
|
|
8
|
+
export const EXAMPLE_TODO_SOURCE = 'example:todo'
|
|
8
9
|
|
|
9
10
|
export type InteractionRecord = InteractionSummary & {
|
|
10
11
|
authorName?: string | null
|
|
@@ -48,6 +49,7 @@ export function mapInteractionRecordToActivitySummary(interaction: InteractionRe
|
|
|
48
49
|
|
|
49
50
|
export function mapInteractionRecordToTodoSummary(interaction: InteractionRecord): TodoLinkSummary {
|
|
50
51
|
const customValues: Record<string, unknown> = { ...(interaction.customValues ?? {}) }
|
|
52
|
+
const externalHref = resolveExampleIntegrationHref(interaction)
|
|
51
53
|
if (interaction.priority !== undefined) customValues.priority = interaction.priority
|
|
52
54
|
if (interaction.body !== undefined && customValues.description === undefined) {
|
|
53
55
|
customValues.description = interaction.body ?? null
|
|
@@ -73,5 +75,19 @@ export function mapInteractionRecordToTodoSummary(interaction: InteractionRecord
|
|
|
73
75
|
dueAt: interaction.scheduledAt ?? null,
|
|
74
76
|
todoOrganizationId: null,
|
|
75
77
|
customValues: Object.keys(customValues).length > 0 ? customValues : null,
|
|
78
|
+
externalHref,
|
|
76
79
|
}
|
|
77
80
|
}
|
|
81
|
+
|
|
82
|
+
type IntegrationCarrier = {
|
|
83
|
+
_integrations?: {
|
|
84
|
+
example?: { href?: string | null; syncStatus?: string | null; [key: string]: unknown }
|
|
85
|
+
[key: string]: unknown
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveExampleIntegrationHref(item: IntegrationCarrier): string | null {
|
|
90
|
+
const example = item._integrations?.example
|
|
91
|
+
if (!example || typeof example !== 'object') return null
|
|
92
|
+
return typeof example.href === 'string' && example.href.trim().length > 0 ? example.href : null
|
|
93
|
+
}
|