@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
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Users, Link2, Plus, Filter } from 'lucide-react'
|
|
5
|
+
import { EmptyState } from '@open-mercato/ui/backend/EmptyState'
|
|
6
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
7
|
+
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
8
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
9
|
+
import {
|
|
10
|
+
readVersionedIdSet,
|
|
11
|
+
writeVersionedIdSet,
|
|
12
|
+
} from '@open-mercato/shared/lib/browser/versionedPreference'
|
|
13
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
14
|
+
import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
15
|
+
import type { TabEmptyStateConfig, Translator } from './types'
|
|
16
|
+
import { PersonCard } from './PersonCard'
|
|
17
|
+
import {
|
|
18
|
+
LinkEntityDialog,
|
|
19
|
+
type LinkEntityAdapter,
|
|
20
|
+
type LinkEntityConfirmInput,
|
|
21
|
+
} from '../linking/LinkEntityDialog'
|
|
22
|
+
|
|
23
|
+
type GuardedMutationRunner = <T>(
|
|
24
|
+
operation: () => Promise<T>,
|
|
25
|
+
mutationPayload?: Record<string, unknown>,
|
|
26
|
+
) => Promise<T>
|
|
27
|
+
|
|
28
|
+
export type LinkedPersonSummary = {
|
|
29
|
+
id: string
|
|
30
|
+
displayName: string
|
|
31
|
+
primaryEmail?: string | null
|
|
32
|
+
primaryPhone?: string | null
|
|
33
|
+
status?: string | null
|
|
34
|
+
lifecycleStage?: string | null
|
|
35
|
+
jobTitle?: string | null
|
|
36
|
+
department?: string | null
|
|
37
|
+
createdAt?: string | null
|
|
38
|
+
organizationId?: string | null
|
|
39
|
+
temperature?: string | null
|
|
40
|
+
source?: string | null
|
|
41
|
+
linkedAt?: string | null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type LinkedPeopleSortMode = 'name-asc' | 'name-desc' | 'recent'
|
|
45
|
+
|
|
46
|
+
export const ALL_LINKED_PEOPLE_SORTS: LinkedPeopleSortMode[] = ['name-asc', 'name-desc', 'recent']
|
|
47
|
+
|
|
48
|
+
export type LinkedPeoplePage = {
|
|
49
|
+
items?: LinkedPersonSummary[]
|
|
50
|
+
page?: number
|
|
51
|
+
total?: number
|
|
52
|
+
totalPages?: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type LinkedPeopleSectionProps<
|
|
56
|
+
TDetails = unknown,
|
|
57
|
+
TLinkSettings = Record<string, unknown>,
|
|
58
|
+
> = {
|
|
59
|
+
/** Stable id of the owning record; scopes browser-local preferences such as starred people. */
|
|
60
|
+
scopeId: string
|
|
61
|
+
/** Records the host already knows about, used until the first page load resolves. */
|
|
62
|
+
fallbackPeople?: LinkedPersonSummary[]
|
|
63
|
+
loadPage: (params: {
|
|
64
|
+
page: number
|
|
65
|
+
pageSize: number
|
|
66
|
+
sort: LinkedPeopleSortMode
|
|
67
|
+
search: string
|
|
68
|
+
}) => Promise<LinkedPeoplePage>
|
|
69
|
+
/** Owns messaging and optimistic-lock handling for the unlink write. */
|
|
70
|
+
onUnlink: (personId: string) => Promise<void>
|
|
71
|
+
linkAdapter: LinkEntityAdapter<TDetails, TLinkSettings>
|
|
72
|
+
/** Controlled link-dialog state; falls back to internal state when omitted. */
|
|
73
|
+
linkDialogOpen?: boolean
|
|
74
|
+
onLinkDialogOpenChange?: (open: boolean) => void
|
|
75
|
+
linkedIds?: string[]
|
|
76
|
+
/** Owns messaging for the link write; rejecting keeps the link dialog open. */
|
|
77
|
+
onLinkConfirm: (input: LinkEntityConfirmInput<TLinkSettings>) => Promise<void>
|
|
78
|
+
sectionTitle: string
|
|
79
|
+
sectionSubtitle?: string
|
|
80
|
+
searchPlaceholder: string
|
|
81
|
+
linkActionLabel: string
|
|
82
|
+
addActionLabel?: string
|
|
83
|
+
onAddPerson?: () => void
|
|
84
|
+
emptyLabel: string
|
|
85
|
+
emptyState: TabEmptyStateConfig
|
|
86
|
+
onEmptyStateAction?: () => void
|
|
87
|
+
/**
|
|
88
|
+
* Which sort options to offer, in order. Defaults to all three.
|
|
89
|
+
*
|
|
90
|
+
* A host whose `linkedAt` is not trustworthy must omit `recent`: the deal side does so until
|
|
91
|
+
* `syncDealPeople` stops re-dating every surviving link on each write, because the ordering
|
|
92
|
+
* would otherwise collapse into an arbitrary label tie-break.
|
|
93
|
+
*/
|
|
94
|
+
availableSorts?: LinkedPeopleSortMode[]
|
|
95
|
+
/** Forwarded to `PersonCard`; a host without a durable `linkedAt` passes `false`. */
|
|
96
|
+
showLinkedDate?: boolean
|
|
97
|
+
/** Bump to force a reload after the host mutates the links itself. */
|
|
98
|
+
refreshKey?: number
|
|
99
|
+
disabled?: boolean
|
|
100
|
+
translator?: Translator
|
|
101
|
+
runGuardedMutation?: GuardedMutationRunner
|
|
102
|
+
onLoadingChange?: (isLoading: boolean) => void
|
|
103
|
+
header?: React.ReactNode
|
|
104
|
+
renderFooter?: (ctx: {
|
|
105
|
+
people: LinkedPersonSummary[]
|
|
106
|
+
starredIds: Set<string>
|
|
107
|
+
}) => React.ReactNode
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const LINKED_PEOPLE_PAGE_SIZE = 20
|
|
111
|
+
const STARRED_PEOPLE_STORAGE_VERSION = 1
|
|
112
|
+
|
|
113
|
+
const SORT_LABELS: Record<LinkedPeopleSortMode, { key: string; fallback: string }> = {
|
|
114
|
+
'name-asc': { key: 'customers.linkedPeople.sortNameAsc', fallback: 'Sort: Name A-Z' },
|
|
115
|
+
'name-desc': { key: 'customers.linkedPeople.sortNameDesc', fallback: 'Sort: Name Z-A' },
|
|
116
|
+
recent: { key: 'customers.linkedPeople.sortRecent', fallback: 'Sort: Recently linked' },
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function LinkedPeopleSection<
|
|
120
|
+
TDetails = unknown,
|
|
121
|
+
TLinkSettings = Record<string, unknown>,
|
|
122
|
+
>({
|
|
123
|
+
scopeId,
|
|
124
|
+
fallbackPeople,
|
|
125
|
+
loadPage,
|
|
126
|
+
onUnlink,
|
|
127
|
+
linkAdapter,
|
|
128
|
+
linkDialogOpen: controlledLinkDialogOpen,
|
|
129
|
+
onLinkDialogOpenChange,
|
|
130
|
+
linkedIds,
|
|
131
|
+
onLinkConfirm,
|
|
132
|
+
sectionTitle,
|
|
133
|
+
sectionSubtitle,
|
|
134
|
+
searchPlaceholder,
|
|
135
|
+
linkActionLabel,
|
|
136
|
+
addActionLabel,
|
|
137
|
+
onAddPerson,
|
|
138
|
+
emptyLabel,
|
|
139
|
+
emptyState,
|
|
140
|
+
onEmptyStateAction,
|
|
141
|
+
availableSorts = ALL_LINKED_PEOPLE_SORTS,
|
|
142
|
+
showLinkedDate = true,
|
|
143
|
+
refreshKey = 0,
|
|
144
|
+
disabled = false,
|
|
145
|
+
translator,
|
|
146
|
+
runGuardedMutation,
|
|
147
|
+
onLoadingChange,
|
|
148
|
+
header,
|
|
149
|
+
renderFooter,
|
|
150
|
+
}: LinkedPeopleSectionProps<TDetails, TLinkSettings>) {
|
|
151
|
+
const tHook = useT()
|
|
152
|
+
const fallbackTranslator = React.useMemo<Translator>(
|
|
153
|
+
() => createTranslatorWithFallback(tHook),
|
|
154
|
+
[tHook],
|
|
155
|
+
)
|
|
156
|
+
const translate: Translator = translator ?? fallbackTranslator
|
|
157
|
+
const knownPeople = React.useMemo(() => fallbackPeople ?? [], [fallbackPeople])
|
|
158
|
+
const sortOptions = availableSorts.length > 0 ? availableSorts : ALL_LINKED_PEOPLE_SORTS
|
|
159
|
+
|
|
160
|
+
const [removingId, setRemovingId] = React.useState<string | null>(null)
|
|
161
|
+
const [internalLinkDialogOpen, setInternalLinkDialogOpen] = React.useState(false)
|
|
162
|
+
const linkDialogOpen = controlledLinkDialogOpen ?? internalLinkDialogOpen
|
|
163
|
+
const setLinkDialogOpen = React.useCallback(
|
|
164
|
+
(open: boolean) => {
|
|
165
|
+
setInternalLinkDialogOpen(open)
|
|
166
|
+
onLinkDialogOpenChange?.(open)
|
|
167
|
+
},
|
|
168
|
+
[onLinkDialogOpenChange],
|
|
169
|
+
)
|
|
170
|
+
const [searchQuery, setSearchQuery] = React.useState('')
|
|
171
|
+
const [sortMode, setSortMode] = React.useState<LinkedPeopleSortMode>(sortOptions[0])
|
|
172
|
+
const [filtersOpen, setFiltersOpen] = React.useState(true)
|
|
173
|
+
const [visiblePeople, setVisiblePeople] = React.useState<LinkedPersonSummary[]>([])
|
|
174
|
+
const [listPage, setListPage] = React.useState(1)
|
|
175
|
+
const [listTotalPages, setListTotalPages] = React.useState(1)
|
|
176
|
+
const [listTotalCount, setListTotalCount] = React.useState(knownPeople.length)
|
|
177
|
+
const [listLoading, setListLoading] = React.useState(true)
|
|
178
|
+
const [starredIds, setStarredIds] = React.useState<Set<string>>(
|
|
179
|
+
() => readVersionedIdSet(`om:starred-people:${scopeId}`, STARRED_PEOPLE_STORAGE_VERSION),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
const toggleStar = React.useCallback(
|
|
183
|
+
(personId: string) => {
|
|
184
|
+
setStarredIds((prev) => {
|
|
185
|
+
const next = new Set(prev)
|
|
186
|
+
if (next.has(personId)) next.delete(personId)
|
|
187
|
+
else next.add(personId)
|
|
188
|
+
writeVersionedIdSet(`om:starred-people:${scopeId}`, STARRED_PEOPLE_STORAGE_VERSION, next)
|
|
189
|
+
return next
|
|
190
|
+
})
|
|
191
|
+
},
|
|
192
|
+
[scopeId],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
const displayedPeople = React.useMemo(
|
|
196
|
+
() => (visiblePeople.length > 0 ? visiblePeople : knownPeople),
|
|
197
|
+
[knownPeople, visiblePeople],
|
|
198
|
+
)
|
|
199
|
+
const totalLinkedPeople = listTotalCount > 0 ? listTotalCount : displayedPeople.length
|
|
200
|
+
|
|
201
|
+
const loadVisiblePeople = React.useCallback(async () => {
|
|
202
|
+
setListLoading(true)
|
|
203
|
+
try {
|
|
204
|
+
const payload = await loadPage({
|
|
205
|
+
page: listPage,
|
|
206
|
+
pageSize: LINKED_PEOPLE_PAGE_SIZE,
|
|
207
|
+
sort: sortMode,
|
|
208
|
+
search: searchQuery.trim(),
|
|
209
|
+
})
|
|
210
|
+
const nextTotalCount = typeof payload.total === 'number' ? payload.total : 0
|
|
211
|
+
setVisiblePeople(Array.isArray(payload.items) ? payload.items : [])
|
|
212
|
+
setListPage(typeof payload.page === 'number' ? payload.page : listPage)
|
|
213
|
+
setListTotalCount((current) =>
|
|
214
|
+
searchQuery.trim().length > 0 ? Math.max(current, nextTotalCount) : nextTotalCount,
|
|
215
|
+
)
|
|
216
|
+
setListTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)
|
|
217
|
+
} catch {
|
|
218
|
+
setVisiblePeople([])
|
|
219
|
+
if (searchQuery.trim().length === 0) {
|
|
220
|
+
setListTotalCount(0)
|
|
221
|
+
}
|
|
222
|
+
setListTotalPages(1)
|
|
223
|
+
} finally {
|
|
224
|
+
setListLoading(false)
|
|
225
|
+
}
|
|
226
|
+
}, [listPage, loadPage, searchQuery, sortMode])
|
|
227
|
+
|
|
228
|
+
React.useEffect(() => {
|
|
229
|
+
void loadVisiblePeople()
|
|
230
|
+
}, [loadVisiblePeople, refreshKey])
|
|
231
|
+
|
|
232
|
+
React.useEffect(() => {
|
|
233
|
+
setListPage(1)
|
|
234
|
+
}, [searchQuery, sortMode])
|
|
235
|
+
|
|
236
|
+
const handleLinkConfirm = React.useCallback(
|
|
237
|
+
async (input: LinkEntityConfirmInput<TLinkSettings>) => {
|
|
238
|
+
onLoadingChange?.(true)
|
|
239
|
+
try {
|
|
240
|
+
await onLinkConfirm(input)
|
|
241
|
+
await loadVisiblePeople()
|
|
242
|
+
} finally {
|
|
243
|
+
onLoadingChange?.(false)
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
[loadVisiblePeople, onLinkConfirm, onLoadingChange],
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
const handleRemove = React.useCallback(
|
|
250
|
+
async (personId: string) => {
|
|
251
|
+
if (!personId || removingId) return
|
|
252
|
+
setRemovingId(personId)
|
|
253
|
+
onLoadingChange?.(true)
|
|
254
|
+
try {
|
|
255
|
+
await onUnlink(personId)
|
|
256
|
+
await loadVisiblePeople()
|
|
257
|
+
} catch {
|
|
258
|
+
// onUnlink owns user-facing error reporting
|
|
259
|
+
} finally {
|
|
260
|
+
setRemovingId(null)
|
|
261
|
+
onLoadingChange?.(false)
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
[loadVisiblePeople, onLoadingChange, onUnlink, removingId],
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const linkAction = (
|
|
268
|
+
<Button
|
|
269
|
+
type="button"
|
|
270
|
+
variant="outline"
|
|
271
|
+
size="sm"
|
|
272
|
+
onClick={() => setLinkDialogOpen(true)}
|
|
273
|
+
disabled={disabled}
|
|
274
|
+
>
|
|
275
|
+
<Link2 className="mr-1.5 h-4 w-4" />
|
|
276
|
+
{linkActionLabel}
|
|
277
|
+
</Button>
|
|
278
|
+
)
|
|
279
|
+
const addPersonAction = onAddPerson && addActionLabel ? (
|
|
280
|
+
<Button type="button" size="sm" onClick={onAddPerson} disabled={disabled}>
|
|
281
|
+
<Plus className="mr-1.5 h-4 w-4" />
|
|
282
|
+
{addActionLabel}
|
|
283
|
+
</Button>
|
|
284
|
+
) : null
|
|
285
|
+
|
|
286
|
+
const linkDialog = (
|
|
287
|
+
<LinkEntityDialog
|
|
288
|
+
open={linkDialogOpen}
|
|
289
|
+
onOpenChange={setLinkDialogOpen}
|
|
290
|
+
adapter={linkAdapter}
|
|
291
|
+
initialSelectedIds={linkedIds ?? []}
|
|
292
|
+
onConfirm={handleLinkConfirm}
|
|
293
|
+
runGuardedMutation={runGuardedMutation}
|
|
294
|
+
/>
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if (!listLoading && totalLinkedPeople === 0) {
|
|
298
|
+
const emptyStateAction = onEmptyStateAction ?? onAddPerson
|
|
299
|
+
return (
|
|
300
|
+
<>
|
|
301
|
+
<EmptyState
|
|
302
|
+
icon={<Users className="h-10 w-10 text-muted-foreground" />}
|
|
303
|
+
title={emptyState.title}
|
|
304
|
+
actionLabel={emptyStateAction ? emptyState.actionLabel : undefined}
|
|
305
|
+
onAction={emptyStateAction}
|
|
306
|
+
>
|
|
307
|
+
<p className="text-sm text-muted-foreground">{emptyLabel}</p>
|
|
308
|
+
<div className="mt-4">{linkAction}</div>
|
|
309
|
+
</EmptyState>
|
|
310
|
+
{linkDialog}
|
|
311
|
+
</>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<>
|
|
317
|
+
<div className="space-y-4">
|
|
318
|
+
{header}
|
|
319
|
+
|
|
320
|
+
<section className="rounded-lg border bg-card px-4 py-4 sm:px-5">
|
|
321
|
+
<div className="flex flex-col gap-4">
|
|
322
|
+
<div className="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
|
|
323
|
+
<div className="space-y-1">
|
|
324
|
+
<div className="flex items-center gap-2">
|
|
325
|
+
<h3 className="text-base font-semibold">{sectionTitle}</h3>
|
|
326
|
+
<Badge
|
|
327
|
+
variant="secondary"
|
|
328
|
+
className="rounded-full px-2 py-0 text-xs font-semibold"
|
|
329
|
+
>
|
|
330
|
+
{totalLinkedPeople}
|
|
331
|
+
</Badge>
|
|
332
|
+
</div>
|
|
333
|
+
{sectionSubtitle ? (
|
|
334
|
+
<p className="text-xs text-muted-foreground">{sectionSubtitle}</p>
|
|
335
|
+
) : null}
|
|
336
|
+
</div>
|
|
337
|
+
<div className="flex flex-wrap items-center gap-2 xl:justify-end">
|
|
338
|
+
{linkAction}
|
|
339
|
+
{addPersonAction}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
{totalLinkedPeople > 0 ? (
|
|
344
|
+
<div className="flex flex-col gap-3 lg:flex-row lg:items-center">
|
|
345
|
+
{filtersOpen ? (
|
|
346
|
+
<div className="min-w-0 flex-1">
|
|
347
|
+
<Input
|
|
348
|
+
type="text"
|
|
349
|
+
size="lg"
|
|
350
|
+
value={searchQuery}
|
|
351
|
+
onChange={(event) => setSearchQuery(event.target.value)}
|
|
352
|
+
placeholder={searchPlaceholder}
|
|
353
|
+
/>
|
|
354
|
+
</div>
|
|
355
|
+
) : null}
|
|
356
|
+
<div className="flex flex-wrap items-center gap-2 lg:justify-end">
|
|
357
|
+
<Button
|
|
358
|
+
type="button"
|
|
359
|
+
variant="outline"
|
|
360
|
+
size="sm"
|
|
361
|
+
onClick={() => setFiltersOpen((current) => !current)}
|
|
362
|
+
className="h-10"
|
|
363
|
+
>
|
|
364
|
+
<Filter className="mr-1.5 h-4 w-4" />
|
|
365
|
+
{translate('customers.linkedPeople.filter', 'Filters')}
|
|
366
|
+
</Button>
|
|
367
|
+
{/*
|
|
368
|
+
Deliberately a native <select> rather than the DS `Select`. That primitive is
|
|
369
|
+
Radix-based (a button + listbox), and this element is the one the extraction
|
|
370
|
+
gate runs through: `CompanyPeopleSection.test.tsx` must keep passing
|
|
371
|
+
unmodified, and it drives the sort with `getByDisplayValue` + `fireEvent
|
|
372
|
+
.change` — native-select APIs. Swapping it would also change the company
|
|
373
|
+
tab's keyboard and mobile-picker behaviour, which is the one thing this PR
|
|
374
|
+
asserts it does not do. Worth migrating, but as its own change.
|
|
375
|
+
*/}
|
|
376
|
+
{filtersOpen && sortOptions.length > 1 ? (
|
|
377
|
+
<select
|
|
378
|
+
value={sortMode}
|
|
379
|
+
onChange={(event) =>
|
|
380
|
+
setSortMode(event.target.value as LinkedPeopleSortMode)
|
|
381
|
+
}
|
|
382
|
+
className="h-10 min-w-44 rounded-md border border-input bg-background px-3 text-sm focus:outline-none focus:ring-1 focus:ring-ring"
|
|
383
|
+
>
|
|
384
|
+
{sortOptions.map((option) => (
|
|
385
|
+
<option key={option} value={option}>
|
|
386
|
+
{translate(SORT_LABELS[option].key, SORT_LABELS[option].fallback)}
|
|
387
|
+
</option>
|
|
388
|
+
))}
|
|
389
|
+
</select>
|
|
390
|
+
) : null}
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
) : null}
|
|
394
|
+
|
|
395
|
+
{listLoading ? (
|
|
396
|
+
<p className="py-6 text-center text-sm text-muted-foreground">
|
|
397
|
+
{translate('customers.linkedPeople.loading', 'Loading people…')}
|
|
398
|
+
</p>
|
|
399
|
+
) : visiblePeople.length > 0 ? (
|
|
400
|
+
<>
|
|
401
|
+
<div
|
|
402
|
+
className="grid items-start gap-4"
|
|
403
|
+
style={{
|
|
404
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 19.5rem), 1fr))',
|
|
405
|
+
}}
|
|
406
|
+
>
|
|
407
|
+
{visiblePeople.map((person) => (
|
|
408
|
+
<PersonCard
|
|
409
|
+
key={person.id}
|
|
410
|
+
person={person}
|
|
411
|
+
isStarred={starredIds.has(person.id)}
|
|
412
|
+
onToggleStar={toggleStar}
|
|
413
|
+
onUnlink={handleRemove}
|
|
414
|
+
showLinkedDate={showLinkedDate}
|
|
415
|
+
/>
|
|
416
|
+
))}
|
|
417
|
+
</div>
|
|
418
|
+
{listTotalPages > 1 ? (
|
|
419
|
+
<div className="flex items-center justify-between border-t border-border/60 pt-3 text-sm text-muted-foreground">
|
|
420
|
+
<span>
|
|
421
|
+
{translate(
|
|
422
|
+
'customers.linkedPeople.pageSummary',
|
|
423
|
+
'Page {{page}} of {{total}}',
|
|
424
|
+
{
|
|
425
|
+
page: listPage,
|
|
426
|
+
total: listTotalPages,
|
|
427
|
+
},
|
|
428
|
+
)}
|
|
429
|
+
</span>
|
|
430
|
+
<div className="flex items-center gap-2">
|
|
431
|
+
<Button
|
|
432
|
+
type="button"
|
|
433
|
+
variant="outline"
|
|
434
|
+
size="sm"
|
|
435
|
+
onClick={() => setListPage((current) => Math.max(1, current - 1))}
|
|
436
|
+
disabled={listPage <= 1}
|
|
437
|
+
>
|
|
438
|
+
{translate('customers.linkedPeople.previous', 'Previous')}
|
|
439
|
+
</Button>
|
|
440
|
+
<Button
|
|
441
|
+
type="button"
|
|
442
|
+
variant="outline"
|
|
443
|
+
size="sm"
|
|
444
|
+
onClick={() =>
|
|
445
|
+
setListPage((current) => Math.min(listTotalPages, current + 1))
|
|
446
|
+
}
|
|
447
|
+
disabled={listPage >= listTotalPages}
|
|
448
|
+
>
|
|
449
|
+
{translate('customers.linkedPeople.next', 'Next')}
|
|
450
|
+
</Button>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
) : null}
|
|
454
|
+
</>
|
|
455
|
+
) : totalLinkedPeople > 0 ? (
|
|
456
|
+
<p className="py-6 text-center text-sm text-muted-foreground">
|
|
457
|
+
{translate(
|
|
458
|
+
'customers.linkedPeople.noSearchResults',
|
|
459
|
+
'No people match your search.',
|
|
460
|
+
)}
|
|
461
|
+
</p>
|
|
462
|
+
) : null}
|
|
463
|
+
</div>
|
|
464
|
+
</section>
|
|
465
|
+
|
|
466
|
+
{renderFooter?.({ people: displayedPeople, starredIds })}
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
{linkDialog}
|
|
470
|
+
</>
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export default LinkedPeopleSection
|
|
@@ -10,7 +10,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
|
|
|
10
10
|
import { Avatar } from '@open-mercato/ui/primitives/avatar'
|
|
11
11
|
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
12
12
|
import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
|
|
13
|
-
import type {
|
|
13
|
+
import type { LinkedPersonSummary } from './LinkedPeopleSection'
|
|
14
14
|
import { formatDate, formatFallbackLabel } from './utils'
|
|
15
15
|
|
|
16
16
|
const sourceColorMap: Record<string, string> = {
|
|
@@ -52,20 +52,28 @@ function splitSourceTags(source: string | null | undefined): string[] {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
interface PersonCardProps {
|
|
55
|
-
person:
|
|
55
|
+
person: LinkedPersonSummary
|
|
56
56
|
isStarred?: boolean
|
|
57
57
|
onToggleStar?: (personId: string) => void
|
|
58
58
|
onUnlink?: (personId: string) => void
|
|
59
|
+
/**
|
|
60
|
+
* Render the "Linked {date}" line. Defaults to `true`; a host whose `linkedAt` is not
|
|
61
|
+
* durable passes `false` rather than showing a date that is wrong after the first write.
|
|
62
|
+
*/
|
|
63
|
+
showLinkedDate?: boolean
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
export function PersonCard({ person, isStarred, onToggleStar, onUnlink }: PersonCardProps) {
|
|
66
|
+
export function PersonCard({ person, isStarred, onToggleStar, onUnlink, showLinkedDate = true }: PersonCardProps) {
|
|
62
67
|
const t = useT()
|
|
63
68
|
const sourceTags = React.useMemo(() => splitSourceTags(person.source), [person.source])
|
|
64
69
|
const temperature = React.useMemo(() => {
|
|
65
70
|
const value = typeof person.temperature === 'string' ? person.temperature.trim().toLowerCase() : ''
|
|
66
71
|
return value in temperatureConfig ? temperatureConfig[value as keyof typeof temperatureConfig] : null
|
|
67
72
|
}, [person.temperature])
|
|
68
|
-
const linkedDate = React.useMemo(
|
|
73
|
+
const linkedDate = React.useMemo(
|
|
74
|
+
() => (showLinkedDate ? formatDate(person.linkedAt ?? person.createdAt) : null),
|
|
75
|
+
[person.createdAt, person.linkedAt, showLinkedDate],
|
|
76
|
+
)
|
|
69
77
|
|
|
70
78
|
return (
|
|
71
79
|
<div className="min-w-0 overflow-hidden rounded-lg border bg-card p-4">
|
|
@@ -34,6 +34,7 @@ type PersonAdapterOptions = {
|
|
|
34
34
|
selectedEmptyHint: string
|
|
35
35
|
confirmButtonLabel: string
|
|
36
36
|
excludeLinkedCompanyId?: string
|
|
37
|
+
excludeLinkedDealId?: string
|
|
37
38
|
excludeIds?: string[]
|
|
38
39
|
defaultAvatarIcon?: React.ReactNode
|
|
39
40
|
pageSize?: number
|
|
@@ -258,6 +259,9 @@ export function createPersonLinkAdapter(
|
|
|
258
259
|
if (options.excludeLinkedCompanyId) {
|
|
259
260
|
params.set('excludeLinkedCompanyId', options.excludeLinkedCompanyId)
|
|
260
261
|
}
|
|
262
|
+
if (options.excludeLinkedDealId) {
|
|
263
|
+
params.set('excludeLinkedDealId', options.excludeLinkedDealId)
|
|
264
|
+
}
|
|
261
265
|
const payload = await readApiResultOrThrow<Record<string, unknown>>(
|
|
262
266
|
`/api/customers/people?${params.toString()}`,
|
|
263
267
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Collection, OptionalProps } from '@mikro-orm/core'
|
|
2
2
|
import { Entity, Index, ManyToOne, OneToMany, OneToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
|
|
3
3
|
import type { DictionaryEntrySortMode } from '@open-mercato/core/modules/dictionaries/lib/entrySort'
|
|
4
|
+
import type { CalendarEventTypeBehavior } from '../calendar-event-types'
|
|
4
5
|
|
|
5
6
|
export type CustomerEntityKind = 'person' | 'company'
|
|
6
7
|
export type CustomerAddressFormat = 'line_first' | 'street_first'
|
|
@@ -571,7 +572,7 @@ export class CustomerActivity {
|
|
|
571
572
|
`create index "customer_interactions_email_channel_idx" on "customer_interactions" ("channel_id", "entity_id") where "interaction_type" = 'email' and "channel_id" is not null and "deleted_at" is null`,
|
|
572
573
|
})
|
|
573
574
|
export class CustomerInteraction {
|
|
574
|
-
[OptionalProps]?: 'status' | 'pinned' | 'createdAt' | 'updatedAt' | 'deletedAt' | 'durationMinutes' | 'location' | 'allDay' | 'recurrenceRule' | 'recurrenceEnd' | 'participants' | 'reminderMinutes' | 'visibility' | 'linkedEntities' | 'guestPermissions' | 'externalMessageId' | 'channelProviderKey' | 'channelId'
|
|
575
|
+
[OptionalProps]?: 'status' | 'pinned' | 'createdAt' | 'updatedAt' | 'deletedAt' | 'timezone' | 'durationMinutes' | 'location' | 'allDay' | 'recurrenceRule' | 'recurrenceEnd' | 'participants' | 'reminderMinutes' | 'visibility' | 'linkedEntities' | 'guestPermissions' | 'externalMessageId' | 'channelProviderKey' | 'channelId'
|
|
575
576
|
|
|
576
577
|
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
577
578
|
id!: string
|
|
@@ -661,6 +662,9 @@ export class CustomerInteraction {
|
|
|
661
662
|
@Property({ name: 'deal_id', type: 'uuid', nullable: true })
|
|
662
663
|
dealId?: string | null
|
|
663
664
|
|
|
665
|
+
@Property({ name: 'timezone', type: 'text', nullable: true })
|
|
666
|
+
timezone?: string | null
|
|
667
|
+
|
|
664
668
|
@Property({ name: 'duration_minutes', type: 'int', nullable: true })
|
|
665
669
|
durationMinutes?: number | null
|
|
666
670
|
|
|
@@ -953,6 +957,9 @@ export class CustomerDictionaryEntry {
|
|
|
953
957
|
@Property({ type: 'text', nullable: true })
|
|
954
958
|
icon?: string | null
|
|
955
959
|
|
|
960
|
+
@Property({ name: 'activity_type_behavior', type: 'jsonb', nullable: true })
|
|
961
|
+
activityTypeBehavior?: CalendarEventTypeBehavior | null
|
|
962
|
+
|
|
956
963
|
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
957
964
|
createdAt: Date = new Date()
|
|
958
965
|
|