@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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/customers/components/detail/DealPeopleSection.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Users } from 'lucide-react'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport type { TabEmptyStateConfig, Translator } from './types'\nimport {\n LinkedPeopleSection,\n type LinkedPeoplePage,\n type LinkedPeopleSortMode,\n type LinkedPersonSummary,\n} from './LinkedPeopleSection'\nimport { CreatePersonDialog, type CreatedPersonSummary } from './CreatePersonDialog'\nimport { createPersonLinkAdapter } from '../linking/adapters/personAdapter'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\n/**\n * `syncDealPeople` still deletes and recreates every link row on a real change, so a surviving\n * participant's `created_at` \u2014 surfaced as `linkedAt` \u2014 is rewritten by any unlink or inline\n * create. Both of those are exactly what this tab adds, so the two surfaces that read the value\n * are withheld until the set-diff lands: the recency sort would degenerate into an arbitrary\n * label tie-break, and the card would claim a six-month-old contact was linked today.\n *\n * Dropping these two lines is the whole of that follow-up's UI work.\n */\nconst DEAL_SORTS: LinkedPeopleSortMode[] = ['name-asc', 'name-desc']\nconst DEAL_SHOWS_LINKED_DATE = false\n\nexport type DealPeopleSectionProps = {\n dealId: string\n dealName?: string | null\n /** Ids currently linked to the deal; also seeds the link dialog selection. */\n selectedIds: string[]\n /** Persists the whole selection through the deal's optimistic-locked update. */\n onSaveSelection: (nextIds: string[]) => Promise<void>\n fallbackPeople?: LinkedPersonSummary[]\n addActionLabel: string\n disabled?: boolean\n emptyLabel: string\n emptyState: TabEmptyStateConfig\n translator?: Translator\n runGuardedMutation?: GuardedMutationRunner\n onLoadingChange?: (isLoading: boolean) => void\n}\n\nexport function DealPeopleSection({\n dealId,\n dealName,\n selectedIds,\n onSaveSelection,\n fallbackPeople,\n addActionLabel,\n disabled = false,\n emptyLabel,\n emptyState,\n translator,\n runGuardedMutation,\n onLoadingChange,\n}: DealPeopleSectionProps) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(\n () => createTranslatorWithFallback(tHook),\n [tHook],\n )\n const translate: Translator = translator ?? fallbackTranslator\n const [createDialogOpen, setCreateDialogOpen] = React.useState(false)\n const [refreshKey, setRefreshKey] = React.useState(0)\n\n const loadPage = React.useCallback(\n async ({\n page,\n pageSize,\n sort,\n search,\n }: {\n page: number\n pageSize: number\n sort: LinkedPeopleSortMode\n search: string\n }): Promise<LinkedPeoplePage> => {\n const params = new URLSearchParams({\n page: String(page),\n pageSize: String(pageSize),\n sort,\n })\n if (search.length > 0) {\n params.set('search', search)\n }\n return readApiResultOrThrow<LinkedPeoplePage>(\n `/api/customers/deals/${encodeURIComponent(dealId)}/people?${params.toString()}`,\n undefined,\n {\n errorMessage: translate(\n 'customers.deals.detail.people.loadError',\n 'Failed to load people.',\n ),\n },\n )\n },\n [dealId, translate],\n )\n\n // Both writes go through the deal's own update rather than a per-link endpoint, so they\n // inherit its optimistic lock and the conflict bar the page already renders on a 409.\n const handleUnlink = React.useCallback(\n async (personId: string) => {\n await onSaveSelection(selectedIds.filter((id) => id !== personId))\n },\n [onSaveSelection, selectedIds],\n )\n\n const handleLinkConfirm = React.useCallback(\n async ({ nextSelectedIds }: { nextSelectedIds: string[] }) => {\n await onSaveSelection(nextSelectedIds)\n },\n [onSaveSelection],\n )\n\n // The people CRUD route can auto-link a new person to a company, but has no deal equivalent,\n // so the deal link is a follow-up write through the same optimistic-locked selection save.\n const handlePersonCreated = React.useCallback(\n async (created?: CreatedPersonSummary) => {\n const createdId = created?.id?.trim()\n if (!createdId || selectedIds.includes(createdId)) {\n setRefreshKey((current) => current + 1)\n return\n }\n try {\n await onSaveSelection([...selectedIds, createdId])\n } catch {\n // onSaveSelection has already reported the failure; the person exists and can be\n // linked manually. Callers invoke this fire-and-forget, so swallowing here is what\n // keeps a reported failure from surfacing again as an unhandled rejection.\n }\n setRefreshKey((current) => current + 1)\n },\n [onSaveSelection, selectedIds],\n )\n\n const personLinkAdapter = React.useMemo(\n () =>\n createPersonLinkAdapter({\n dialogTitle: translate('customers.linking.person.dialogTitle', 'Link person'),\n dialogSubtitle: dealName\n ? translate(\n 'customers.linking.person.dialogSubtitleFor',\n 'Link an existing contact to {{name}}',\n { name: dealName },\n )\n : translate(\n 'customers.deals.detail.people.dialogSubtitle',\n 'Link an existing contact to this deal',\n ),\n sectionLabel: translate('customers.linking.person.sectionLabel', 'MATCHING CONTACTS'),\n searchPlaceholder: translate(\n 'customers.linking.person.searchPlaceholder',\n 'Search all people\u2026',\n ),\n searchEmptyHint: translate(\n 'customers.linking.person.searchEmpty',\n 'No matching people found.',\n ),\n selectedEmptyHint: translate(\n 'customers.linking.person.selectedEmpty',\n 'No people selected.',\n ),\n confirmButtonLabel: translate('customers.linking.person.confirmButton', 'Link person'),\n defaultAvatarIcon: <Users className=\"size-4\" />,\n excludeLinkedDealId: dealId,\n addNew: {\n title: translate('customers.linking.person.addNew', 'Add new contact'),\n subtitle: translate(\n 'customers.deals.detail.people.addNewSubtitle',\n 'The new contact is linked to this deal automatically',\n ),\n // Hand the new person back through `onCreated` rather than saving here. That is the\n // dialog's own flow: it merges the option into the draft, focuses it and closes just\n // the nested form, so anything the user had already ticked survives and the whole\n // selection is written once, on confirm. Saving and force-closing from here would\n // discard those ticks silently.\n render: ({ onCreated, onCancel }) => (\n <CreatePersonDialog\n open\n onClose={onCancel}\n runGuardedMutation={runGuardedMutation}\n onPersonCreated={(created) => {\n const createdId = created?.id?.trim()\n if (!createdId) {\n onCancel()\n return\n }\n onCreated({ id: createdId, label: created?.displayName || createdId })\n }}\n />\n ),\n },\n }),\n [dealId, dealName, runGuardedMutation, translate],\n )\n\n return (\n <>\n <LinkedPeopleSection\n scopeId={dealId}\n fallbackPeople={fallbackPeople}\n loadPage={loadPage}\n onUnlink={handleUnlink}\n linkAdapter={personLinkAdapter}\n linkedIds={selectedIds}\n onLinkConfirm={handleLinkConfirm}\n refreshKey={refreshKey}\n availableSorts={DEAL_SORTS}\n showLinkedDate={DEAL_SHOWS_LINKED_DATE}\n addActionLabel={addActionLabel}\n onAddPerson={() => setCreateDialogOpen(true)}\n sectionTitle={translate('customers.deals.detail.people.sectionTitle', 'People')}\n sectionSubtitle={translate(\n 'customers.deals.detail.people.sectionSubtitle',\n 'Contacts involved in this deal',\n )}\n searchPlaceholder={translate(\n 'customers.deals.detail.people.searchPlaceholder',\n 'Search by name, role, email...',\n )}\n linkActionLabel={translate(\n 'customers.deals.detail.people.linkAction',\n 'Link existing person',\n )}\n emptyLabel={emptyLabel}\n emptyState={emptyState}\n disabled={disabled}\n translator={translate}\n runGuardedMutation={runGuardedMutation}\n onLoadingChange={onLoadingChange}\n />\n\n <CreatePersonDialog\n open={createDialogOpen}\n onClose={() => setCreateDialogOpen(false)}\n runGuardedMutation={runGuardedMutation}\n onPersonCreated={(created) => {\n setCreateDialogOpen(false)\n void handlePersonCreated(created)\n }}\n />\n </>\n )\n}\n\nexport default DealPeopleSection\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
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 { PersonCard } from "./PersonCard.js";
|
|
16
|
+
import {
|
|
17
|
+
LinkEntityDialog
|
|
18
|
+
} from "../linking/LinkEntityDialog.js";
|
|
19
|
+
const ALL_LINKED_PEOPLE_SORTS = ["name-asc", "name-desc", "recent"];
|
|
20
|
+
const LINKED_PEOPLE_PAGE_SIZE = 20;
|
|
21
|
+
const STARRED_PEOPLE_STORAGE_VERSION = 1;
|
|
22
|
+
const SORT_LABELS = {
|
|
23
|
+
"name-asc": { key: "customers.linkedPeople.sortNameAsc", fallback: "Sort: Name A-Z" },
|
|
24
|
+
"name-desc": { key: "customers.linkedPeople.sortNameDesc", fallback: "Sort: Name Z-A" },
|
|
25
|
+
recent: { key: "customers.linkedPeople.sortRecent", fallback: "Sort: Recently linked" }
|
|
26
|
+
};
|
|
27
|
+
function LinkedPeopleSection({
|
|
28
|
+
scopeId,
|
|
29
|
+
fallbackPeople,
|
|
30
|
+
loadPage,
|
|
31
|
+
onUnlink,
|
|
32
|
+
linkAdapter,
|
|
33
|
+
linkDialogOpen: controlledLinkDialogOpen,
|
|
34
|
+
onLinkDialogOpenChange,
|
|
35
|
+
linkedIds,
|
|
36
|
+
onLinkConfirm,
|
|
37
|
+
sectionTitle,
|
|
38
|
+
sectionSubtitle,
|
|
39
|
+
searchPlaceholder,
|
|
40
|
+
linkActionLabel,
|
|
41
|
+
addActionLabel,
|
|
42
|
+
onAddPerson,
|
|
43
|
+
emptyLabel,
|
|
44
|
+
emptyState,
|
|
45
|
+
onEmptyStateAction,
|
|
46
|
+
availableSorts = ALL_LINKED_PEOPLE_SORTS,
|
|
47
|
+
showLinkedDate = true,
|
|
48
|
+
refreshKey = 0,
|
|
49
|
+
disabled = false,
|
|
50
|
+
translator,
|
|
51
|
+
runGuardedMutation,
|
|
52
|
+
onLoadingChange,
|
|
53
|
+
header,
|
|
54
|
+
renderFooter
|
|
55
|
+
}) {
|
|
56
|
+
const tHook = useT();
|
|
57
|
+
const fallbackTranslator = React.useMemo(
|
|
58
|
+
() => createTranslatorWithFallback(tHook),
|
|
59
|
+
[tHook]
|
|
60
|
+
);
|
|
61
|
+
const translate = translator ?? fallbackTranslator;
|
|
62
|
+
const knownPeople = React.useMemo(() => fallbackPeople ?? [], [fallbackPeople]);
|
|
63
|
+
const sortOptions = availableSorts.length > 0 ? availableSorts : ALL_LINKED_PEOPLE_SORTS;
|
|
64
|
+
const [removingId, setRemovingId] = React.useState(null);
|
|
65
|
+
const [internalLinkDialogOpen, setInternalLinkDialogOpen] = React.useState(false);
|
|
66
|
+
const linkDialogOpen = controlledLinkDialogOpen ?? internalLinkDialogOpen;
|
|
67
|
+
const setLinkDialogOpen = React.useCallback(
|
|
68
|
+
(open) => {
|
|
69
|
+
setInternalLinkDialogOpen(open);
|
|
70
|
+
onLinkDialogOpenChange?.(open);
|
|
71
|
+
},
|
|
72
|
+
[onLinkDialogOpenChange]
|
|
73
|
+
);
|
|
74
|
+
const [searchQuery, setSearchQuery] = React.useState("");
|
|
75
|
+
const [sortMode, setSortMode] = React.useState(sortOptions[0]);
|
|
76
|
+
const [filtersOpen, setFiltersOpen] = React.useState(true);
|
|
77
|
+
const [visiblePeople, setVisiblePeople] = React.useState([]);
|
|
78
|
+
const [listPage, setListPage] = React.useState(1);
|
|
79
|
+
const [listTotalPages, setListTotalPages] = React.useState(1);
|
|
80
|
+
const [listTotalCount, setListTotalCount] = React.useState(knownPeople.length);
|
|
81
|
+
const [listLoading, setListLoading] = React.useState(true);
|
|
82
|
+
const [starredIds, setStarredIds] = React.useState(
|
|
83
|
+
() => readVersionedIdSet(`om:starred-people:${scopeId}`, STARRED_PEOPLE_STORAGE_VERSION)
|
|
84
|
+
);
|
|
85
|
+
const toggleStar = React.useCallback(
|
|
86
|
+
(personId) => {
|
|
87
|
+
setStarredIds((prev) => {
|
|
88
|
+
const next = new Set(prev);
|
|
89
|
+
if (next.has(personId)) next.delete(personId);
|
|
90
|
+
else next.add(personId);
|
|
91
|
+
writeVersionedIdSet(`om:starred-people:${scopeId}`, STARRED_PEOPLE_STORAGE_VERSION, next);
|
|
92
|
+
return next;
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
[scopeId]
|
|
96
|
+
);
|
|
97
|
+
const displayedPeople = React.useMemo(
|
|
98
|
+
() => visiblePeople.length > 0 ? visiblePeople : knownPeople,
|
|
99
|
+
[knownPeople, visiblePeople]
|
|
100
|
+
);
|
|
101
|
+
const totalLinkedPeople = listTotalCount > 0 ? listTotalCount : displayedPeople.length;
|
|
102
|
+
const loadVisiblePeople = React.useCallback(async () => {
|
|
103
|
+
setListLoading(true);
|
|
104
|
+
try {
|
|
105
|
+
const payload = await loadPage({
|
|
106
|
+
page: listPage,
|
|
107
|
+
pageSize: LINKED_PEOPLE_PAGE_SIZE,
|
|
108
|
+
sort: sortMode,
|
|
109
|
+
search: searchQuery.trim()
|
|
110
|
+
});
|
|
111
|
+
const nextTotalCount = typeof payload.total === "number" ? payload.total : 0;
|
|
112
|
+
setVisiblePeople(Array.isArray(payload.items) ? payload.items : []);
|
|
113
|
+
setListPage(typeof payload.page === "number" ? payload.page : listPage);
|
|
114
|
+
setListTotalCount(
|
|
115
|
+
(current) => searchQuery.trim().length > 0 ? Math.max(current, nextTotalCount) : nextTotalCount
|
|
116
|
+
);
|
|
117
|
+
setListTotalPages(typeof payload.totalPages === "number" ? payload.totalPages : 1);
|
|
118
|
+
} catch {
|
|
119
|
+
setVisiblePeople([]);
|
|
120
|
+
if (searchQuery.trim().length === 0) {
|
|
121
|
+
setListTotalCount(0);
|
|
122
|
+
}
|
|
123
|
+
setListTotalPages(1);
|
|
124
|
+
} finally {
|
|
125
|
+
setListLoading(false);
|
|
126
|
+
}
|
|
127
|
+
}, [listPage, loadPage, searchQuery, sortMode]);
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
void loadVisiblePeople();
|
|
130
|
+
}, [loadVisiblePeople, refreshKey]);
|
|
131
|
+
React.useEffect(() => {
|
|
132
|
+
setListPage(1);
|
|
133
|
+
}, [searchQuery, sortMode]);
|
|
134
|
+
const handleLinkConfirm = React.useCallback(
|
|
135
|
+
async (input) => {
|
|
136
|
+
onLoadingChange?.(true);
|
|
137
|
+
try {
|
|
138
|
+
await onLinkConfirm(input);
|
|
139
|
+
await loadVisiblePeople();
|
|
140
|
+
} finally {
|
|
141
|
+
onLoadingChange?.(false);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
[loadVisiblePeople, onLinkConfirm, onLoadingChange]
|
|
145
|
+
);
|
|
146
|
+
const handleRemove = React.useCallback(
|
|
147
|
+
async (personId) => {
|
|
148
|
+
if (!personId || removingId) return;
|
|
149
|
+
setRemovingId(personId);
|
|
150
|
+
onLoadingChange?.(true);
|
|
151
|
+
try {
|
|
152
|
+
await onUnlink(personId);
|
|
153
|
+
await loadVisiblePeople();
|
|
154
|
+
} catch {
|
|
155
|
+
} finally {
|
|
156
|
+
setRemovingId(null);
|
|
157
|
+
onLoadingChange?.(false);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
[loadVisiblePeople, onLoadingChange, onUnlink, removingId]
|
|
161
|
+
);
|
|
162
|
+
const linkAction = /* @__PURE__ */ jsxs(
|
|
163
|
+
Button,
|
|
164
|
+
{
|
|
165
|
+
type: "button",
|
|
166
|
+
variant: "outline",
|
|
167
|
+
size: "sm",
|
|
168
|
+
onClick: () => setLinkDialogOpen(true),
|
|
169
|
+
disabled,
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ jsx(Link2, { className: "mr-1.5 h-4 w-4" }),
|
|
172
|
+
linkActionLabel
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
const addPersonAction = onAddPerson && addActionLabel ? /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onAddPerson, disabled, children: [
|
|
177
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
178
|
+
addActionLabel
|
|
179
|
+
] }) : null;
|
|
180
|
+
const linkDialog = /* @__PURE__ */ jsx(
|
|
181
|
+
LinkEntityDialog,
|
|
182
|
+
{
|
|
183
|
+
open: linkDialogOpen,
|
|
184
|
+
onOpenChange: setLinkDialogOpen,
|
|
185
|
+
adapter: linkAdapter,
|
|
186
|
+
initialSelectedIds: linkedIds ?? [],
|
|
187
|
+
onConfirm: handleLinkConfirm,
|
|
188
|
+
runGuardedMutation
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
if (!listLoading && totalLinkedPeople === 0) {
|
|
192
|
+
const emptyStateAction = onEmptyStateAction ?? onAddPerson;
|
|
193
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
194
|
+
/* @__PURE__ */ jsxs(
|
|
195
|
+
EmptyState,
|
|
196
|
+
{
|
|
197
|
+
icon: /* @__PURE__ */ jsx(Users, { className: "h-10 w-10 text-muted-foreground" }),
|
|
198
|
+
title: emptyState.title,
|
|
199
|
+
actionLabel: emptyStateAction ? emptyState.actionLabel : void 0,
|
|
200
|
+
onAction: emptyStateAction,
|
|
201
|
+
children: [
|
|
202
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: emptyLabel }),
|
|
203
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4", children: linkAction })
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
linkDialog
|
|
208
|
+
] });
|
|
209
|
+
}
|
|
210
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
211
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
212
|
+
header,
|
|
213
|
+
/* @__PURE__ */ jsx("section", { className: "rounded-lg border bg-card px-4 py-4 sm:px-5", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
214
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between", children: [
|
|
215
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
216
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
217
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: sectionTitle }),
|
|
218
|
+
/* @__PURE__ */ jsx(
|
|
219
|
+
Badge,
|
|
220
|
+
{
|
|
221
|
+
variant: "secondary",
|
|
222
|
+
className: "rounded-full px-2 py-0 text-xs font-semibold",
|
|
223
|
+
children: totalLinkedPeople
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
] }),
|
|
227
|
+
sectionSubtitle ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: sectionSubtitle }) : null
|
|
228
|
+
] }),
|
|
229
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 xl:justify-end", children: [
|
|
230
|
+
linkAction,
|
|
231
|
+
addPersonAction
|
|
232
|
+
] })
|
|
233
|
+
] }),
|
|
234
|
+
totalLinkedPeople > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 lg:flex-row lg:items-center", children: [
|
|
235
|
+
filtersOpen ? /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
|
|
236
|
+
Input,
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
size: "lg",
|
|
240
|
+
value: searchQuery,
|
|
241
|
+
onChange: (event) => setSearchQuery(event.target.value),
|
|
242
|
+
placeholder: searchPlaceholder
|
|
243
|
+
}
|
|
244
|
+
) }) : null,
|
|
245
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 lg:justify-end", children: [
|
|
246
|
+
/* @__PURE__ */ jsxs(
|
|
247
|
+
Button,
|
|
248
|
+
{
|
|
249
|
+
type: "button",
|
|
250
|
+
variant: "outline",
|
|
251
|
+
size: "sm",
|
|
252
|
+
onClick: () => setFiltersOpen((current) => !current),
|
|
253
|
+
className: "h-10",
|
|
254
|
+
children: [
|
|
255
|
+
/* @__PURE__ */ jsx(Filter, { className: "mr-1.5 h-4 w-4" }),
|
|
256
|
+
translate("customers.linkedPeople.filter", "Filters")
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
),
|
|
260
|
+
filtersOpen && sortOptions.length > 1 ? /* @__PURE__ */ jsx(
|
|
261
|
+
"select",
|
|
262
|
+
{
|
|
263
|
+
value: sortMode,
|
|
264
|
+
onChange: (event) => setSortMode(event.target.value),
|
|
265
|
+
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",
|
|
266
|
+
children: sortOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: translate(SORT_LABELS[option].key, SORT_LABELS[option].fallback) }, option))
|
|
267
|
+
}
|
|
268
|
+
) : null
|
|
269
|
+
] })
|
|
270
|
+
] }) : null,
|
|
271
|
+
listLoading ? /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: translate("customers.linkedPeople.loading", "Loading people\u2026") }) : visiblePeople.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
272
|
+
/* @__PURE__ */ jsx(
|
|
273
|
+
"div",
|
|
274
|
+
{
|
|
275
|
+
className: "grid items-start gap-4",
|
|
276
|
+
style: {
|
|
277
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 19.5rem), 1fr))"
|
|
278
|
+
},
|
|
279
|
+
children: visiblePeople.map((person) => /* @__PURE__ */ jsx(
|
|
280
|
+
PersonCard,
|
|
281
|
+
{
|
|
282
|
+
person,
|
|
283
|
+
isStarred: starredIds.has(person.id),
|
|
284
|
+
onToggleStar: toggleStar,
|
|
285
|
+
onUnlink: handleRemove,
|
|
286
|
+
showLinkedDate
|
|
287
|
+
},
|
|
288
|
+
person.id
|
|
289
|
+
))
|
|
290
|
+
}
|
|
291
|
+
),
|
|
292
|
+
listTotalPages > 1 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border/60 pt-3 text-sm text-muted-foreground", children: [
|
|
293
|
+
/* @__PURE__ */ jsx("span", { children: translate(
|
|
294
|
+
"customers.linkedPeople.pageSummary",
|
|
295
|
+
"Page {{page}} of {{total}}",
|
|
296
|
+
{
|
|
297
|
+
page: listPage,
|
|
298
|
+
total: listTotalPages
|
|
299
|
+
}
|
|
300
|
+
) }),
|
|
301
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
302
|
+
/* @__PURE__ */ jsx(
|
|
303
|
+
Button,
|
|
304
|
+
{
|
|
305
|
+
type: "button",
|
|
306
|
+
variant: "outline",
|
|
307
|
+
size: "sm",
|
|
308
|
+
onClick: () => setListPage((current) => Math.max(1, current - 1)),
|
|
309
|
+
disabled: listPage <= 1,
|
|
310
|
+
children: translate("customers.linkedPeople.previous", "Previous")
|
|
311
|
+
}
|
|
312
|
+
),
|
|
313
|
+
/* @__PURE__ */ jsx(
|
|
314
|
+
Button,
|
|
315
|
+
{
|
|
316
|
+
type: "button",
|
|
317
|
+
variant: "outline",
|
|
318
|
+
size: "sm",
|
|
319
|
+
onClick: () => setListPage((current) => Math.min(listTotalPages, current + 1)),
|
|
320
|
+
disabled: listPage >= listTotalPages,
|
|
321
|
+
children: translate("customers.linkedPeople.next", "Next")
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
] })
|
|
325
|
+
] }) : null
|
|
326
|
+
] }) : totalLinkedPeople > 0 ? /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: translate(
|
|
327
|
+
"customers.linkedPeople.noSearchResults",
|
|
328
|
+
"No people match your search."
|
|
329
|
+
) }) : null
|
|
330
|
+
] }) }),
|
|
331
|
+
renderFooter?.({ people: displayedPeople, starredIds })
|
|
332
|
+
] }),
|
|
333
|
+
linkDialog
|
|
334
|
+
] });
|
|
335
|
+
}
|
|
336
|
+
var LinkedPeopleSection_default = LinkedPeopleSection;
|
|
337
|
+
export {
|
|
338
|
+
ALL_LINKED_PEOPLE_SORTS,
|
|
339
|
+
LinkedPeopleSection,
|
|
340
|
+
LinkedPeopleSection_default as default
|
|
341
|
+
};
|
|
342
|
+
//# sourceMappingURL=LinkedPeopleSection.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/customers/components/detail/LinkedPeopleSection.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Users, Link2, Plus, Filter } from 'lucide-react'\nimport { EmptyState } from '@open-mercato/ui/backend/EmptyState'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n readVersionedIdSet,\n writeVersionedIdSet,\n} from '@open-mercato/shared/lib/browser/versionedPreference'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport type { TabEmptyStateConfig, Translator } from './types'\nimport { PersonCard } from './PersonCard'\nimport {\n LinkEntityDialog,\n type LinkEntityAdapter,\n type LinkEntityConfirmInput,\n} from '../linking/LinkEntityDialog'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type LinkedPersonSummary = {\n id: string\n displayName: string\n primaryEmail?: string | null\n primaryPhone?: string | null\n status?: string | null\n lifecycleStage?: string | null\n jobTitle?: string | null\n department?: string | null\n createdAt?: string | null\n organizationId?: string | null\n temperature?: string | null\n source?: string | null\n linkedAt?: string | null\n}\n\nexport type LinkedPeopleSortMode = 'name-asc' | 'name-desc' | 'recent'\n\nexport const ALL_LINKED_PEOPLE_SORTS: LinkedPeopleSortMode[] = ['name-asc', 'name-desc', 'recent']\n\nexport type LinkedPeoplePage = {\n items?: LinkedPersonSummary[]\n page?: number\n total?: number\n totalPages?: number\n}\n\nexport type LinkedPeopleSectionProps<\n TDetails = unknown,\n TLinkSettings = Record<string, unknown>,\n> = {\n /** Stable id of the owning record; scopes browser-local preferences such as starred people. */\n scopeId: string\n /** Records the host already knows about, used until the first page load resolves. */\n fallbackPeople?: LinkedPersonSummary[]\n loadPage: (params: {\n page: number\n pageSize: number\n sort: LinkedPeopleSortMode\n search: string\n }) => Promise<LinkedPeoplePage>\n /** Owns messaging and optimistic-lock handling for the unlink write. */\n onUnlink: (personId: string) => Promise<void>\n linkAdapter: LinkEntityAdapter<TDetails, TLinkSettings>\n /** Controlled link-dialog state; falls back to internal state when omitted. */\n linkDialogOpen?: boolean\n onLinkDialogOpenChange?: (open: boolean) => void\n linkedIds?: string[]\n /** Owns messaging for the link write; rejecting keeps the link dialog open. */\n onLinkConfirm: (input: LinkEntityConfirmInput<TLinkSettings>) => Promise<void>\n sectionTitle: string\n sectionSubtitle?: string\n searchPlaceholder: string\n linkActionLabel: string\n addActionLabel?: string\n onAddPerson?: () => void\n emptyLabel: string\n emptyState: TabEmptyStateConfig\n onEmptyStateAction?: () => void\n /**\n * Which sort options to offer, in order. Defaults to all three.\n *\n * A host whose `linkedAt` is not trustworthy must omit `recent`: the deal side does so until\n * `syncDealPeople` stops re-dating every surviving link on each write, because the ordering\n * would otherwise collapse into an arbitrary label tie-break.\n */\n availableSorts?: LinkedPeopleSortMode[]\n /** Forwarded to `PersonCard`; a host without a durable `linkedAt` passes `false`. */\n showLinkedDate?: boolean\n /** Bump to force a reload after the host mutates the links itself. */\n refreshKey?: number\n disabled?: boolean\n translator?: Translator\n runGuardedMutation?: GuardedMutationRunner\n onLoadingChange?: (isLoading: boolean) => void\n header?: React.ReactNode\n renderFooter?: (ctx: {\n people: LinkedPersonSummary[]\n starredIds: Set<string>\n }) => React.ReactNode\n}\n\nconst LINKED_PEOPLE_PAGE_SIZE = 20\nconst STARRED_PEOPLE_STORAGE_VERSION = 1\n\nconst SORT_LABELS: Record<LinkedPeopleSortMode, { key: string; fallback: string }> = {\n 'name-asc': { key: 'customers.linkedPeople.sortNameAsc', fallback: 'Sort: Name A-Z' },\n 'name-desc': { key: 'customers.linkedPeople.sortNameDesc', fallback: 'Sort: Name Z-A' },\n recent: { key: 'customers.linkedPeople.sortRecent', fallback: 'Sort: Recently linked' },\n}\n\nexport function LinkedPeopleSection<\n TDetails = unknown,\n TLinkSettings = Record<string, unknown>,\n>({\n scopeId,\n fallbackPeople,\n loadPage,\n onUnlink,\n linkAdapter,\n linkDialogOpen: controlledLinkDialogOpen,\n onLinkDialogOpenChange,\n linkedIds,\n onLinkConfirm,\n sectionTitle,\n sectionSubtitle,\n searchPlaceholder,\n linkActionLabel,\n addActionLabel,\n onAddPerson,\n emptyLabel,\n emptyState,\n onEmptyStateAction,\n availableSorts = ALL_LINKED_PEOPLE_SORTS,\n showLinkedDate = true,\n refreshKey = 0,\n disabled = false,\n translator,\n runGuardedMutation,\n onLoadingChange,\n header,\n renderFooter,\n}: LinkedPeopleSectionProps<TDetails, TLinkSettings>) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(\n () => createTranslatorWithFallback(tHook),\n [tHook],\n )\n const translate: Translator = translator ?? fallbackTranslator\n const knownPeople = React.useMemo(() => fallbackPeople ?? [], [fallbackPeople])\n const sortOptions = availableSorts.length > 0 ? availableSorts : ALL_LINKED_PEOPLE_SORTS\n\n const [removingId, setRemovingId] = React.useState<string | null>(null)\n const [internalLinkDialogOpen, setInternalLinkDialogOpen] = React.useState(false)\n const linkDialogOpen = controlledLinkDialogOpen ?? internalLinkDialogOpen\n const setLinkDialogOpen = React.useCallback(\n (open: boolean) => {\n setInternalLinkDialogOpen(open)\n onLinkDialogOpenChange?.(open)\n },\n [onLinkDialogOpenChange],\n )\n const [searchQuery, setSearchQuery] = React.useState('')\n const [sortMode, setSortMode] = React.useState<LinkedPeopleSortMode>(sortOptions[0])\n const [filtersOpen, setFiltersOpen] = React.useState(true)\n const [visiblePeople, setVisiblePeople] = React.useState<LinkedPersonSummary[]>([])\n const [listPage, setListPage] = React.useState(1)\n const [listTotalPages, setListTotalPages] = React.useState(1)\n const [listTotalCount, setListTotalCount] = React.useState(knownPeople.length)\n const [listLoading, setListLoading] = React.useState(true)\n const [starredIds, setStarredIds] = React.useState<Set<string>>(\n () => readVersionedIdSet(`om:starred-people:${scopeId}`, STARRED_PEOPLE_STORAGE_VERSION),\n )\n\n const toggleStar = React.useCallback(\n (personId: string) => {\n setStarredIds((prev) => {\n const next = new Set(prev)\n if (next.has(personId)) next.delete(personId)\n else next.add(personId)\n writeVersionedIdSet(`om:starred-people:${scopeId}`, STARRED_PEOPLE_STORAGE_VERSION, next)\n return next\n })\n },\n [scopeId],\n )\n\n const displayedPeople = React.useMemo(\n () => (visiblePeople.length > 0 ? visiblePeople : knownPeople),\n [knownPeople, visiblePeople],\n )\n const totalLinkedPeople = listTotalCount > 0 ? listTotalCount : displayedPeople.length\n\n const loadVisiblePeople = React.useCallback(async () => {\n setListLoading(true)\n try {\n const payload = await loadPage({\n page: listPage,\n pageSize: LINKED_PEOPLE_PAGE_SIZE,\n sort: sortMode,\n search: searchQuery.trim(),\n })\n const nextTotalCount = typeof payload.total === 'number' ? payload.total : 0\n setVisiblePeople(Array.isArray(payload.items) ? payload.items : [])\n setListPage(typeof payload.page === 'number' ? payload.page : listPage)\n setListTotalCount((current) =>\n searchQuery.trim().length > 0 ? Math.max(current, nextTotalCount) : nextTotalCount,\n )\n setListTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)\n } catch {\n setVisiblePeople([])\n if (searchQuery.trim().length === 0) {\n setListTotalCount(0)\n }\n setListTotalPages(1)\n } finally {\n setListLoading(false)\n }\n }, [listPage, loadPage, searchQuery, sortMode])\n\n React.useEffect(() => {\n void loadVisiblePeople()\n }, [loadVisiblePeople, refreshKey])\n\n React.useEffect(() => {\n setListPage(1)\n }, [searchQuery, sortMode])\n\n const handleLinkConfirm = React.useCallback(\n async (input: LinkEntityConfirmInput<TLinkSettings>) => {\n onLoadingChange?.(true)\n try {\n await onLinkConfirm(input)\n await loadVisiblePeople()\n } finally {\n onLoadingChange?.(false)\n }\n },\n [loadVisiblePeople, onLinkConfirm, onLoadingChange],\n )\n\n const handleRemove = React.useCallback(\n async (personId: string) => {\n if (!personId || removingId) return\n setRemovingId(personId)\n onLoadingChange?.(true)\n try {\n await onUnlink(personId)\n await loadVisiblePeople()\n } catch {\n // onUnlink owns user-facing error reporting\n } finally {\n setRemovingId(null)\n onLoadingChange?.(false)\n }\n },\n [loadVisiblePeople, onLoadingChange, onUnlink, removingId],\n )\n\n const linkAction = (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setLinkDialogOpen(true)}\n disabled={disabled}\n >\n <Link2 className=\"mr-1.5 h-4 w-4\" />\n {linkActionLabel}\n </Button>\n )\n const addPersonAction = onAddPerson && addActionLabel ? (\n <Button type=\"button\" size=\"sm\" onClick={onAddPerson} disabled={disabled}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n {addActionLabel}\n </Button>\n ) : null\n\n const linkDialog = (\n <LinkEntityDialog\n open={linkDialogOpen}\n onOpenChange={setLinkDialogOpen}\n adapter={linkAdapter}\n initialSelectedIds={linkedIds ?? []}\n onConfirm={handleLinkConfirm}\n runGuardedMutation={runGuardedMutation}\n />\n )\n\n if (!listLoading && totalLinkedPeople === 0) {\n const emptyStateAction = onEmptyStateAction ?? onAddPerson\n return (\n <>\n <EmptyState\n icon={<Users className=\"h-10 w-10 text-muted-foreground\" />}\n title={emptyState.title}\n actionLabel={emptyStateAction ? emptyState.actionLabel : undefined}\n onAction={emptyStateAction}\n >\n <p className=\"text-sm text-muted-foreground\">{emptyLabel}</p>\n <div className=\"mt-4\">{linkAction}</div>\n </EmptyState>\n {linkDialog}\n </>\n )\n }\n\n return (\n <>\n <div className=\"space-y-4\">\n {header}\n\n <section className=\"rounded-lg border bg-card px-4 py-4 sm:px-5\">\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between\">\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-base font-semibold\">{sectionTitle}</h3>\n <Badge\n variant=\"secondary\"\n className=\"rounded-full px-2 py-0 text-xs font-semibold\"\n >\n {totalLinkedPeople}\n </Badge>\n </div>\n {sectionSubtitle ? (\n <p className=\"text-xs text-muted-foreground\">{sectionSubtitle}</p>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-2 xl:justify-end\">\n {linkAction}\n {addPersonAction}\n </div>\n </div>\n\n {totalLinkedPeople > 0 ? (\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center\">\n {filtersOpen ? (\n <div className=\"min-w-0 flex-1\">\n <Input\n type=\"text\"\n size=\"lg\"\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder={searchPlaceholder}\n />\n </div>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2 lg:justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setFiltersOpen((current) => !current)}\n className=\"h-10\"\n >\n <Filter className=\"mr-1.5 h-4 w-4\" />\n {translate('customers.linkedPeople.filter', 'Filters')}\n </Button>\n {/*\n Deliberately a native <select> rather than the DS `Select`. That primitive is\n Radix-based (a button + listbox), and this element is the one the extraction\n gate runs through: `CompanyPeopleSection.test.tsx` must keep passing\n unmodified, and it drives the sort with `getByDisplayValue` + `fireEvent\n .change` \u2014 native-select APIs. Swapping it would also change the company\n tab's keyboard and mobile-picker behaviour, which is the one thing this PR\n asserts it does not do. Worth migrating, but as its own change.\n */}\n {filtersOpen && sortOptions.length > 1 ? (\n <select\n value={sortMode}\n onChange={(event) =>\n setSortMode(event.target.value as LinkedPeopleSortMode)\n }\n 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\"\n >\n {sortOptions.map((option) => (\n <option key={option} value={option}>\n {translate(SORT_LABELS[option].key, SORT_LABELS[option].fallback)}\n </option>\n ))}\n </select>\n ) : null}\n </div>\n </div>\n ) : null}\n\n {listLoading ? (\n <p className=\"py-6 text-center text-sm text-muted-foreground\">\n {translate('customers.linkedPeople.loading', 'Loading people\u2026')}\n </p>\n ) : visiblePeople.length > 0 ? (\n <>\n <div\n className=\"grid items-start gap-4\"\n style={{\n gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 19.5rem), 1fr))',\n }}\n >\n {visiblePeople.map((person) => (\n <PersonCard\n key={person.id}\n person={person}\n isStarred={starredIds.has(person.id)}\n onToggleStar={toggleStar}\n onUnlink={handleRemove}\n showLinkedDate={showLinkedDate}\n />\n ))}\n </div>\n {listTotalPages > 1 ? (\n <div className=\"flex items-center justify-between border-t border-border/60 pt-3 text-sm text-muted-foreground\">\n <span>\n {translate(\n 'customers.linkedPeople.pageSummary',\n 'Page {{page}} of {{total}}',\n {\n page: listPage,\n total: listTotalPages,\n },\n )}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setListPage((current) => Math.max(1, current - 1))}\n disabled={listPage <= 1}\n >\n {translate('customers.linkedPeople.previous', 'Previous')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() =>\n setListPage((current) => Math.min(listTotalPages, current + 1))\n }\n disabled={listPage >= listTotalPages}\n >\n {translate('customers.linkedPeople.next', 'Next')}\n </Button>\n </div>\n </div>\n ) : null}\n </>\n ) : totalLinkedPeople > 0 ? (\n <p className=\"py-6 text-center text-sm text-muted-foreground\">\n {translate(\n 'customers.linkedPeople.noSearchResults',\n 'No people match your search.',\n )}\n </p>\n ) : null}\n </div>\n </section>\n\n {renderFooter?.({ people: displayedPeople, starredIds })}\n </div>\n\n {linkDialog}\n </>\n )\n}\n\nexport default LinkedPeopleSection\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -43,14 +43,17 @@ function splitSourceTags(source) {
|
|
|
43
43
|
if (typeof source !== "string") return [];
|
|
44
44
|
return source.split(/[;,|]/).map((entry) => entry.trim()).filter((entry, index, array) => entry.length > 0 && array.indexOf(entry) === index);
|
|
45
45
|
}
|
|
46
|
-
function PersonCard({ person, isStarred, onToggleStar, onUnlink }) {
|
|
46
|
+
function PersonCard({ person, isStarred, onToggleStar, onUnlink, showLinkedDate = true }) {
|
|
47
47
|
const t = useT();
|
|
48
48
|
const sourceTags = React.useMemo(() => splitSourceTags(person.source), [person.source]);
|
|
49
49
|
const temperature = React.useMemo(() => {
|
|
50
50
|
const value = typeof person.temperature === "string" ? person.temperature.trim().toLowerCase() : "";
|
|
51
51
|
return value in temperatureConfig ? temperatureConfig[value] : null;
|
|
52
52
|
}, [person.temperature]);
|
|
53
|
-
const linkedDate = React.useMemo(
|
|
53
|
+
const linkedDate = React.useMemo(
|
|
54
|
+
() => showLinkedDate ? formatDate(person.linkedAt ?? person.createdAt) : null,
|
|
55
|
+
[person.createdAt, person.linkedAt, showLinkedDate]
|
|
56
|
+
);
|
|
54
57
|
return /* @__PURE__ */ jsxs("div", { className: "min-w-0 overflow-hidden rounded-lg border bg-card p-4", children: [
|
|
55
58
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
56
59
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-3", children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/PersonCard.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Mail, Phone, Star, MoreHorizontal, ArrowUpRight, Unlink } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport type { CompanyPersonSummary } from './CompanyPeopleSection'\nimport { formatDate, formatFallbackLabel } from './utils'\n\nconst sourceColorMap: Record<string, string> = {\n linkedin: 'border-status-info-icon text-status-info-icon',\n email: 'border-status-success-icon text-status-success-icon',\n web_form: 'border-status-info-icon text-status-info-icon',\n 'web form': 'border-status-info-icon text-status-info-icon',\n referral: 'border-status-warning-icon text-status-warning-icon',\n customer_referral: 'border-status-warning-icon text-status-warning-icon',\n 'customer referral': 'border-status-warning-icon text-status-warning-icon',\n partner_referral: 'border-status-warning-icon text-status-warning-icon',\n 'partner referral': 'border-status-warning-icon text-status-warning-icon',\n event: 'border-status-info-icon text-status-info-icon',\n 'conference/event': 'border-status-info-icon text-status-info-icon',\n cold_outreach: 'border-status-neutral-icon text-status-neutral-icon',\n 'cold outreach': 'border-status-neutral-icon text-status-neutral-icon',\n facebook: 'border-status-info-icon text-status-info-icon',\n typeform: 'border-status-info-icon text-status-info-icon',\n other: 'border-status-neutral-icon text-status-neutral-icon',\n}\n\nconst temperatureConfig = {\n hot: { filled: 5, dotClassName: 'bg-status-error-icon', labelKey: 'customers.temperature.hot', fallback: 'Hot Client' },\n warm: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.warm', fallback: 'High Interest' },\n high: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.high', fallback: 'High Interest' },\n neutral: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.neutral', fallback: 'Engaged' },\n medium: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.medium', fallback: 'Engaged' },\n cool: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cool', fallback: 'Low Activity' },\n low: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.low', fallback: 'Low Activity' },\n cold: { filled: 1, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cold', fallback: 'At Risk' },\n} as const\n\nfunction splitSourceTags(source: string | null | undefined): string[] {\n if (typeof source !== 'string') return []\n return source\n .split(/[;,|]/)\n .map((entry) => entry.trim())\n .filter((entry, index, array) => entry.length > 0 && array.indexOf(entry) === index)\n}\n\ninterface PersonCardProps {\n person: CompanyPersonSummary\n isStarred?: boolean\n onToggleStar?: (personId: string) => void\n onUnlink?: (personId: string) => void\n}\n\nexport function PersonCard({ person, isStarred, onToggleStar, onUnlink }: PersonCardProps) {\n const t = useT()\n const sourceTags = React.useMemo(() => splitSourceTags(person.source), [person.source])\n const temperature = React.useMemo(() => {\n const value = typeof person.temperature === 'string' ? person.temperature.trim().toLowerCase() : ''\n return value in temperatureConfig ? temperatureConfig[value as keyof typeof temperatureConfig] : null\n }, [person.temperature])\n const linkedDate = React.useMemo(() => formatDate(person.linkedAt ?? person.createdAt), [person.createdAt, person.linkedAt])\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border bg-card p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-start gap-3\">\n <Avatar label={person.displayName} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n <span className=\"min-w-0 break-words text-sm font-bold leading-5 text-foreground\">{person.displayName}</span>\n {onToggleStar && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onToggleStar(person.id)}\n className=\"h-auto shrink-0 p-0 text-muted-foreground hover:text-status-warning-icon\"\n aria-label={t('customers.people.card.toggleStar', 'Toggle star')}\n >\n <Star className={cn('size-3.5', isStarred && 'fill-status-warning-icon text-status-warning-icon')} />\n </IconButton>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {person.jobTitle && (\n <Badge variant=\"secondary\" className=\"max-w-full truncate px-2 py-0.5 text-xs font-semibold\">\n {person.jobTitle}\n </Badge>\n )}\n {person.status && (\n <Badge variant=\"outline\" className=\"shrink-0 px-2 py-0.5 text-xs font-medium\">\n <span className=\"mr-1 inline-block size-1.5 rounded-full bg-status-success-icon\" />\n {person.status}\n </Badge>\n )}\n </div>\n </div>\n </div>\n <Popover>\n <PopoverTrigger asChild>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"shrink-0\"\n aria-label={t('customers.people.card.more', 'More')}\n >\n <MoreHorizontal className=\"size-3.5\" />\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-36 p-1\">\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start text-xs text-destructive hover:text-destructive\"\n onClick={() => onUnlink(person.id)}\n >\n <Unlink className=\"mr-1.5 size-3\" />\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </PopoverContent>\n </Popover>\n </div>\n\n <div className=\"mt-3 space-y-2 border-t pt-3 text-xs text-foreground\">\n {person.primaryEmail && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Mail className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryEmail}</span>\n </div>\n )}\n {person.primaryPhone && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryPhone}</span>\n </div>\n )}\n {(person.lifecycleStage || linkedDate) && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-1 text-xs text-muted-foreground\">\n <span className=\"truncate\">{person.lifecycleStage ?? t('customers.people.card.defaultStage', 'customer')}</span>\n {linkedDate ? (\n <>\n <span aria-hidden>·</span>\n <span className=\"truncate\">{t('customers.people.card.linkedOn', 'Linked {{date}}', { date: linkedDate })}</span>\n </>\n ) : null}\n </div>\n )}\n </div>\n\n {(temperature || sourceTags.length > 0) && (\n <div className=\"mt-3 flex flex-col gap-3 border-t pt-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0 flex-1 space-y-1\">\n {sourceTags.length > 0 ? (\n <>\n <p className=\"text-overline font-bold uppercase tracking-wide text-muted-foreground\">\n {t('customers.people.card.source', 'Source')}\n </p>\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n {sourceTags.map((sourceTag) => {\n const normalized = sourceTag.toLowerCase().replace(/\\s+/g, '_')\n return (\n <Badge\n key={sourceTag}\n variant=\"outline\"\n className={cn(\n 'max-w-full truncate px-2 py-0.5 text-xs font-semibold',\n sourceColorMap[normalized] ?? sourceColorMap[sourceTag.toLowerCase()] ?? 'border-border text-muted-foreground',\n )}\n >\n {formatFallbackLabel(sourceTag)}\n </Badge>\n )\n })}\n </div>\n </>\n ) : null}\n </div>\n {temperature ? (\n <div className=\"space-y-1 sm:ml-auto sm:text-right\">\n <div className=\"flex items-center gap-1 text-overline font-bold text-muted-foreground sm:justify-end\">\n <span>{t(temperature.labelKey, temperature.fallback)}</span>\n </div>\n <div className=\"flex items-center gap-1 sm:justify-end\">\n {Array.from({ length: 5 }).map((_, index) => (\n <span\n key={index}\n className={cn(\n 'inline-block size-2 rounded-full border border-border/70',\n index < temperature.filled ? temperature.dotClassName : 'bg-muted',\n )}\n />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n )}\n\n <div className=\"mt-3 flex flex-col gap-2 border-t pt-3 sm:flex-row sm:flex-wrap\">\n <Button\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full text-xs font-semibold sm:min-w-[9rem] sm:flex-1\"\n >\n <Link href={`/backend/customers/people-v2/${person.id}`}>\n <ArrowUpRight className=\"mr-1 size-3\" />\n {t('customers.people.card.open', 'Open person')}\n </Link>\n </Button>\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full border-destructive/30 text-xs font-semibold text-destructive hover:bg-destructive/10 sm:min-w-[7rem] sm:flex-none\"\n onClick={() => onUnlink(person.id)}\n >\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Mail, Phone, Star, MoreHorizontal, ArrowUpRight, Unlink } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport type { LinkedPersonSummary } from './LinkedPeopleSection'\nimport { formatDate, formatFallbackLabel } from './utils'\n\nconst sourceColorMap: Record<string, string> = {\n linkedin: 'border-status-info-icon text-status-info-icon',\n email: 'border-status-success-icon text-status-success-icon',\n web_form: 'border-status-info-icon text-status-info-icon',\n 'web form': 'border-status-info-icon text-status-info-icon',\n referral: 'border-status-warning-icon text-status-warning-icon',\n customer_referral: 'border-status-warning-icon text-status-warning-icon',\n 'customer referral': 'border-status-warning-icon text-status-warning-icon',\n partner_referral: 'border-status-warning-icon text-status-warning-icon',\n 'partner referral': 'border-status-warning-icon text-status-warning-icon',\n event: 'border-status-info-icon text-status-info-icon',\n 'conference/event': 'border-status-info-icon text-status-info-icon',\n cold_outreach: 'border-status-neutral-icon text-status-neutral-icon',\n 'cold outreach': 'border-status-neutral-icon text-status-neutral-icon',\n facebook: 'border-status-info-icon text-status-info-icon',\n typeform: 'border-status-info-icon text-status-info-icon',\n other: 'border-status-neutral-icon text-status-neutral-icon',\n}\n\nconst temperatureConfig = {\n hot: { filled: 5, dotClassName: 'bg-status-error-icon', labelKey: 'customers.temperature.hot', fallback: 'Hot Client' },\n warm: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.warm', fallback: 'High Interest' },\n high: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.high', fallback: 'High Interest' },\n neutral: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.neutral', fallback: 'Engaged' },\n medium: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.medium', fallback: 'Engaged' },\n cool: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cool', fallback: 'Low Activity' },\n low: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.low', fallback: 'Low Activity' },\n cold: { filled: 1, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cold', fallback: 'At Risk' },\n} as const\n\nfunction splitSourceTags(source: string | null | undefined): string[] {\n if (typeof source !== 'string') return []\n return source\n .split(/[;,|]/)\n .map((entry) => entry.trim())\n .filter((entry, index, array) => entry.length > 0 && array.indexOf(entry) === index)\n}\n\ninterface PersonCardProps {\n person: LinkedPersonSummary\n isStarred?: boolean\n onToggleStar?: (personId: string) => void\n onUnlink?: (personId: string) => void\n /**\n * Render the \"Linked {date}\" line. Defaults to `true`; a host whose `linkedAt` is not\n * durable passes `false` rather than showing a date that is wrong after the first write.\n */\n showLinkedDate?: boolean\n}\n\nexport function PersonCard({ person, isStarred, onToggleStar, onUnlink, showLinkedDate = true }: PersonCardProps) {\n const t = useT()\n const sourceTags = React.useMemo(() => splitSourceTags(person.source), [person.source])\n const temperature = React.useMemo(() => {\n const value = typeof person.temperature === 'string' ? person.temperature.trim().toLowerCase() : ''\n return value in temperatureConfig ? temperatureConfig[value as keyof typeof temperatureConfig] : null\n }, [person.temperature])\n const linkedDate = React.useMemo(\n () => (showLinkedDate ? formatDate(person.linkedAt ?? person.createdAt) : null),\n [person.createdAt, person.linkedAt, showLinkedDate],\n )\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border bg-card p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-start gap-3\">\n <Avatar label={person.displayName} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n <span className=\"min-w-0 break-words text-sm font-bold leading-5 text-foreground\">{person.displayName}</span>\n {onToggleStar && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onToggleStar(person.id)}\n className=\"h-auto shrink-0 p-0 text-muted-foreground hover:text-status-warning-icon\"\n aria-label={t('customers.people.card.toggleStar', 'Toggle star')}\n >\n <Star className={cn('size-3.5', isStarred && 'fill-status-warning-icon text-status-warning-icon')} />\n </IconButton>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {person.jobTitle && (\n <Badge variant=\"secondary\" className=\"max-w-full truncate px-2 py-0.5 text-xs font-semibold\">\n {person.jobTitle}\n </Badge>\n )}\n {person.status && (\n <Badge variant=\"outline\" className=\"shrink-0 px-2 py-0.5 text-xs font-medium\">\n <span className=\"mr-1 inline-block size-1.5 rounded-full bg-status-success-icon\" />\n {person.status}\n </Badge>\n )}\n </div>\n </div>\n </div>\n <Popover>\n <PopoverTrigger asChild>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"shrink-0\"\n aria-label={t('customers.people.card.more', 'More')}\n >\n <MoreHorizontal className=\"size-3.5\" />\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-36 p-1\">\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start text-xs text-destructive hover:text-destructive\"\n onClick={() => onUnlink(person.id)}\n >\n <Unlink className=\"mr-1.5 size-3\" />\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </PopoverContent>\n </Popover>\n </div>\n\n <div className=\"mt-3 space-y-2 border-t pt-3 text-xs text-foreground\">\n {person.primaryEmail && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Mail className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryEmail}</span>\n </div>\n )}\n {person.primaryPhone && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryPhone}</span>\n </div>\n )}\n {(person.lifecycleStage || linkedDate) && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-1 text-xs text-muted-foreground\">\n <span className=\"truncate\">{person.lifecycleStage ?? t('customers.people.card.defaultStage', 'customer')}</span>\n {linkedDate ? (\n <>\n <span aria-hidden>·</span>\n <span className=\"truncate\">{t('customers.people.card.linkedOn', 'Linked {{date}}', { date: linkedDate })}</span>\n </>\n ) : null}\n </div>\n )}\n </div>\n\n {(temperature || sourceTags.length > 0) && (\n <div className=\"mt-3 flex flex-col gap-3 border-t pt-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0 flex-1 space-y-1\">\n {sourceTags.length > 0 ? (\n <>\n <p className=\"text-overline font-bold uppercase tracking-wide text-muted-foreground\">\n {t('customers.people.card.source', 'Source')}\n </p>\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n {sourceTags.map((sourceTag) => {\n const normalized = sourceTag.toLowerCase().replace(/\\s+/g, '_')\n return (\n <Badge\n key={sourceTag}\n variant=\"outline\"\n className={cn(\n 'max-w-full truncate px-2 py-0.5 text-xs font-semibold',\n sourceColorMap[normalized] ?? sourceColorMap[sourceTag.toLowerCase()] ?? 'border-border text-muted-foreground',\n )}\n >\n {formatFallbackLabel(sourceTag)}\n </Badge>\n )\n })}\n </div>\n </>\n ) : null}\n </div>\n {temperature ? (\n <div className=\"space-y-1 sm:ml-auto sm:text-right\">\n <div className=\"flex items-center gap-1 text-overline font-bold text-muted-foreground sm:justify-end\">\n <span>{t(temperature.labelKey, temperature.fallback)}</span>\n </div>\n <div className=\"flex items-center gap-1 sm:justify-end\">\n {Array.from({ length: 5 }).map((_, index) => (\n <span\n key={index}\n className={cn(\n 'inline-block size-2 rounded-full border border-border/70',\n index < temperature.filled ? temperature.dotClassName : 'bg-muted',\n )}\n />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n )}\n\n <div className=\"mt-3 flex flex-col gap-2 border-t pt-3 sm:flex-row sm:flex-wrap\">\n <Button\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full text-xs font-semibold sm:min-w-[9rem] sm:flex-1\"\n >\n <Link href={`/backend/customers/people-v2/${person.id}`}>\n <ArrowUpRight className=\"mr-1 size-3\" />\n {t('customers.people.card.open', 'Open person')}\n </Link>\n </Button>\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full border-destructive/30 text-xs font-semibold text-destructive hover:bg-destructive/10 sm:min-w-[7rem] sm:flex-none\"\n onClick={() => onUnlink(person.id)}\n >\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -133,6 +133,9 @@ function createPersonLinkAdapter(options) {
|
|
|
133
133
|
if (options.excludeLinkedCompanyId) {
|
|
134
134
|
params.set("excludeLinkedCompanyId", options.excludeLinkedCompanyId);
|
|
135
135
|
}
|
|
136
|
+
if (options.excludeLinkedDealId) {
|
|
137
|
+
params.set("excludeLinkedDealId", options.excludeLinkedDealId);
|
|
138
|
+
}
|
|
136
139
|
const payload = await readApiResultOrThrow(
|
|
137
140
|
`/api/customers/people?${params.toString()}`
|
|
138
141
|
);
|