@open-mercato/core 0.9.0 → 0.9.1-develop.7459.1.99e3accd30
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/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +1 -1
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
- package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +52 -17
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
- package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
- package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
- package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
- package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +7 -3
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
- package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonCard.js +5 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
- package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +13 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +9 -8
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/catalog/commands/categories.ts +1 -1
- package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
- package/src/modules/catalog/commands/products.ts +58 -17
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -0
- package/src/modules/communication_channels/i18n/en.json +4 -0
- package/src/modules/communication_channels/i18n/es.json +4 -0
- package/src/modules/communication_channels/i18n/ko.json +4 -0
- package/src/modules/communication_channels/i18n/pl.json +4 -0
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
- package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
- package/src/modules/customers/components/detail/DealForm.tsx +7 -3
- package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
- package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
- package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +104 -8
- package/src/modules/customers/i18n/en.json +104 -8
- package/src/modules/customers/i18n/es.json +104 -8
- package/src/modules/customers/i18n/ko.json +104 -8
- package/src/modules/customers/i18n/pl.json +104 -8
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/messages/subscribers/message-notification.ts +13 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
- package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
- package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
- package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
|
@@ -28,15 +28,30 @@ type GuardedMutationRunner = <T,>(
|
|
|
28
28
|
export type CreatedPersonSummary = {
|
|
29
29
|
id: string
|
|
30
30
|
displayName: string
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Always emitted, but nullable now that the dialog also serves the company-less case.
|
|
33
|
+
*
|
|
34
|
+
* This IS a lost guarantee, not a free widening: on a result type, `string | null` breaks a
|
|
35
|
+
* consumer that previously wrote `const owner: string = created.companyId`. It is safe here
|
|
36
|
+
* only because no in-repo consumer reads either field — the two `onPersonCreated` callers in
|
|
37
|
+
* `CompanyPeopleSection` ignore the argument entirely, and `DealPeopleSection` reads only
|
|
38
|
+
* `id` and `displayName`. Widening a result is not generally safe; do not read this as
|
|
39
|
+
* precedent.
|
|
40
|
+
*/
|
|
41
|
+
companyId: string | null
|
|
42
|
+
companyName: string | null
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
interface CreatePersonDialogProps {
|
|
36
46
|
open: boolean
|
|
37
47
|
onClose: () => void
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
/**
|
|
49
|
+
* When set, the company field is locked to it and the create command links the person on
|
|
50
|
+
* the same request. Omit it for the ordinary editable company picker — the deal side does,
|
|
51
|
+
* and links the new person to the deal itself afterwards.
|
|
52
|
+
*/
|
|
53
|
+
companyId?: string
|
|
54
|
+
companyName?: string
|
|
40
55
|
runGuardedMutation?: GuardedMutationRunner
|
|
41
56
|
onPersonCreated?: (created?: CreatedPersonSummary) => void
|
|
42
57
|
}
|
|
@@ -65,7 +80,7 @@ export function CreatePersonDialog({
|
|
|
65
80
|
return createPersonFormFields(t)
|
|
66
81
|
.filter((field) => field.id !== 'addresses')
|
|
67
82
|
.map((field) => {
|
|
68
|
-
if (field.id !== 'companyEntityId') {
|
|
83
|
+
if (field.id !== 'companyEntityId' || !companyId) {
|
|
69
84
|
return field
|
|
70
85
|
}
|
|
71
86
|
return {
|
|
@@ -90,7 +105,7 @@ export function CreatePersonDialog({
|
|
|
90
105
|
),
|
|
91
106
|
} satisfies CrudField
|
|
92
107
|
})
|
|
93
|
-
}, [companyName, t])
|
|
108
|
+
}, [companyId, companyName, t])
|
|
94
109
|
|
|
95
110
|
const groups = React.useMemo(
|
|
96
111
|
() => createPersonFormGroups(t).filter((group) => group.id !== 'addresses'),
|
|
@@ -98,9 +113,7 @@ export function CreatePersonDialog({
|
|
|
98
113
|
)
|
|
99
114
|
|
|
100
115
|
const initialValues = React.useMemo<Partial<PersonFormValues>>(
|
|
101
|
-
() => ({
|
|
102
|
-
companyEntityId: companyId,
|
|
103
|
-
}),
|
|
116
|
+
() => (companyId ? { companyEntityId: companyId } : {}),
|
|
104
117
|
[companyId],
|
|
105
118
|
)
|
|
106
119
|
|
|
@@ -121,14 +134,19 @@ export function CreatePersonDialog({
|
|
|
121
134
|
? await runGuardedMutation(operation, payload)
|
|
122
135
|
: await operation()
|
|
123
136
|
|
|
124
|
-
flash(
|
|
137
|
+
flash(
|
|
138
|
+
companyId
|
|
139
|
+
? t('customers.people.createDialog.success', 'Person created and linked to company')
|
|
140
|
+
: t('customers.people.createDialog.successPlain', 'Person created'),
|
|
141
|
+
'success',
|
|
142
|
+
)
|
|
125
143
|
const newId = response?.result?.id ?? response?.result?.entityId ?? ''
|
|
126
144
|
const displayNameFromPayload = typeof payload.displayName === 'string' ? payload.displayName : ''
|
|
127
145
|
onPersonCreated?.({
|
|
128
146
|
id: newId,
|
|
129
147
|
displayName: displayNameFromPayload,
|
|
130
|
-
companyId,
|
|
131
|
-
companyName,
|
|
148
|
+
companyId: companyId ?? null,
|
|
149
|
+
companyName: companyName ?? null,
|
|
132
150
|
})
|
|
133
151
|
onClose()
|
|
134
152
|
}, [companyId, companyName, onClose, onPersonCreated, organizationId, runGuardedMutation, t])
|
|
@@ -148,16 +166,18 @@ export function CreatePersonDialog({
|
|
|
148
166
|
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-[1028px]" onKeyDown={handleKeyDown}>
|
|
149
167
|
<DialogHeader>
|
|
150
168
|
<DialogTitle>{t('customers.people.createDialog.title', 'Add new person')}</DialogTitle>
|
|
151
|
-
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
{companyId ? (
|
|
170
|
+
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
|
171
|
+
<Building2 className="size-3.5" />
|
|
172
|
+
<span>{companyName}</span>
|
|
173
|
+
<span className="text-xs">·</span>
|
|
174
|
+
<span>{t('customers.people.createDialog.autoLink', 'auto-linked to company')}</span>
|
|
175
|
+
</div>
|
|
176
|
+
) : null}
|
|
157
177
|
</DialogHeader>
|
|
158
178
|
|
|
159
179
|
<CrudForm<PersonFormValues>
|
|
160
|
-
key={`${companyId}:${formInstanceKey}`}
|
|
180
|
+
key={`${companyId ?? 'no-company'}:${formInstanceKey}`}
|
|
161
181
|
embedded
|
|
162
182
|
entityIds={[E.customers.customer_entity, E.customers.customer_person_profile]}
|
|
163
183
|
fields={fields}
|
|
@@ -1289,8 +1289,12 @@ export function DealForm({
|
|
|
1289
1289
|
ownerUserId: parsed.data.ownerUserId && parsed.data.ownerUserId.length
|
|
1290
1290
|
? parsed.data.ownerUserId
|
|
1291
1291
|
: null,
|
|
1292
|
-
|
|
1293
|
-
|
|
1292
|
+
// Only submit the link lists when this form is actually showing them. The deal
|
|
1293
|
+
// detail page hides the associations group and manages links from its People and
|
|
1294
|
+
// Companies tabs instead, so a Details save there would otherwise write back the
|
|
1295
|
+
// ids this form captured when it mounted — re-linking whoever the user has since
|
|
1296
|
+
// unlinked, and re-dating every surviving link row on the way through.
|
|
1297
|
+
...(showAssociationsGroup ? { personIds, companyIds } : {}),
|
|
1294
1298
|
}
|
|
1295
1299
|
const customEntries = collectCustomFieldValues(values, {
|
|
1296
1300
|
transform: (value) => normalizeCustomFieldSubmitValue(value),
|
|
@@ -1300,7 +1304,7 @@ export function DealForm({
|
|
|
1300
1304
|
setPending(false)
|
|
1301
1305
|
}
|
|
1302
1306
|
},
|
|
1303
|
-
[isSubmitting, onSubmit, pending, t],
|
|
1307
|
+
[isSubmitting, onSubmit, pending, showAssociationsGroup, t],
|
|
1304
1308
|
)
|
|
1305
1309
|
|
|
1306
1310
|
return (
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Users } from 'lucide-react'
|
|
5
|
+
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
8
|
+
import type { TabEmptyStateConfig, Translator } from './types'
|
|
9
|
+
import {
|
|
10
|
+
LinkedPeopleSection,
|
|
11
|
+
type LinkedPeoplePage,
|
|
12
|
+
type LinkedPeopleSortMode,
|
|
13
|
+
type LinkedPersonSummary,
|
|
14
|
+
} from './LinkedPeopleSection'
|
|
15
|
+
import { CreatePersonDialog, type CreatedPersonSummary } from './CreatePersonDialog'
|
|
16
|
+
import { createPersonLinkAdapter } from '../linking/adapters/personAdapter'
|
|
17
|
+
|
|
18
|
+
type GuardedMutationRunner = <T>(
|
|
19
|
+
operation: () => Promise<T>,
|
|
20
|
+
mutationPayload?: Record<string, unknown>,
|
|
21
|
+
) => Promise<T>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `syncDealPeople` still deletes and recreates every link row on a real change, so a surviving
|
|
25
|
+
* participant's `created_at` — surfaced as `linkedAt` — is rewritten by any unlink or inline
|
|
26
|
+
* create. Both of those are exactly what this tab adds, so the two surfaces that read the value
|
|
27
|
+
* are withheld until the set-diff lands: the recency sort would degenerate into an arbitrary
|
|
28
|
+
* label tie-break, and the card would claim a six-month-old contact was linked today.
|
|
29
|
+
*
|
|
30
|
+
* Dropping these two lines is the whole of that follow-up's UI work.
|
|
31
|
+
*/
|
|
32
|
+
const DEAL_SORTS: LinkedPeopleSortMode[] = ['name-asc', 'name-desc']
|
|
33
|
+
const DEAL_SHOWS_LINKED_DATE = false
|
|
34
|
+
|
|
35
|
+
export type DealPeopleSectionProps = {
|
|
36
|
+
dealId: string
|
|
37
|
+
dealName?: string | null
|
|
38
|
+
/** Ids currently linked to the deal; also seeds the link dialog selection. */
|
|
39
|
+
selectedIds: string[]
|
|
40
|
+
/** Persists the whole selection through the deal's optimistic-locked update. */
|
|
41
|
+
onSaveSelection: (nextIds: string[]) => Promise<void>
|
|
42
|
+
fallbackPeople?: LinkedPersonSummary[]
|
|
43
|
+
addActionLabel: string
|
|
44
|
+
disabled?: boolean
|
|
45
|
+
emptyLabel: string
|
|
46
|
+
emptyState: TabEmptyStateConfig
|
|
47
|
+
translator?: Translator
|
|
48
|
+
runGuardedMutation?: GuardedMutationRunner
|
|
49
|
+
onLoadingChange?: (isLoading: boolean) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function DealPeopleSection({
|
|
53
|
+
dealId,
|
|
54
|
+
dealName,
|
|
55
|
+
selectedIds,
|
|
56
|
+
onSaveSelection,
|
|
57
|
+
fallbackPeople,
|
|
58
|
+
addActionLabel,
|
|
59
|
+
disabled = false,
|
|
60
|
+
emptyLabel,
|
|
61
|
+
emptyState,
|
|
62
|
+
translator,
|
|
63
|
+
runGuardedMutation,
|
|
64
|
+
onLoadingChange,
|
|
65
|
+
}: DealPeopleSectionProps) {
|
|
66
|
+
const tHook = useT()
|
|
67
|
+
const fallbackTranslator = React.useMemo<Translator>(
|
|
68
|
+
() => createTranslatorWithFallback(tHook),
|
|
69
|
+
[tHook],
|
|
70
|
+
)
|
|
71
|
+
const translate: Translator = translator ?? fallbackTranslator
|
|
72
|
+
const [createDialogOpen, setCreateDialogOpen] = React.useState(false)
|
|
73
|
+
const [refreshKey, setRefreshKey] = React.useState(0)
|
|
74
|
+
|
|
75
|
+
const loadPage = React.useCallback(
|
|
76
|
+
async ({
|
|
77
|
+
page,
|
|
78
|
+
pageSize,
|
|
79
|
+
sort,
|
|
80
|
+
search,
|
|
81
|
+
}: {
|
|
82
|
+
page: number
|
|
83
|
+
pageSize: number
|
|
84
|
+
sort: LinkedPeopleSortMode
|
|
85
|
+
search: string
|
|
86
|
+
}): Promise<LinkedPeoplePage> => {
|
|
87
|
+
const params = new URLSearchParams({
|
|
88
|
+
page: String(page),
|
|
89
|
+
pageSize: String(pageSize),
|
|
90
|
+
sort,
|
|
91
|
+
})
|
|
92
|
+
if (search.length > 0) {
|
|
93
|
+
params.set('search', search)
|
|
94
|
+
}
|
|
95
|
+
return readApiResultOrThrow<LinkedPeoplePage>(
|
|
96
|
+
`/api/customers/deals/${encodeURIComponent(dealId)}/people?${params.toString()}`,
|
|
97
|
+
undefined,
|
|
98
|
+
{
|
|
99
|
+
errorMessage: translate(
|
|
100
|
+
'customers.deals.detail.people.loadError',
|
|
101
|
+
'Failed to load people.',
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
},
|
|
106
|
+
[dealId, translate],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// Both writes go through the deal's own update rather than a per-link endpoint, so they
|
|
110
|
+
// inherit its optimistic lock and the conflict bar the page already renders on a 409.
|
|
111
|
+
const handleUnlink = React.useCallback(
|
|
112
|
+
async (personId: string) => {
|
|
113
|
+
await onSaveSelection(selectedIds.filter((id) => id !== personId))
|
|
114
|
+
},
|
|
115
|
+
[onSaveSelection, selectedIds],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const handleLinkConfirm = React.useCallback(
|
|
119
|
+
async ({ nextSelectedIds }: { nextSelectedIds: string[] }) => {
|
|
120
|
+
await onSaveSelection(nextSelectedIds)
|
|
121
|
+
},
|
|
122
|
+
[onSaveSelection],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
// The people CRUD route can auto-link a new person to a company, but has no deal equivalent,
|
|
126
|
+
// so the deal link is a follow-up write through the same optimistic-locked selection save.
|
|
127
|
+
const handlePersonCreated = React.useCallback(
|
|
128
|
+
async (created?: CreatedPersonSummary) => {
|
|
129
|
+
const createdId = created?.id?.trim()
|
|
130
|
+
if (!createdId || selectedIds.includes(createdId)) {
|
|
131
|
+
setRefreshKey((current) => current + 1)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await onSaveSelection([...selectedIds, createdId])
|
|
136
|
+
} catch {
|
|
137
|
+
// onSaveSelection has already reported the failure; the person exists and can be
|
|
138
|
+
// linked manually. Callers invoke this fire-and-forget, so swallowing here is what
|
|
139
|
+
// keeps a reported failure from surfacing again as an unhandled rejection.
|
|
140
|
+
}
|
|
141
|
+
setRefreshKey((current) => current + 1)
|
|
142
|
+
},
|
|
143
|
+
[onSaveSelection, selectedIds],
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const personLinkAdapter = React.useMemo(
|
|
147
|
+
() =>
|
|
148
|
+
createPersonLinkAdapter({
|
|
149
|
+
dialogTitle: translate('customers.linking.person.dialogTitle', 'Link person'),
|
|
150
|
+
dialogSubtitle: dealName
|
|
151
|
+
? translate(
|
|
152
|
+
'customers.linking.person.dialogSubtitleFor',
|
|
153
|
+
'Link an existing contact to {{name}}',
|
|
154
|
+
{ name: dealName },
|
|
155
|
+
)
|
|
156
|
+
: translate(
|
|
157
|
+
'customers.deals.detail.people.dialogSubtitle',
|
|
158
|
+
'Link an existing contact to this deal',
|
|
159
|
+
),
|
|
160
|
+
sectionLabel: translate('customers.linking.person.sectionLabel', 'MATCHING CONTACTS'),
|
|
161
|
+
searchPlaceholder: translate(
|
|
162
|
+
'customers.linking.person.searchPlaceholder',
|
|
163
|
+
'Search all people…',
|
|
164
|
+
),
|
|
165
|
+
searchEmptyHint: translate(
|
|
166
|
+
'customers.linking.person.searchEmpty',
|
|
167
|
+
'No matching people found.',
|
|
168
|
+
),
|
|
169
|
+
selectedEmptyHint: translate(
|
|
170
|
+
'customers.linking.person.selectedEmpty',
|
|
171
|
+
'No people selected.',
|
|
172
|
+
),
|
|
173
|
+
confirmButtonLabel: translate('customers.linking.person.confirmButton', 'Link person'),
|
|
174
|
+
defaultAvatarIcon: <Users className="size-4" />,
|
|
175
|
+
excludeLinkedDealId: dealId,
|
|
176
|
+
addNew: {
|
|
177
|
+
title: translate('customers.linking.person.addNew', 'Add new contact'),
|
|
178
|
+
subtitle: translate(
|
|
179
|
+
'customers.deals.detail.people.addNewSubtitle',
|
|
180
|
+
'The new contact is linked to this deal automatically',
|
|
181
|
+
),
|
|
182
|
+
// Hand the new person back through `onCreated` rather than saving here. That is the
|
|
183
|
+
// dialog's own flow: it merges the option into the draft, focuses it and closes just
|
|
184
|
+
// the nested form, so anything the user had already ticked survives and the whole
|
|
185
|
+
// selection is written once, on confirm. Saving and force-closing from here would
|
|
186
|
+
// discard those ticks silently.
|
|
187
|
+
render: ({ onCreated, onCancel }) => (
|
|
188
|
+
<CreatePersonDialog
|
|
189
|
+
open
|
|
190
|
+
onClose={onCancel}
|
|
191
|
+
runGuardedMutation={runGuardedMutation}
|
|
192
|
+
onPersonCreated={(created) => {
|
|
193
|
+
const createdId = created?.id?.trim()
|
|
194
|
+
if (!createdId) {
|
|
195
|
+
onCancel()
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
onCreated({ id: createdId, label: created?.displayName || createdId })
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
[dealId, dealName, runGuardedMutation, translate],
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<>
|
|
209
|
+
<LinkedPeopleSection
|
|
210
|
+
scopeId={dealId}
|
|
211
|
+
fallbackPeople={fallbackPeople}
|
|
212
|
+
loadPage={loadPage}
|
|
213
|
+
onUnlink={handleUnlink}
|
|
214
|
+
linkAdapter={personLinkAdapter}
|
|
215
|
+
linkedIds={selectedIds}
|
|
216
|
+
onLinkConfirm={handleLinkConfirm}
|
|
217
|
+
refreshKey={refreshKey}
|
|
218
|
+
availableSorts={DEAL_SORTS}
|
|
219
|
+
showLinkedDate={DEAL_SHOWS_LINKED_DATE}
|
|
220
|
+
addActionLabel={addActionLabel}
|
|
221
|
+
onAddPerson={() => setCreateDialogOpen(true)}
|
|
222
|
+
sectionTitle={translate('customers.deals.detail.people.sectionTitle', 'People')}
|
|
223
|
+
sectionSubtitle={translate(
|
|
224
|
+
'customers.deals.detail.people.sectionSubtitle',
|
|
225
|
+
'Contacts involved in this deal',
|
|
226
|
+
)}
|
|
227
|
+
searchPlaceholder={translate(
|
|
228
|
+
'customers.deals.detail.people.searchPlaceholder',
|
|
229
|
+
'Search by name, role, email...',
|
|
230
|
+
)}
|
|
231
|
+
linkActionLabel={translate(
|
|
232
|
+
'customers.deals.detail.people.linkAction',
|
|
233
|
+
'Link existing person',
|
|
234
|
+
)}
|
|
235
|
+
emptyLabel={emptyLabel}
|
|
236
|
+
emptyState={emptyState}
|
|
237
|
+
disabled={disabled}
|
|
238
|
+
translator={translate}
|
|
239
|
+
runGuardedMutation={runGuardedMutation}
|
|
240
|
+
onLoadingChange={onLoadingChange}
|
|
241
|
+
/>
|
|
242
|
+
|
|
243
|
+
<CreatePersonDialog
|
|
244
|
+
open={createDialogOpen}
|
|
245
|
+
onClose={() => setCreateDialogOpen(false)}
|
|
246
|
+
runGuardedMutation={runGuardedMutation}
|
|
247
|
+
onPersonCreated={(created) => {
|
|
248
|
+
setCreateDialogOpen(false)
|
|
249
|
+
void handlePersonCreated(created)
|
|
250
|
+
}}
|
|
251
|
+
/>
|
|
252
|
+
</>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export default DealPeopleSection
|