@open-mercato/core 0.5.1-develop.2856.35de414092 → 0.5.1-develop.2874.77704bccbd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js +18 -18
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/api_keys/backend/api-keys/create/page.js +1 -1
- package/dist/modules/api_keys/backend/api-keys/create/page.js.map +1 -1
- package/dist/modules/attachments/components/AttachmentLibrary.js +2 -2
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +1 -1
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +1 -1
- package/dist/modules/attachments/fields/attachment.js +1 -1
- package/dist/modules/attachments/fields/attachment.js.map +1 -1
- package/dist/modules/audit_logs/components/ActionLogDetailsDialog.js +1 -1
- package/dist/modules/audit_logs/components/ActionLogDetailsDialog.js.map +2 -2
- package/dist/modules/audit_logs/lib/display-helpers.js +1 -1
- package/dist/modules/audit_logs/lib/display-helpers.js.map +1 -1
- package/dist/modules/auth/backend/users/create/page.js +1 -1
- package/dist/modules/auth/backend/users/create/page.js.map +1 -1
- package/dist/modules/business_rules/backend/rules/page.js +6 -6
- package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/page.js +2 -2
- package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
- package/dist/modules/business_rules/components/ActionBuilder.js +5 -5
- package/dist/modules/business_rules/components/ActionBuilder.js.map +2 -2
- package/dist/modules/business_rules/components/ActionRow.js +8 -8
- package/dist/modules/business_rules/components/ActionRow.js.map +1 -1
- package/dist/modules/business_rules/components/ConditionBuilder.js +5 -5
- package/dist/modules/business_rules/components/ConditionBuilder.js.map +2 -2
- package/dist/modules/business_rules/components/ConditionGroup.js +2 -2
- package/dist/modules/business_rules/components/ConditionGroup.js.map +1 -1
- package/dist/modules/business_rules/components/ConditionRow.js +3 -3
- package/dist/modules/business_rules/components/ConditionRow.js.map +2 -2
- package/dist/modules/business_rules/components/RuleSetMembers.js +8 -8
- package/dist/modules/business_rules/components/RuleSetMembers.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +1 -1
- package/dist/modules/catalog/backend/catalog/products/create/page.js +5 -5
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +1 -1
- package/dist/modules/catalog/components/products/MetadataEditor.js +1 -1
- package/dist/modules/catalog/components/products/MetadataEditor.js.map +1 -1
- package/dist/modules/catalog/components/products/ProductImageCell.js +1 -1
- package/dist/modules/catalog/components/products/ProductImageCell.js.map +1 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js +1 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +1 -1
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +1 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +9 -9
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +7 -7
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +1 -1
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +3 -3
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +1 -1
- package/dist/modules/customers/components/AddressTiles.js +1 -1
- package/dist/modules/customers/components/AddressTiles.js.map +1 -1
- package/dist/modules/customers/components/detail/ActivityForm.js +3 -3
- package/dist/modules/customers/components/detail/ActivityForm.js.map +1 -1
- package/dist/modules/customers/components/detail/AnnualRevenueField.js +2 -2
- package/dist/modules/customers/components/detail/AnnualRevenueField.js.map +1 -1
- package/dist/modules/customers/components/detail/CustomFieldValuesList.js +1 -1
- package/dist/modules/customers/components/detail/CustomFieldValuesList.js.map +1 -1
- package/dist/modules/customers/components/detail/DealForm.js +1 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +1 -1
- package/dist/modules/customers/components/detail/DealsSection.js.map +1 -1
- package/dist/modules/customers/components/detail/DetailFieldsSection.js +1 -1
- package/dist/modules/customers/components/detail/DetailFieldsSection.js.map +1 -1
- package/dist/modules/customers/components/detail/InlineEditors.js +5 -5
- package/dist/modules/customers/components/detail/InlineEditors.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +1 -1
- package/dist/modules/customers/components/detail/TasksSection.js.map +1 -1
- package/dist/modules/customers/components/detail/TimelineItemHeader.js +1 -1
- package/dist/modules/customers/components/detail/TimelineItemHeader.js.map +1 -1
- package/dist/modules/customers/components/formConfig.js +2 -2
- package/dist/modules/customers/components/formConfig.js.map +1 -1
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +1 -1
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +1 -1
- package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js +2 -2
- package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js.map +1 -1
- package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js +1 -1
- package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js.map +1 -1
- package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js +1 -1
- package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js.map +1 -1
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +1 -1
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +1 -1
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +1 -1
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +1 -1
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +1 -1
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +1 -1
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +1 -1
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +1 -1
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +1 -1
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +1 -1
- package/dist/modules/data_sync/backend/data-sync/page.js +4 -4
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +1 -1
- package/dist/modules/dictionaries/components/AppearanceSelector.js +3 -3
- package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +4 -4
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +3 -3
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +1 -1
- package/dist/modules/dictionaries/fields/dictionary.js +4 -4
- package/dist/modules/dictionaries/fields/dictionary.js.map +1 -1
- package/dist/modules/entities/components/EncryptionManager.js +3 -3
- package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
- package/dist/modules/entities/components/UserEntitiesTable.js +1 -1
- package/dist/modules/entities/components/UserEntitiesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/formConfig.js +1 -1
- package/dist/modules/feature_toggles/components/formConfig.js.map +1 -1
- package/dist/modules/feature_toggles/components/overrideFormConfig.js +2 -2
- package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +1 -1
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +12 -12
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +2 -2
- package/dist/modules/inbox_ops/components/messages/InboxEmailPreview.js +1 -1
- package/dist/modules/inbox_ops/components/messages/InboxEmailPreview.js.map +1 -1
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js +12 -12
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
- package/dist/modules/inbox_ops/widgets/notifications/ProposalCreatedRenderer.js +3 -3
- package/dist/modules/inbox_ops/widgets/notifications/ProposalCreatedRenderer.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +6 -6
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -1
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +1 -1
- package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +1 -1
- package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +1 -1
- package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js +1 -1
- package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js.map +1 -1
- package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js +1 -1
- package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js.map +1 -1
- package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js +1 -1
- package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js.map +1 -1
- package/dist/modules/messages/components/message-detail/panels/attachments-panel.js +1 -1
- package/dist/modules/messages/components/message-detail/panels/attachments-panel.js.map +1 -1
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +11 -11
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +3 -3
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -3
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +4 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +4 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +1 -1
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +1 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +4 -4
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +1 -1
- package/dist/modules/sales/components/DocumentNumberSettings.js +1 -1
- package/dist/modules/sales/components/DocumentNumberSettings.js.map +1 -1
- package/dist/modules/sales/components/channels/ChannelOfferForm.js +1 -1
- package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +1 -1
- package/dist/modules/sales/components/documents/AdjustmentDialog.js +1 -1
- package/dist/modules/sales/components/documents/AdjustmentDialog.js.map +1 -1
- package/dist/modules/sales/components/documents/DocumentTotals.js +3 -3
- package/dist/modules/sales/components/documents/DocumentTotals.js.map +1 -1
- package/dist/modules/sales/components/documents/PaymentDialog.js +1 -1
- package/dist/modules/sales/components/documents/PaymentDialog.js.map +1 -1
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +4 -4
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +1 -1
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +4 -4
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +1 -1
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +2 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +1 -1
- package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js +1 -1
- package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js.map +1 -1
- package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js +1 -1
- package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js.map +1 -1
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/PackageEditor.js +1 -1
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/PackageEditor.js.map +1 -1
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +1 -1
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +1 -1
- package/dist/modules/translations/components/TranslationDrawerAction.js +2 -2
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +1 -1
- package/dist/modules/translations/components/TranslationManager.js +3 -3
- package/dist/modules/translations/components/TranslationManager.js.map +1 -1
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +1 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js +5 -5
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.js +4 -4
- package/dist/modules/workflows/backend/events/[id]/page.js.map +1 -1
- package/dist/modules/workflows/backend/instances/[id]/page.js +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
- package/dist/modules/workflows/backend/tasks/[id]/page.js +20 -20
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +1 -1
- package/dist/modules/workflows/components/EdgeEditDialog.js +12 -12
- package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
- package/dist/modules/workflows/components/NodeEditDialog.js +26 -26
- package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +1 -1
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +1 -1
- package/dist/modules/workflows/components/mobile/MobileDefinitionDetail.js +2 -2
- package/dist/modules/workflows/components/mobile/MobileDefinitionDetail.js.map +2 -2
- package/dist/modules/workflows/components/mobile/MobileInstanceOverview.js +7 -7
- package/dist/modules/workflows/components/mobile/MobileInstanceOverview.js.map +2 -2
- package/dist/modules/workflows/components/mobile/MobileTaskForm.js +11 -11
- package/dist/modules/workflows/components/mobile/MobileTaskForm.js.map +2 -2
- package/dist/modules/workflows/components/mobile/MobileVisualEditor.js +1 -1
- package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
- package/dist/modules/workflows/components/mobile/MobileWorkflowTimeline.js +23 -23
- package/dist/modules/workflows/components/mobile/MobileWorkflowTimeline.js.map +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js +6 -6
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +1 -1
- package/package.json +3 -3
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +18 -18
- package/src/modules/api_keys/backend/api-keys/create/page.tsx +1 -1
- package/src/modules/attachments/components/AttachmentLibrary.tsx +3 -3
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +1 -1
- package/src/modules/attachments/fields/attachment.tsx +1 -1
- package/src/modules/audit_logs/components/ActionLogDetailsDialog.tsx +1 -1
- package/src/modules/audit_logs/lib/display-helpers.tsx +1 -1
- package/src/modules/auth/backend/users/create/page.tsx +1 -1
- package/src/modules/business_rules/backend/rules/page.tsx +7 -7
- package/src/modules/business_rules/backend/sets/page.tsx +3 -3
- package/src/modules/business_rules/components/ActionBuilder.tsx +6 -6
- package/src/modules/business_rules/components/ActionRow.tsx +8 -8
- package/src/modules/business_rules/components/ConditionBuilder.tsx +6 -6
- package/src/modules/business_rules/components/ConditionGroup.tsx +2 -2
- package/src/modules/business_rules/components/ConditionRow.tsx +3 -3
- package/src/modules/business_rules/components/RuleSetMembers.tsx +9 -9
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2 -2
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +5 -5
- package/src/modules/catalog/components/products/MetadataEditor.tsx +1 -1
- package/src/modules/catalog/components/products/ProductImageCell.tsx +1 -1
- package/src/modules/catalog/components/products/VariantBuilder.tsx +1 -1
- package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +1 -1
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +2 -2
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +10 -10
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +9 -9
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +3 -3
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +2 -2
- package/src/modules/customers/components/AddressTiles.tsx +1 -1
- package/src/modules/customers/components/detail/ActivityForm.tsx +3 -3
- package/src/modules/customers/components/detail/AnnualRevenueField.tsx +2 -2
- package/src/modules/customers/components/detail/CustomFieldValuesList.tsx +1 -1
- package/src/modules/customers/components/detail/DealForm.tsx +1 -1
- package/src/modules/customers/components/detail/DealsSection.tsx +1 -1
- package/src/modules/customers/components/detail/DetailFieldsSection.tsx +1 -1
- package/src/modules/customers/components/detail/InlineEditors.tsx +5 -5
- package/src/modules/customers/components/detail/TasksSection.tsx +1 -1
- package/src/modules/customers/components/detail/TimelineItemHeader.tsx +1 -1
- package/src/modules/customers/components/formConfig.tsx +2 -2
- package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +1 -1
- package/src/modules/customers/widgets/dashboard/new-customers/widget.client.tsx +2 -2
- package/src/modules/customers/widgets/dashboard/new-deals/widget.client.tsx +1 -1
- package/src/modules/customers/widgets/dashboard/next-interactions/widget.client.tsx +1 -1
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +2 -2
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +1 -1
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +2 -2
- package/src/modules/data_sync/backend/data-sync/page.tsx +4 -4
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +2 -2
- package/src/modules/dictionaries/components/AppearanceSelector.tsx +3 -3
- package/src/modules/dictionaries/components/DictionariesManager.tsx +4 -4
- package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +2 -2
- package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +3 -3
- package/src/modules/dictionaries/fields/dictionary.tsx +4 -4
- package/src/modules/entities/components/EncryptionManager.tsx +3 -3
- package/src/modules/entities/components/UserEntitiesTable.tsx +1 -1
- package/src/modules/feature_toggles/components/formConfig.tsx +1 -1
- package/src/modules/feature_toggles/components/overrideFormConfig.tsx +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +12 -12
- package/src/modules/inbox_ops/components/messages/InboxEmailPreview.tsx +1 -1
- package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +12 -12
- package/src/modules/inbox_ops/widgets/notifications/ProposalCreatedRenderer.tsx +4 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +6 -6
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +1 -1
- package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +1 -1
- package/src/modules/messages/components/defaults/MessageRecordObjectDetail.tsx +1 -1
- package/src/modules/messages/components/defaults/MessageRecordObjectPreview.tsx +1 -1
- package/src/modules/messages/components/message-detail/panels/MessageListComponent.tsx +1 -1
- package/src/modules/messages/components/message-detail/panels/attachments-panel.tsx +1 -1
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +11 -11
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +2 -2
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +2 -2
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +3 -3
- package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +3 -3
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +4 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +4 -4
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +1 -1
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +4 -4
- package/src/modules/sales/components/DocumentNumberSettings.tsx +1 -1
- package/src/modules/sales/components/channels/ChannelOfferForm.tsx +1 -1
- package/src/modules/sales/components/documents/AdjustmentDialog.tsx +1 -1
- package/src/modules/sales/components/documents/DocumentTotals.tsx +3 -3
- package/src/modules/sales/components/documents/PaymentDialog.tsx +1 -1
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +2 -2
- package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +4 -4
- package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +4 -4
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +2 -2
- package/src/modules/sales/widgets/messages/SalesDocumentMessageDetail.tsx +1 -1
- package/src/modules/sales/widgets/messages/SalesDocumentMessagePreview.tsx +1 -1
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/PackageEditor.tsx +1 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +1 -1
- package/src/modules/translations/components/TranslationDrawerAction.tsx +2 -2
- package/src/modules/translations/components/TranslationManager.tsx +3 -3
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +2 -2
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +5 -5
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +2 -2
- package/src/modules/workflows/backend/events/[id]/page.tsx +4 -4
- package/src/modules/workflows/backend/instances/[id]/page.tsx +2 -2
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +23 -23
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
- package/src/modules/workflows/components/EdgeEditDialog.tsx +12 -12
- package/src/modules/workflows/components/NodeEditDialog.tsx +26 -26
- package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +1 -1
- package/src/modules/workflows/components/mobile/MobileDefinitionDetail.tsx +2 -2
- package/src/modules/workflows/components/mobile/MobileInstanceOverview.tsx +7 -7
- package/src/modules/workflows/components/mobile/MobileTaskForm.tsx +14 -14
- package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +1 -1
- package/src/modules/workflows/components/mobile/MobileWorkflowTimeline.tsx +23 -23
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +6 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/DealsSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Link2, Pencil, Plus } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { LinkEntityDialog } from '../linking/LinkEntityDialog'\nimport { createDealLinkAdapter } from '../linking/adapters/dealAdapter'\nimport { LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { E } from '#generated/entities.ids.generated'\nimport type { DealCustomFieldEntry, DealSummary, SectionAction, TabEmptyStateConfig, Translator } from './types'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatDate } from './utils'\nimport { DealDialog } from './DealDialog'\nimport type { DealFormBaseValues, DealFormSubmitPayload } from './DealForm'\nimport { generateTempId } from '@open-mercato/core/modules/customers/lib/detailHelpers'\nimport { useCurrencyDictionary } from './hooks/useCurrencyDictionary'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { CustomFieldValuesList } from './CustomFieldValuesList'\nimport { useCustomFieldDisplay } from './hooks/useCustomFieldDisplay'\nimport { normalizeCustomFieldKey } from './customFieldUtils'\n\nconst DEALS_PAGE_SIZE = 10\n\ntype DealsScope =\n | { kind: 'person'; entityId: string }\n | { kind: 'company'; entityId: string }\n\ntype GuardedMutationRunner = <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype PendingAction =\n | { kind: 'create' }\n | { kind: 'update'; id: string }\n | { kind: 'remove'; id: string }\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction sanitizeCustomValues(input: unknown): Record<string, unknown> | null {\n if (!isPlainObject(input)) return null\n const result: Record<string, unknown> = {}\n Object.entries(input).forEach(([key, value]) => {\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n result[trimmedKey] = value\n })\n return Object.keys(result).length ? result : null\n}\n\nfunction sanitizeCustomFieldEntries(\n entries: unknown,\n values: Record<string, unknown> | null,\n): DealCustomFieldEntry[] {\n const map = new Map<string, DealCustomFieldEntry>()\n if (Array.isArray(entries)) {\n entries.forEach((entry) => {\n if (!entry || typeof entry !== 'object') return\n const record = entry as Record<string, unknown>\n const keyRaw =\n typeof record.key === 'string'\n ? record.key\n : typeof record.id === 'string'\n ? record.id\n : null\n if (!keyRaw) return\n const key = keyRaw.trim()\n if (!key.length) return\n const normalizedKey = normalizeCustomFieldKey(key)\n if (!normalizedKey) return\n const label =\n typeof record.label === 'string' && record.label.trim().length\n ? record.label.trim()\n : null\n const kind =\n typeof record.kind === 'string' && record.kind.trim().length\n ? record.kind.trim()\n : null\n const multi =\n typeof record.multi === 'boolean' ? record.multi : Array.isArray(record.value) ? true : undefined\n map.set(normalizedKey, {\n key,\n label,\n value: record.value,\n kind,\n multi,\n })\n })\n }\n\n if (values) {\n Object.entries(values).forEach(([rawKey, value]) => {\n const key = rawKey.trim()\n if (!key.length) return\n const normalizedKey = normalizeCustomFieldKey(key)\n if (!normalizedKey) return\n const existing = map.get(normalizedKey)\n if (existing) {\n existing.value = value\n if (existing.multi === undefined) existing.multi = Array.isArray(value)\n } else {\n map.set(normalizedKey, {\n key,\n label: null,\n value,\n kind: null,\n multi: Array.isArray(value) ? true : undefined,\n })\n }\n })\n }\n\n return Array.from(map.values())\n}\n\ntype NormalizedDeal = Omit<\n DealSummary,\n 'valueAmount' | 'probability' | 'expectedCloseAt' | 'customValues' | 'customFields' | 'personIds' | 'companyIds'\n> & {\n valueAmount: number | null\n probability: number | null\n expectedCloseAt: string | null\n personIds: string[]\n companyIds: string[]\n customValues: Record<string, unknown> | null\n customFields: DealCustomFieldEntry[]\n}\n\nfunction toNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\nfunction toIso(value: unknown): string | null {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = new Date(trimmed)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n return null\n}\n\nfunction mergeIds(...sources: Array<unknown>): string[] {\n const set = new Set<string>()\n sources.forEach((source) => {\n if (Array.isArray(source)) {\n source.forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed.length) return\n set.add(trimmed)\n })\n }\n })\n return Array.from(set)\n}\n\nfunction normalizeDeal(deal: Partial<DealSummary> & { id: string; title?: string }): NormalizedDeal {\n const title = typeof deal.title === 'string' && deal.title.trim().length ? deal.title.trim() : ''\n const normalizeIdList = (list: unknown): string[] => {\n if (!Array.isArray(list)) return []\n const seen = new Set<string>()\n const result: string[] = []\n list.forEach((candidate) => {\n if (typeof candidate !== 'string') return\n const trimmed = candidate.trim()\n if (!trimmed.length || seen.has(trimmed)) return\n seen.add(trimmed)\n result.push(trimmed)\n })\n return result\n }\n\n const normalizeAssignees = (entries: unknown, fallbackIds: string[]): { id: string; label: string }[] => {\n if (!Array.isArray(entries)) {\n return fallbackIds.map((id) => ({ id, label: '' }))\n }\n const seen = new Set<string>()\n const resolved: { id: string; label: string }[] = []\n entries.forEach((entry) => {\n if (!entry || typeof entry !== 'object') return\n const data = entry as Record<string, unknown>\n const id = typeof data.id === 'string' ? data.id.trim() : ''\n if (!id || seen.has(id)) return\n const label = typeof data.label === 'string' ? data.label : ''\n seen.add(id)\n resolved.push({ id, label })\n })\n if (!resolved.length && fallbackIds.length) {\n return fallbackIds.map((id) => ({ id, label: '' }))\n }\n return resolved\n }\n\n const personIds = normalizeIdList(deal.personIds ?? null)\n const companyIds = normalizeIdList(deal.companyIds ?? null)\n const people = normalizeAssignees(deal.people ?? null, personIds)\n const companies = normalizeAssignees(deal.companies ?? null, companyIds)\n\n const customValues = sanitizeCustomValues(deal.customValues ?? null)\n const customFields = sanitizeCustomFieldEntries(deal.customFields ?? null, customValues)\n\n return {\n id: deal.id,\n title,\n status: typeof deal.status === 'string' ? deal.status : deal.status ?? null,\n pipelineStage:\n typeof deal.pipelineStage === 'string' ? deal.pipelineStage : deal.pipelineStage ?? null,\n valueAmount: toNumber(deal.valueAmount ?? null),\n valueCurrency:\n typeof deal.valueCurrency === 'string' && deal.valueCurrency.trim().length\n ? deal.valueCurrency.trim().toUpperCase()\n : null,\n probability: toNumber(deal.probability ?? null),\n expectedCloseAt: toIso(deal.expectedCloseAt ?? null),\n description:\n typeof deal.description === 'string' && deal.description.trim().length\n ? deal.description\n : deal.description ?? null,\n ownerUserId:\n typeof deal.ownerUserId === 'string' && deal.ownerUserId.trim().length\n ? deal.ownerUserId\n : deal.ownerUserId ?? null,\n source:\n typeof deal.source === 'string' && deal.source.trim().length ? deal.source : deal.source ?? null,\n closureOutcome:\n typeof deal.closureOutcome === 'string' && deal.closureOutcome.trim().length\n ? deal.closureOutcome\n : deal.closureOutcome ?? null,\n lossReasonId:\n typeof deal.lossReasonId === 'string' && deal.lossReasonId.trim().length\n ? deal.lossReasonId\n : deal.lossReasonId ?? null,\n lossNotes:\n typeof deal.lossNotes === 'string' && deal.lossNotes.trim().length\n ? deal.lossNotes\n : deal.lossNotes ?? null,\n createdAt: toIso(deal.createdAt ?? null),\n updatedAt: toIso(deal.updatedAt ?? null),\n personIds,\n companyIds,\n people,\n companies,\n customValues,\n customFields,\n }\n}\n\nfunction formatValueLabel(amount: number | null, currency: string | null, emptyLabel: string): string {\n if (typeof amount === 'number') {\n const formatter = new Intl.NumberFormat(undefined, {\n style: currency ? 'currency' : 'decimal',\n currency: currency ?? undefined,\n maximumFractionDigits: 2,\n })\n try {\n return formatter.format(amount)\n } catch {\n return currency ? `${amount} ${currency}` : `${amount}`\n }\n }\n return emptyLabel\n}\n\nexport type DealsSectionProps = {\n scope: DealsScope | null\n addActionLabel: string\n emptyLabel: string\n emptyState: TabEmptyStateConfig\n onCountDelta?: (delta: number) => void\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => Promise<void> | void\n translator?: Translator\n runGuardedMutation?: GuardedMutationRunner\n}\n\nexport function DealsSection({\n scope,\n addActionLabel,\n emptyLabel,\n emptyState,\n onCountDelta,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n translator,\n runGuardedMutation,\n}: DealsSectionProps) {\n const tHook = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t: Translator = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator])\n useCurrencyDictionary()\n const scopeVersion = useOrganizationScopeVersion()\n const statusDictionaryQuery = useCustomerDictionary('deal-statuses', scopeVersion)\n const statusDictionaryMap = statusDictionaryQuery.data?.map ?? null\n const customFieldResources = useCustomFieldDisplay(E.customers.customer_deal)\n\n const [deals, setDeals] = React.useState<NormalizedDeal[]>([])\n const [isLoading, setIsLoading] = React.useState<boolean>(() => Boolean(scope))\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [pendingAction, setPendingAction] = React.useState<PendingAction | null>(null)\n const [hasMore, setHasMore] = React.useState(false)\n const pageRef = React.useRef(0)\n const hasMoreRef = React.useRef(true)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [initialValues, setInitialValues] = React.useState<\n Partial<DealFormBaseValues & Record<string, unknown>> | undefined\n >(undefined)\n const pendingCounterRef = React.useRef(0)\n\n const pushLoading = React.useCallback(() => {\n pendingCounterRef.current += 1\n if (pendingCounterRef.current === 1) onLoadingChange?.(true)\n }, [onLoadingChange])\n\n const popLoading = React.useCallback(() => {\n pendingCounterRef.current = Math.max(0, pendingCounterRef.current - 1)\n if (pendingCounterRef.current === 0) onLoadingChange?.(false)\n }, [onLoadingChange])\n\n const translate = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) => {\n const value = t(key, fallback, params)\n return value === key && fallback ? fallback : value\n },\n [t],\n )\n const runWriteMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n if (!runGuardedMutation) {\n return operation()\n }\n return runGuardedMutation(operation, mutationPayload)\n },\n [runGuardedMutation],\n )\n const refreshParentData = React.useCallback(async () => {\n await Promise.resolve(onDataRefresh?.())\n }, [onDataRefresh])\n\n const loadDeals = React.useCallback(async ({ append }: { append: boolean }) => {\n if (!scope) {\n setDeals([])\n setLoadError(null)\n setHasMore(false)\n setIsLoading(false)\n pendingCounterRef.current = 0\n onLoadingChange?.(false)\n pageRef.current = 0\n hasMoreRef.current = false\n return\n }\n if (append && !hasMoreRef.current) return\n const nextPage = append ? pageRef.current + 1 : 1\n if (!append) {\n pageRef.current = 0\n hasMoreRef.current = true\n setHasMore(true)\n setDeals([])\n }\n pushLoading()\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: String(nextPage),\n pageSize: String(DEALS_PAGE_SIZE),\n sortField: 'updatedAt',\n sortDir: 'desc',\n })\n if (scope.kind === 'person') params.set('personEntityId', scope.entityId)\n if (scope.kind === 'company') params.set('companyEntityId', scope.entityId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals?${params.toString()}`,\n undefined,\n { errorMessage: translate('customers.people.detail.deals.loadError', 'Failed to load deals.') },\n )\n const rawItems = Array.isArray(payload?.items) ? payload.items : []\n const mapped: NormalizedDeal[] = rawItems.map((item: unknown) => {\n const record = (item && typeof item === 'object') ? (item as Record<string, unknown>) : {}\n const id =\n typeof record.id === 'string' && record.id.trim().length ? record.id : generateTempId()\n const title =\n typeof record.title === 'string' && record.title.trim().length ? record.title.trim() : ''\n const status =\n typeof record.status === 'string' && record.status.trim().length\n ? record.status.trim()\n : null\n const pipelineStage =\n typeof record.pipelineStage === 'string' && record.pipelineStage.trim().length\n ? record.pipelineStage.trim()\n : typeof record.pipeline_stage === 'string' && record.pipeline_stage.trim().length\n ? record.pipeline_stage.trim()\n : null\n const valueAmount = toNumber(record.valueAmount ?? record.value_amount)\n const valueCurrencyRaw = record.valueCurrency ?? record.value_currency ?? null\n const valueCurrency =\n typeof valueCurrencyRaw === 'string' && valueCurrencyRaw.trim().length\n ? valueCurrencyRaw.trim().toUpperCase()\n : null\n const probability = toNumber(record.probability)\n const expectedCloseAt =\n typeof record.expectedCloseAt === 'string' && record.expectedCloseAt.trim().length\n ? record.expectedCloseAt\n : typeof record.expected_close_at === 'string' && record.expected_close_at.trim().length\n ? record.expected_close_at\n : null\n const description =\n typeof record.description === 'string' && record.description.trim().length\n ? record.description\n : null\n const ownerUserId =\n typeof record.ownerUserId === 'string' && record.ownerUserId.trim().length\n ? record.ownerUserId\n : typeof record.owner_user_id === 'string' && record.owner_user_id.trim().length\n ? record.owner_user_id\n : null\n const source =\n typeof record.source === 'string' && record.source.trim().length\n ? record.source\n : null\n const closureOutcome =\n typeof record.closureOutcome === 'string' && record.closureOutcome.trim().length\n ? record.closureOutcome\n : typeof record.closure_outcome === 'string' && record.closure_outcome.trim().length\n ? record.closure_outcome\n : null\n const lossReasonId =\n typeof record.lossReasonId === 'string' && record.lossReasonId.trim().length\n ? record.lossReasonId\n : typeof record.loss_reason_id === 'string' && record.loss_reason_id.trim().length\n ? record.loss_reason_id\n : null\n const lossNotes =\n typeof record.lossNotes === 'string' && record.lossNotes.trim().length\n ? record.lossNotes\n : typeof record.loss_notes === 'string' && record.loss_notes.trim().length\n ? record.loss_notes\n : null\n const createdAt =\n typeof record.createdAt === 'string' && record.createdAt.trim().length\n ? record.createdAt\n : typeof record.created_at === 'string' && record.created_at.trim().length\n ? record.created_at\n : null\n const updatedAt =\n typeof record.updatedAt === 'string' && record.updatedAt.trim().length\n ? record.updatedAt\n : typeof record.updated_at === 'string' && record.updated_at.trim().length\n ? record.updated_at\n : null\n const personIds = Array.isArray(record.personIds)\n ? record.personIds\n : Array.isArray(record.person_ids)\n ? record.person_ids\n : []\n const people = Array.isArray(record.people) ? record.people : []\n const companyIds = Array.isArray(record.companyIds)\n ? record.companyIds\n : Array.isArray(record.company_ids)\n ? record.company_ids\n : []\n const companies = Array.isArray(record.companies) ? record.companies : []\n const customValues = sanitizeCustomValues(\n record.customValues ?? record.custom_values ?? null,\n )\n const customFieldEntries = sanitizeCustomFieldEntries(\n record.customFields ?? record.custom_fields ?? null,\n customValues,\n )\n return normalizeDeal({\n id,\n title,\n status,\n pipelineStage,\n valueAmount,\n valueCurrency,\n probability,\n expectedCloseAt,\n description,\n ownerUserId,\n source,\n closureOutcome,\n lossReasonId,\n lossNotes,\n createdAt,\n updatedAt,\n personIds: personIds as string[] | undefined,\n people: people as { id: string; label: string }[] | undefined,\n companyIds: companyIds as string[] | undefined,\n companies: companies as { id: string; label: string }[] | undefined,\n customValues,\n customFields: customFieldEntries,\n })\n })\n setDeals((prev) => {\n if (!append) return mapped\n const mappedById = new Map(mapped.map((deal) => [deal.id, deal]))\n const prevIds = new Set(prev.map((deal) => deal.id))\n const updatedPrev = prev.map((deal) => mappedById.get(deal.id) ?? deal)\n const appended = mapped.filter((deal) => !prevIds.has(deal.id))\n return [...updatedPrev, ...appended]\n })\n pageRef.current = nextPage\n const totalPagesRaw = payload?.totalPages\n const totalPages =\n typeof totalPagesRaw === 'number'\n ? totalPagesRaw\n : typeof totalPagesRaw === 'string' && totalPagesRaw.trim().length\n ? Number(totalPagesRaw)\n : null\n const nextHasMore =\n totalPages && Number.isFinite(totalPages)\n ? nextPage < totalPages\n : mapped.length === DEALS_PAGE_SIZE\n hasMoreRef.current = nextHasMore\n setHasMore(nextHasMore)\n setLoadError(null)\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : translate('customers.people.detail.deals.loadError', 'Failed to load deals.')\n setLoadError(message)\n if (!append) {\n setHasMore(false)\n hasMoreRef.current = false\n }\n } finally {\n setIsLoading(false)\n popLoading()\n }\n }, [popLoading, pushLoading, scope, translate])\n\n React.useEffect(() => {\n loadDeals({ append: false }).catch(() => {})\n }, [loadDeals, scope])\n\n const openCreateDialog = React.useCallback(() => {\n if (!scope) return\n setInitialValues({\n personIds: scope.kind === 'person' ? [scope.entityId] : [],\n companyIds: scope.kind === 'company' ? [scope.entityId] : [],\n })\n setDialogOpen(true)\n }, [scope])\n\n const [linkDialogOpen, setLinkDialogOpen] = React.useState(false)\n const openLinkDialog = React.useCallback(() => {\n if (!scope) return\n setLinkDialogOpen(true)\n }, [scope])\n\n const existingDealIds = React.useMemo(() => deals.map((deal) => deal.id), [deals])\n\n const dealLinkAdapter = React.useMemo(\n () =>\n createDealLinkAdapter({\n dialogTitle: t('customers.linking.deal.dialogTitle', 'Link deal'),\n dialogSubtitle: t(\n 'customers.linking.deal.dialogSubtitle',\n 'Link an existing deal to this record',\n ),\n sectionLabel: t('customers.linking.deal.sectionLabel', 'MATCHING DEALS'),\n searchPlaceholder: t('customers.linking.deal.searchPlaceholder', 'Search all deals\u2026'),\n searchEmptyHint: t('customers.linking.deal.searchEmpty', 'No matching deals found.'),\n selectedEmptyHint: t('customers.linking.deal.selectedEmpty', 'No deals selected.'),\n confirmButtonLabel: t('customers.linking.deal.confirmButton', 'Link deal'),\n orphanWarningTitle: t('customers.linking.deal.orphanWarningTitle', 'Deal without company'),\n orphanWarningMessage: t(\n 'customers.linking.deal.orphanWarning',\n 'This deal has no other linked entities. If you unlink it later, it will become unreachable.',\n ),\n contextEntityId: scope?.entityId,\n }),\n [scope?.entityId, t],\n )\n\n const handleLinkConfirm = React.useCallback(\n async ({\n addedIds,\n removedIds,\n }: {\n addedIds: string[]\n removedIds: string[]\n }) => {\n if (!scope) return\n if (!addedIds.length && !removedIds.length) return\n pushLoading()\n try {\n for (const dealId of addedIds) {\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals/${encodeURIComponent(dealId)}`,\n )\n const currentPersonIds = Array.isArray(payload.personIds)\n ? (payload.personIds as string[]).filter((id) => typeof id === 'string')\n : []\n const currentCompanyIds = Array.isArray(payload.companyIds)\n ? (payload.companyIds as string[]).filter((id) => typeof id === 'string')\n : []\n const nextPersonIds =\n scope.kind === 'person' && !currentPersonIds.includes(scope.entityId)\n ? [...currentPersonIds, scope.entityId]\n : currentPersonIds\n const nextCompanyIds =\n scope.kind === 'company' && !currentCompanyIds.includes(scope.entityId)\n ? [...currentCompanyIds, scope.entityId]\n : currentCompanyIds\n await runWriteMutation(\n () =>\n updateCrud(\n 'customers/deals',\n { id: dealId, personIds: nextPersonIds, companyIds: nextCompanyIds },\n {\n errorMessage: t(\n 'customers.people.detail.deals.linkError',\n 'Failed to link deal.',\n ),\n },\n ),\n { dealId, scopeKind: scope.kind, scopeEntityId: scope.entityId, operation: 'linkDeal' },\n )\n }\n for (const dealId of removedIds) {\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals/${encodeURIComponent(dealId)}`,\n )\n const currentPersonIds = Array.isArray(payload.personIds)\n ? (payload.personIds as string[]).filter((id) => typeof id === 'string')\n : []\n const currentCompanyIds = Array.isArray(payload.companyIds)\n ? (payload.companyIds as string[]).filter((id) => typeof id === 'string')\n : []\n const nextPersonIds =\n scope.kind === 'person'\n ? currentPersonIds.filter((id) => id !== scope.entityId)\n : currentPersonIds\n const nextCompanyIds =\n scope.kind === 'company'\n ? currentCompanyIds.filter((id) => id !== scope.entityId)\n : currentCompanyIds\n await runWriteMutation(\n () =>\n updateCrud(\n 'customers/deals',\n { id: dealId, personIds: nextPersonIds, companyIds: nextCompanyIds },\n {\n errorMessage: t(\n 'customers.people.detail.deals.unlinkError',\n 'Failed to unlink deal.',\n ),\n },\n ),\n { dealId, scopeKind: scope.kind, scopeEntityId: scope.entityId, operation: 'unlinkDeal' },\n )\n }\n await loadDeals({ append: false })\n await refreshParentData()\n if (addedIds.length + removedIds.length > 0) {\n onCountDelta?.(addedIds.length - removedIds.length)\n }\n flash(\n addedIds.length > 0\n ? t('customers.people.detail.deals.linkSuccess', 'Deal linked.')\n : t('customers.people.detail.deals.unlinkSuccess', 'Deal unlinked.'),\n 'success',\n )\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.people.detail.deals.linkError', 'Failed to link deal.')\n flash(message, 'error')\n } finally {\n popLoading()\n }\n },\n [loadDeals, onCountDelta, popLoading, pushLoading, refreshParentData, runWriteMutation, scope, t],\n )\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n setInitialValues(undefined)\n }, [])\n\n const handleDialogOpenChange = React.useCallback(\n (next: boolean) => {\n if (!next) closeDialog()\n else setDialogOpen(true)\n },\n [closeDialog],\n )\n\n const handleCreate = React.useCallback(\n async ({ base, custom }: DealFormSubmitPayload) => {\n if (!scope) {\n throw new Error(translate('customers.people.detail.deals.error', 'Failed to save deal.'))\n }\n setPendingAction({ kind: 'create' })\n pushLoading()\n try {\n const personIds = mergeIds(base.personIds)\n const companyIds = mergeIds(base.companyIds)\n\n const payload: Record<string, unknown> = {\n title: base.title,\n status: base.status ?? undefined,\n pipelineStage: base.pipelineStage ?? undefined,\n valueAmount: typeof base.valueAmount === 'number' ? base.valueAmount : undefined,\n valueCurrency: base.valueCurrency ?? undefined,\n probability: typeof base.probability === 'number' ? base.probability : undefined,\n expectedCloseAt: base.expectedCloseAt ?? undefined,\n description: base.description ?? undefined,\n personIds,\n companyIds,\n }\n if (Object.keys(custom).length) payload.customFields = custom\n const { result } = await runWriteMutation(\n () =>\n createCrud<{ id?: string }>('customers/deals', payload, {\n errorMessage: translate('customers.people.detail.deals.error', 'Failed to save deal.'),\n }),\n {\n ...payload,\n operation: 'createDeal',\n },\n )\n const dealId =\n typeof result?.id === 'string' && result.id.trim().length ? result.id : generateTempId()\n const belongsToScope =\n (scope.kind !== 'person' || personIds.includes(scope.entityId)) &&\n (scope.kind !== 'company' || companyIds.includes(scope.entityId))\n if (belongsToScope) {\n const customValuesForState = sanitizeCustomValues(\n Object.keys(custom).length ? custom : null,\n )\n const timestamp = new Date().toISOString()\n const normalized = normalizeDeal({\n id: dealId,\n title: base.title,\n status: base.status ?? null,\n pipelineStage: base.pipelineStage ?? null,\n valueAmount: base.valueAmount ?? null,\n valueCurrency: base.valueCurrency ?? null,\n probability: base.probability ?? null,\n expectedCloseAt: base.expectedCloseAt ?? null,\n description: base.description ?? null,\n personIds,\n companyIds,\n customValues: customValuesForState,\n createdAt: timestamp,\n updatedAt: timestamp,\n })\n setDeals((prev) => [normalized, ...prev])\n onCountDelta?.(1)\n }\n await refreshParentData()\n flash(translate('customers.people.detail.deals.success', 'Deal created.'), 'success')\n } finally {\n setPendingAction(null)\n popLoading()\n }\n },\n [onCountDelta, popLoading, pushLoading, refreshParentData, runWriteMutation, scope, translate],\n )\n\n const handleUnlink = React.useCallback(\n async (deal: NormalizedDeal) => {\n if (!scope) return\n\n const nextPersonIds =\n scope.kind === 'person'\n ? deal.personIds.filter((id) => id !== scope.entityId)\n : deal.personIds\n const nextCompanyIds =\n scope.kind === 'company'\n ? deal.companyIds.filter((id) => id !== scope.entityId)\n : deal.companyIds\n\n const confirmed = await confirm({\n title:\n scope.kind === 'person'\n ? translate('customers.people.detail.deals.unlinkConfirmPerson', 'Unlink this deal from this person?')\n : translate('customers.people.detail.deals.unlinkConfirmCompany', 'Unlink this deal from this company?'),\n description: translate(\n 'customers.people.detail.deals.unlinkDescription',\n 'The deal will remain available from its own detail page.',\n ),\n confirmText: translate('customers.people.card.unlink', 'Unlink'),\n cancelText: translate('customers.people.detail.deals.cancel', 'Cancel'),\n })\n if (!confirmed) return\n\n setPendingAction({ kind: 'remove', id: deal.id })\n pushLoading()\n try {\n const payload: Record<string, unknown> = {\n id: deal.id,\n personIds: nextPersonIds,\n companyIds: nextCompanyIds,\n }\n await runWriteMutation(\n () =>\n updateCrud('customers/deals', payload, {\n errorMessage: translate('customers.people.detail.deals.unlinkError', 'Failed to unlink deal.'),\n }),\n {\n ...payload,\n operation: 'unlinkDeal',\n },\n )\n setDeals((prev) => prev.filter((item) => item.id !== deal.id))\n onCountDelta?.(-1)\n await refreshParentData()\n flash(translate('customers.people.detail.deals.unlinkSuccess', 'Deal unlinked.'), 'success')\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : translate('customers.people.detail.deals.unlinkError', 'Failed to unlink deal.')\n flash(message, 'error')\n } finally {\n setPendingAction(null)\n popLoading()\n }\n },\n [confirm, onCountDelta, popLoading, pushLoading, refreshParentData, runWriteMutation, scope, translate],\n )\n\n const handleDialogSubmit = React.useCallback(\n async (payload: DealFormSubmitPayload) => {\n try {\n await handleCreate(payload)\n closeDialog()\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : translate('customers.people.detail.deals.error', 'Failed to save deal.')\n flash(message, 'error')\n }\n },\n [closeDialog, handleCreate, translate],\n )\n\n React.useEffect(() => {\n if (!onActionChange) return\n const disabled = !scope || isLoading || pendingAction !== null\n const action: SectionAction = {\n label: addActionLabel,\n onClick: () => {\n if (!disabled) openCreateDialog()\n },\n disabled,\n }\n onActionChange(action)\n return () => {\n onActionChange(null)\n }\n }, [addActionLabel, isLoading, onActionChange, openCreateDialog, pendingAction, scope])\n\n const isFormPending = pendingAction?.kind === 'create'\n\n const sortedDeals = React.useMemo(() => {\n return [...deals].sort((a, b) => {\n const timeA = a.updatedAt ?? a.createdAt ?? ''\n const timeB = b.updatedAt ?? b.createdAt ?? ''\n return timeB.localeCompare(timeA)\n })\n }, [deals])\n\n return (\n <div className=\"mt-4 space-y-4\">\n {loadError ? (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive\">\n {loadError}\n </div>\n ) : null}\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openLinkDialog}\n disabled={!scope || pendingAction !== null}\n >\n <Link2 className=\"mr-1.5 size-3.5\" />\n {t('customers.people.detail.deals.linkExisting', 'Link existing deal')}\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n onClick={openCreateDialog}\n disabled={!scope || pendingAction !== null}\n >\n <Plus className=\"mr-1.5 size-3.5\" />\n {addActionLabel}\n </Button>\n </div>\n {isLoading && sortedDeals.length === 0 ? (\n <LoadingMessage\n label={t('customers.people.detail.deals.loading', 'Loading deals\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : (\n <>\n {!isLoading && sortedDeals.length === 0 ? (\n <TabEmptyState\n title={emptyState.title}\n action={{\n label: emptyState.actionLabel,\n onClick: openCreateDialog,\n disabled: !scope || pendingAction !== null,\n }}\n />\n ) : null}\n <div className=\"space-y-4\">\n {sortedDeals.map((deal) => {\n const valueLabel = formatValueLabel(deal.valueAmount, deal.valueCurrency ?? null, emptyLabel)\n const expectedLabel = deal.expectedCloseAt ? formatDate(deal.expectedCloseAt) ?? emptyLabel : emptyLabel\n const probabilityLabel =\n typeof deal.probability === 'number' ? `${deal.probability}%` : emptyLabel\n const isUnlinkPending = pendingAction?.kind === 'remove' && pendingAction.id === deal.id\n const statusLabel =\n deal.status && statusDictionaryMap\n ? statusDictionaryMap[deal.status]?.label ?? deal.status\n : deal.status ?? emptyLabel\n return (\n <article key={deal.id} className=\"group rounded-lg border bg-card p-4 shadow-xs transition hover:border-border/80\">\n <header className=\"flex flex-wrap items-center justify-between gap-2\">\n <div>\n <Link\n href={`/backend/customers/deals/${encodeURIComponent(deal.id)}`}\n className=\"text-base font-semibold text-foreground transition hover:text-primary hover:underline\"\n >\n {deal.title || emptyLabel}\n </Link>\n {deal.description ? (\n <p className=\"mt-1 text-sm text-muted-foreground whitespace-pre-wrap\">{deal.description}</p>\n ) : null}\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium uppercase text-muted-foreground\">\n {statusLabel}\n </span>\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n <IconButton asChild variant=\"ghost\" size=\"sm\" className=\"h-8 w-8\">\n <Link\n href={`/backend/customers/deals/${encodeURIComponent(deal.id)}`}\n aria-label={t('customers.people.detail.deals.edit', 'Open deal details')}\n >\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 rounded-lg px-3 text-xs\"\n onClick={(event) => {\n event.preventDefault()\n void handleUnlink(deal)\n }}\n disabled={pendingAction !== null}\n >\n {isUnlinkPending\n ? t('customers.people.detail.deals.unlinking', 'Unlinking\u2026')\n : t('customers.people.card.unlink', 'Unlink')}\n </Button>\n </div>\n </div>\n </header>\n <dl className=\"mt-3 grid gap-2 text-xs text-muted-foreground sm:grid-cols-2\">\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.pipelineStage', 'Pipeline stage')}\n </dt>\n <dd>{deal.pipelineStage ?? emptyLabel}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.probability', 'Probability')}\n </dt>\n <dd>{probabilityLabel}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.valueAmount', 'Value')}\n </dt>\n <dd>{valueLabel}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.expectedCloseAt', 'Expected close')}\n </dt>\n <dd>{expectedLabel}</dd>\n </div>\n </dl>\n <CustomFieldValuesList\n entries={deal.customFields?.map((entry) => ({\n key: entry.key,\n value: entry.value,\n label: entry.label ?? undefined,\n }))}\n values={deal.customValues ?? undefined}\n resources={customFieldResources}\n emptyLabel={emptyLabel}\n itemKeyPrefix={`deal-${deal.id}-field`}\n className=\"mt-3\"\n />\n </article>\n )\n })}\n {isLoading && deals.length > 0 ? (\n <div className=\"flex justify-center\">\n <LoadingMessage\n label={t('customers.people.detail.deals.loading', 'Loading deals\u2026')}\n className=\"border-0 bg-transparent p-0 justify-center [&_span[role='status']]:h-5 [&_span[role='status']]:w-5\"\n />\n </div>\n ) : null}\n {!isLoading && hasMore ? (\n <div className=\"flex justify-center\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n loadDeals({ append: true }).catch(() => {})\n }}\n disabled={pendingAction !== null || isLoading}\n >\n {t('customers.people.detail.deals.loadMore', 'Load more deals')}\n </Button>\n </div>\n ) : null}\n </div>\n </>\n )}\n\n <DealDialog\n open={dialogOpen}\n mode=\"create\"\n onOpenChange={handleDialogOpenChange}\n initialValues={initialValues}\n onSubmit={async (payload) => {\n await handleDialogSubmit(payload)\n }}\n isSubmitting={Boolean(isFormPending)}\n />\n <LinkEntityDialog\n open={linkDialogOpen}\n onOpenChange={setLinkDialogOpen}\n adapter={dealLinkAdapter}\n initialSelectedIds={existingDealIds}\n onConfirm={handleLinkConfirm}\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n\nexport default DealsSection\n"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Link2, Pencil, Plus } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { LinkEntityDialog } from '../linking/LinkEntityDialog'\nimport { createDealLinkAdapter } from '../linking/adapters/dealAdapter'\nimport { LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { E } from '#generated/entities.ids.generated'\nimport type { DealCustomFieldEntry, DealSummary, SectionAction, TabEmptyStateConfig, Translator } from './types'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatDate } from './utils'\nimport { DealDialog } from './DealDialog'\nimport type { DealFormBaseValues, DealFormSubmitPayload } from './DealForm'\nimport { generateTempId } from '@open-mercato/core/modules/customers/lib/detailHelpers'\nimport { useCurrencyDictionary } from './hooks/useCurrencyDictionary'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { CustomFieldValuesList } from './CustomFieldValuesList'\nimport { useCustomFieldDisplay } from './hooks/useCustomFieldDisplay'\nimport { normalizeCustomFieldKey } from './customFieldUtils'\n\nconst DEALS_PAGE_SIZE = 10\n\ntype DealsScope =\n | { kind: 'person'; entityId: string }\n | { kind: 'company'; entityId: string }\n\ntype GuardedMutationRunner = <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype PendingAction =\n | { kind: 'create' }\n | { kind: 'update'; id: string }\n | { kind: 'remove'; id: string }\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction sanitizeCustomValues(input: unknown): Record<string, unknown> | null {\n if (!isPlainObject(input)) return null\n const result: Record<string, unknown> = {}\n Object.entries(input).forEach(([key, value]) => {\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n result[trimmedKey] = value\n })\n return Object.keys(result).length ? result : null\n}\n\nfunction sanitizeCustomFieldEntries(\n entries: unknown,\n values: Record<string, unknown> | null,\n): DealCustomFieldEntry[] {\n const map = new Map<string, DealCustomFieldEntry>()\n if (Array.isArray(entries)) {\n entries.forEach((entry) => {\n if (!entry || typeof entry !== 'object') return\n const record = entry as Record<string, unknown>\n const keyRaw =\n typeof record.key === 'string'\n ? record.key\n : typeof record.id === 'string'\n ? record.id\n : null\n if (!keyRaw) return\n const key = keyRaw.trim()\n if (!key.length) return\n const normalizedKey = normalizeCustomFieldKey(key)\n if (!normalizedKey) return\n const label =\n typeof record.label === 'string' && record.label.trim().length\n ? record.label.trim()\n : null\n const kind =\n typeof record.kind === 'string' && record.kind.trim().length\n ? record.kind.trim()\n : null\n const multi =\n typeof record.multi === 'boolean' ? record.multi : Array.isArray(record.value) ? true : undefined\n map.set(normalizedKey, {\n key,\n label,\n value: record.value,\n kind,\n multi,\n })\n })\n }\n\n if (values) {\n Object.entries(values).forEach(([rawKey, value]) => {\n const key = rawKey.trim()\n if (!key.length) return\n const normalizedKey = normalizeCustomFieldKey(key)\n if (!normalizedKey) return\n const existing = map.get(normalizedKey)\n if (existing) {\n existing.value = value\n if (existing.multi === undefined) existing.multi = Array.isArray(value)\n } else {\n map.set(normalizedKey, {\n key,\n label: null,\n value,\n kind: null,\n multi: Array.isArray(value) ? true : undefined,\n })\n }\n })\n }\n\n return Array.from(map.values())\n}\n\ntype NormalizedDeal = Omit<\n DealSummary,\n 'valueAmount' | 'probability' | 'expectedCloseAt' | 'customValues' | 'customFields' | 'personIds' | 'companyIds'\n> & {\n valueAmount: number | null\n probability: number | null\n expectedCloseAt: string | null\n personIds: string[]\n companyIds: string[]\n customValues: Record<string, unknown> | null\n customFields: DealCustomFieldEntry[]\n}\n\nfunction toNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\nfunction toIso(value: unknown): string | null {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = new Date(trimmed)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n return null\n}\n\nfunction mergeIds(...sources: Array<unknown>): string[] {\n const set = new Set<string>()\n sources.forEach((source) => {\n if (Array.isArray(source)) {\n source.forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed.length) return\n set.add(trimmed)\n })\n }\n })\n return Array.from(set)\n}\n\nfunction normalizeDeal(deal: Partial<DealSummary> & { id: string; title?: string }): NormalizedDeal {\n const title = typeof deal.title === 'string' && deal.title.trim().length ? deal.title.trim() : ''\n const normalizeIdList = (list: unknown): string[] => {\n if (!Array.isArray(list)) return []\n const seen = new Set<string>()\n const result: string[] = []\n list.forEach((candidate) => {\n if (typeof candidate !== 'string') return\n const trimmed = candidate.trim()\n if (!trimmed.length || seen.has(trimmed)) return\n seen.add(trimmed)\n result.push(trimmed)\n })\n return result\n }\n\n const normalizeAssignees = (entries: unknown, fallbackIds: string[]): { id: string; label: string }[] => {\n if (!Array.isArray(entries)) {\n return fallbackIds.map((id) => ({ id, label: '' }))\n }\n const seen = new Set<string>()\n const resolved: { id: string; label: string }[] = []\n entries.forEach((entry) => {\n if (!entry || typeof entry !== 'object') return\n const data = entry as Record<string, unknown>\n const id = typeof data.id === 'string' ? data.id.trim() : ''\n if (!id || seen.has(id)) return\n const label = typeof data.label === 'string' ? data.label : ''\n seen.add(id)\n resolved.push({ id, label })\n })\n if (!resolved.length && fallbackIds.length) {\n return fallbackIds.map((id) => ({ id, label: '' }))\n }\n return resolved\n }\n\n const personIds = normalizeIdList(deal.personIds ?? null)\n const companyIds = normalizeIdList(deal.companyIds ?? null)\n const people = normalizeAssignees(deal.people ?? null, personIds)\n const companies = normalizeAssignees(deal.companies ?? null, companyIds)\n\n const customValues = sanitizeCustomValues(deal.customValues ?? null)\n const customFields = sanitizeCustomFieldEntries(deal.customFields ?? null, customValues)\n\n return {\n id: deal.id,\n title,\n status: typeof deal.status === 'string' ? deal.status : deal.status ?? null,\n pipelineStage:\n typeof deal.pipelineStage === 'string' ? deal.pipelineStage : deal.pipelineStage ?? null,\n valueAmount: toNumber(deal.valueAmount ?? null),\n valueCurrency:\n typeof deal.valueCurrency === 'string' && deal.valueCurrency.trim().length\n ? deal.valueCurrency.trim().toUpperCase()\n : null,\n probability: toNumber(deal.probability ?? null),\n expectedCloseAt: toIso(deal.expectedCloseAt ?? null),\n description:\n typeof deal.description === 'string' && deal.description.trim().length\n ? deal.description\n : deal.description ?? null,\n ownerUserId:\n typeof deal.ownerUserId === 'string' && deal.ownerUserId.trim().length\n ? deal.ownerUserId\n : deal.ownerUserId ?? null,\n source:\n typeof deal.source === 'string' && deal.source.trim().length ? deal.source : deal.source ?? null,\n closureOutcome:\n typeof deal.closureOutcome === 'string' && deal.closureOutcome.trim().length\n ? deal.closureOutcome\n : deal.closureOutcome ?? null,\n lossReasonId:\n typeof deal.lossReasonId === 'string' && deal.lossReasonId.trim().length\n ? deal.lossReasonId\n : deal.lossReasonId ?? null,\n lossNotes:\n typeof deal.lossNotes === 'string' && deal.lossNotes.trim().length\n ? deal.lossNotes\n : deal.lossNotes ?? null,\n createdAt: toIso(deal.createdAt ?? null),\n updatedAt: toIso(deal.updatedAt ?? null),\n personIds,\n companyIds,\n people,\n companies,\n customValues,\n customFields,\n }\n}\n\nfunction formatValueLabel(amount: number | null, currency: string | null, emptyLabel: string): string {\n if (typeof amount === 'number') {\n const formatter = new Intl.NumberFormat(undefined, {\n style: currency ? 'currency' : 'decimal',\n currency: currency ?? undefined,\n maximumFractionDigits: 2,\n })\n try {\n return formatter.format(amount)\n } catch {\n return currency ? `${amount} ${currency}` : `${amount}`\n }\n }\n return emptyLabel\n}\n\nexport type DealsSectionProps = {\n scope: DealsScope | null\n addActionLabel: string\n emptyLabel: string\n emptyState: TabEmptyStateConfig\n onCountDelta?: (delta: number) => void\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => Promise<void> | void\n translator?: Translator\n runGuardedMutation?: GuardedMutationRunner\n}\n\nexport function DealsSection({\n scope,\n addActionLabel,\n emptyLabel,\n emptyState,\n onCountDelta,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n translator,\n runGuardedMutation,\n}: DealsSectionProps) {\n const tHook = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t: Translator = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator])\n useCurrencyDictionary()\n const scopeVersion = useOrganizationScopeVersion()\n const statusDictionaryQuery = useCustomerDictionary('deal-statuses', scopeVersion)\n const statusDictionaryMap = statusDictionaryQuery.data?.map ?? null\n const customFieldResources = useCustomFieldDisplay(E.customers.customer_deal)\n\n const [deals, setDeals] = React.useState<NormalizedDeal[]>([])\n const [isLoading, setIsLoading] = React.useState<boolean>(() => Boolean(scope))\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [pendingAction, setPendingAction] = React.useState<PendingAction | null>(null)\n const [hasMore, setHasMore] = React.useState(false)\n const pageRef = React.useRef(0)\n const hasMoreRef = React.useRef(true)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [initialValues, setInitialValues] = React.useState<\n Partial<DealFormBaseValues & Record<string, unknown>> | undefined\n >(undefined)\n const pendingCounterRef = React.useRef(0)\n\n const pushLoading = React.useCallback(() => {\n pendingCounterRef.current += 1\n if (pendingCounterRef.current === 1) onLoadingChange?.(true)\n }, [onLoadingChange])\n\n const popLoading = React.useCallback(() => {\n pendingCounterRef.current = Math.max(0, pendingCounterRef.current - 1)\n if (pendingCounterRef.current === 0) onLoadingChange?.(false)\n }, [onLoadingChange])\n\n const translate = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) => {\n const value = t(key, fallback, params)\n return value === key && fallback ? fallback : value\n },\n [t],\n )\n const runWriteMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n if (!runGuardedMutation) {\n return operation()\n }\n return runGuardedMutation(operation, mutationPayload)\n },\n [runGuardedMutation],\n )\n const refreshParentData = React.useCallback(async () => {\n await Promise.resolve(onDataRefresh?.())\n }, [onDataRefresh])\n\n const loadDeals = React.useCallback(async ({ append }: { append: boolean }) => {\n if (!scope) {\n setDeals([])\n setLoadError(null)\n setHasMore(false)\n setIsLoading(false)\n pendingCounterRef.current = 0\n onLoadingChange?.(false)\n pageRef.current = 0\n hasMoreRef.current = false\n return\n }\n if (append && !hasMoreRef.current) return\n const nextPage = append ? pageRef.current + 1 : 1\n if (!append) {\n pageRef.current = 0\n hasMoreRef.current = true\n setHasMore(true)\n setDeals([])\n }\n pushLoading()\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: String(nextPage),\n pageSize: String(DEALS_PAGE_SIZE),\n sortField: 'updatedAt',\n sortDir: 'desc',\n })\n if (scope.kind === 'person') params.set('personEntityId', scope.entityId)\n if (scope.kind === 'company') params.set('companyEntityId', scope.entityId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals?${params.toString()}`,\n undefined,\n { errorMessage: translate('customers.people.detail.deals.loadError', 'Failed to load deals.') },\n )\n const rawItems = Array.isArray(payload?.items) ? payload.items : []\n const mapped: NormalizedDeal[] = rawItems.map((item: unknown) => {\n const record = (item && typeof item === 'object') ? (item as Record<string, unknown>) : {}\n const id =\n typeof record.id === 'string' && record.id.trim().length ? record.id : generateTempId()\n const title =\n typeof record.title === 'string' && record.title.trim().length ? record.title.trim() : ''\n const status =\n typeof record.status === 'string' && record.status.trim().length\n ? record.status.trim()\n : null\n const pipelineStage =\n typeof record.pipelineStage === 'string' && record.pipelineStage.trim().length\n ? record.pipelineStage.trim()\n : typeof record.pipeline_stage === 'string' && record.pipeline_stage.trim().length\n ? record.pipeline_stage.trim()\n : null\n const valueAmount = toNumber(record.valueAmount ?? record.value_amount)\n const valueCurrencyRaw = record.valueCurrency ?? record.value_currency ?? null\n const valueCurrency =\n typeof valueCurrencyRaw === 'string' && valueCurrencyRaw.trim().length\n ? valueCurrencyRaw.trim().toUpperCase()\n : null\n const probability = toNumber(record.probability)\n const expectedCloseAt =\n typeof record.expectedCloseAt === 'string' && record.expectedCloseAt.trim().length\n ? record.expectedCloseAt\n : typeof record.expected_close_at === 'string' && record.expected_close_at.trim().length\n ? record.expected_close_at\n : null\n const description =\n typeof record.description === 'string' && record.description.trim().length\n ? record.description\n : null\n const ownerUserId =\n typeof record.ownerUserId === 'string' && record.ownerUserId.trim().length\n ? record.ownerUserId\n : typeof record.owner_user_id === 'string' && record.owner_user_id.trim().length\n ? record.owner_user_id\n : null\n const source =\n typeof record.source === 'string' && record.source.trim().length\n ? record.source\n : null\n const closureOutcome =\n typeof record.closureOutcome === 'string' && record.closureOutcome.trim().length\n ? record.closureOutcome\n : typeof record.closure_outcome === 'string' && record.closure_outcome.trim().length\n ? record.closure_outcome\n : null\n const lossReasonId =\n typeof record.lossReasonId === 'string' && record.lossReasonId.trim().length\n ? record.lossReasonId\n : typeof record.loss_reason_id === 'string' && record.loss_reason_id.trim().length\n ? record.loss_reason_id\n : null\n const lossNotes =\n typeof record.lossNotes === 'string' && record.lossNotes.trim().length\n ? record.lossNotes\n : typeof record.loss_notes === 'string' && record.loss_notes.trim().length\n ? record.loss_notes\n : null\n const createdAt =\n typeof record.createdAt === 'string' && record.createdAt.trim().length\n ? record.createdAt\n : typeof record.created_at === 'string' && record.created_at.trim().length\n ? record.created_at\n : null\n const updatedAt =\n typeof record.updatedAt === 'string' && record.updatedAt.trim().length\n ? record.updatedAt\n : typeof record.updated_at === 'string' && record.updated_at.trim().length\n ? record.updated_at\n : null\n const personIds = Array.isArray(record.personIds)\n ? record.personIds\n : Array.isArray(record.person_ids)\n ? record.person_ids\n : []\n const people = Array.isArray(record.people) ? record.people : []\n const companyIds = Array.isArray(record.companyIds)\n ? record.companyIds\n : Array.isArray(record.company_ids)\n ? record.company_ids\n : []\n const companies = Array.isArray(record.companies) ? record.companies : []\n const customValues = sanitizeCustomValues(\n record.customValues ?? record.custom_values ?? null,\n )\n const customFieldEntries = sanitizeCustomFieldEntries(\n record.customFields ?? record.custom_fields ?? null,\n customValues,\n )\n return normalizeDeal({\n id,\n title,\n status,\n pipelineStage,\n valueAmount,\n valueCurrency,\n probability,\n expectedCloseAt,\n description,\n ownerUserId,\n source,\n closureOutcome,\n lossReasonId,\n lossNotes,\n createdAt,\n updatedAt,\n personIds: personIds as string[] | undefined,\n people: people as { id: string; label: string }[] | undefined,\n companyIds: companyIds as string[] | undefined,\n companies: companies as { id: string; label: string }[] | undefined,\n customValues,\n customFields: customFieldEntries,\n })\n })\n setDeals((prev) => {\n if (!append) return mapped\n const mappedById = new Map(mapped.map((deal) => [deal.id, deal]))\n const prevIds = new Set(prev.map((deal) => deal.id))\n const updatedPrev = prev.map((deal) => mappedById.get(deal.id) ?? deal)\n const appended = mapped.filter((deal) => !prevIds.has(deal.id))\n return [...updatedPrev, ...appended]\n })\n pageRef.current = nextPage\n const totalPagesRaw = payload?.totalPages\n const totalPages =\n typeof totalPagesRaw === 'number'\n ? totalPagesRaw\n : typeof totalPagesRaw === 'string' && totalPagesRaw.trim().length\n ? Number(totalPagesRaw)\n : null\n const nextHasMore =\n totalPages && Number.isFinite(totalPages)\n ? nextPage < totalPages\n : mapped.length === DEALS_PAGE_SIZE\n hasMoreRef.current = nextHasMore\n setHasMore(nextHasMore)\n setLoadError(null)\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : translate('customers.people.detail.deals.loadError', 'Failed to load deals.')\n setLoadError(message)\n if (!append) {\n setHasMore(false)\n hasMoreRef.current = false\n }\n } finally {\n setIsLoading(false)\n popLoading()\n }\n }, [popLoading, pushLoading, scope, translate])\n\n React.useEffect(() => {\n loadDeals({ append: false }).catch(() => {})\n }, [loadDeals, scope])\n\n const openCreateDialog = React.useCallback(() => {\n if (!scope) return\n setInitialValues({\n personIds: scope.kind === 'person' ? [scope.entityId] : [],\n companyIds: scope.kind === 'company' ? [scope.entityId] : [],\n })\n setDialogOpen(true)\n }, [scope])\n\n const [linkDialogOpen, setLinkDialogOpen] = React.useState(false)\n const openLinkDialog = React.useCallback(() => {\n if (!scope) return\n setLinkDialogOpen(true)\n }, [scope])\n\n const existingDealIds = React.useMemo(() => deals.map((deal) => deal.id), [deals])\n\n const dealLinkAdapter = React.useMemo(\n () =>\n createDealLinkAdapter({\n dialogTitle: t('customers.linking.deal.dialogTitle', 'Link deal'),\n dialogSubtitle: t(\n 'customers.linking.deal.dialogSubtitle',\n 'Link an existing deal to this record',\n ),\n sectionLabel: t('customers.linking.deal.sectionLabel', 'MATCHING DEALS'),\n searchPlaceholder: t('customers.linking.deal.searchPlaceholder', 'Search all deals\u2026'),\n searchEmptyHint: t('customers.linking.deal.searchEmpty', 'No matching deals found.'),\n selectedEmptyHint: t('customers.linking.deal.selectedEmpty', 'No deals selected.'),\n confirmButtonLabel: t('customers.linking.deal.confirmButton', 'Link deal'),\n orphanWarningTitle: t('customers.linking.deal.orphanWarningTitle', 'Deal without company'),\n orphanWarningMessage: t(\n 'customers.linking.deal.orphanWarning',\n 'This deal has no other linked entities. If you unlink it later, it will become unreachable.',\n ),\n contextEntityId: scope?.entityId,\n }),\n [scope?.entityId, t],\n )\n\n const handleLinkConfirm = React.useCallback(\n async ({\n addedIds,\n removedIds,\n }: {\n addedIds: string[]\n removedIds: string[]\n }) => {\n if (!scope) return\n if (!addedIds.length && !removedIds.length) return\n pushLoading()\n try {\n for (const dealId of addedIds) {\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals/${encodeURIComponent(dealId)}`,\n )\n const currentPersonIds = Array.isArray(payload.personIds)\n ? (payload.personIds as string[]).filter((id) => typeof id === 'string')\n : []\n const currentCompanyIds = Array.isArray(payload.companyIds)\n ? (payload.companyIds as string[]).filter((id) => typeof id === 'string')\n : []\n const nextPersonIds =\n scope.kind === 'person' && !currentPersonIds.includes(scope.entityId)\n ? [...currentPersonIds, scope.entityId]\n : currentPersonIds\n const nextCompanyIds =\n scope.kind === 'company' && !currentCompanyIds.includes(scope.entityId)\n ? [...currentCompanyIds, scope.entityId]\n : currentCompanyIds\n await runWriteMutation(\n () =>\n updateCrud(\n 'customers/deals',\n { id: dealId, personIds: nextPersonIds, companyIds: nextCompanyIds },\n {\n errorMessage: t(\n 'customers.people.detail.deals.linkError',\n 'Failed to link deal.',\n ),\n },\n ),\n { dealId, scopeKind: scope.kind, scopeEntityId: scope.entityId, operation: 'linkDeal' },\n )\n }\n for (const dealId of removedIds) {\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals/${encodeURIComponent(dealId)}`,\n )\n const currentPersonIds = Array.isArray(payload.personIds)\n ? (payload.personIds as string[]).filter((id) => typeof id === 'string')\n : []\n const currentCompanyIds = Array.isArray(payload.companyIds)\n ? (payload.companyIds as string[]).filter((id) => typeof id === 'string')\n : []\n const nextPersonIds =\n scope.kind === 'person'\n ? currentPersonIds.filter((id) => id !== scope.entityId)\n : currentPersonIds\n const nextCompanyIds =\n scope.kind === 'company'\n ? currentCompanyIds.filter((id) => id !== scope.entityId)\n : currentCompanyIds\n await runWriteMutation(\n () =>\n updateCrud(\n 'customers/deals',\n { id: dealId, personIds: nextPersonIds, companyIds: nextCompanyIds },\n {\n errorMessage: t(\n 'customers.people.detail.deals.unlinkError',\n 'Failed to unlink deal.',\n ),\n },\n ),\n { dealId, scopeKind: scope.kind, scopeEntityId: scope.entityId, operation: 'unlinkDeal' },\n )\n }\n await loadDeals({ append: false })\n await refreshParentData()\n if (addedIds.length + removedIds.length > 0) {\n onCountDelta?.(addedIds.length - removedIds.length)\n }\n flash(\n addedIds.length > 0\n ? t('customers.people.detail.deals.linkSuccess', 'Deal linked.')\n : t('customers.people.detail.deals.unlinkSuccess', 'Deal unlinked.'),\n 'success',\n )\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.people.detail.deals.linkError', 'Failed to link deal.')\n flash(message, 'error')\n } finally {\n popLoading()\n }\n },\n [loadDeals, onCountDelta, popLoading, pushLoading, refreshParentData, runWriteMutation, scope, t],\n )\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n setInitialValues(undefined)\n }, [])\n\n const handleDialogOpenChange = React.useCallback(\n (next: boolean) => {\n if (!next) closeDialog()\n else setDialogOpen(true)\n },\n [closeDialog],\n )\n\n const handleCreate = React.useCallback(\n async ({ base, custom }: DealFormSubmitPayload) => {\n if (!scope) {\n throw new Error(translate('customers.people.detail.deals.error', 'Failed to save deal.'))\n }\n setPendingAction({ kind: 'create' })\n pushLoading()\n try {\n const personIds = mergeIds(base.personIds)\n const companyIds = mergeIds(base.companyIds)\n\n const payload: Record<string, unknown> = {\n title: base.title,\n status: base.status ?? undefined,\n pipelineStage: base.pipelineStage ?? undefined,\n valueAmount: typeof base.valueAmount === 'number' ? base.valueAmount : undefined,\n valueCurrency: base.valueCurrency ?? undefined,\n probability: typeof base.probability === 'number' ? base.probability : undefined,\n expectedCloseAt: base.expectedCloseAt ?? undefined,\n description: base.description ?? undefined,\n personIds,\n companyIds,\n }\n if (Object.keys(custom).length) payload.customFields = custom\n const { result } = await runWriteMutation(\n () =>\n createCrud<{ id?: string }>('customers/deals', payload, {\n errorMessage: translate('customers.people.detail.deals.error', 'Failed to save deal.'),\n }),\n {\n ...payload,\n operation: 'createDeal',\n },\n )\n const dealId =\n typeof result?.id === 'string' && result.id.trim().length ? result.id : generateTempId()\n const belongsToScope =\n (scope.kind !== 'person' || personIds.includes(scope.entityId)) &&\n (scope.kind !== 'company' || companyIds.includes(scope.entityId))\n if (belongsToScope) {\n const customValuesForState = sanitizeCustomValues(\n Object.keys(custom).length ? custom : null,\n )\n const timestamp = new Date().toISOString()\n const normalized = normalizeDeal({\n id: dealId,\n title: base.title,\n status: base.status ?? null,\n pipelineStage: base.pipelineStage ?? null,\n valueAmount: base.valueAmount ?? null,\n valueCurrency: base.valueCurrency ?? null,\n probability: base.probability ?? null,\n expectedCloseAt: base.expectedCloseAt ?? null,\n description: base.description ?? null,\n personIds,\n companyIds,\n customValues: customValuesForState,\n createdAt: timestamp,\n updatedAt: timestamp,\n })\n setDeals((prev) => [normalized, ...prev])\n onCountDelta?.(1)\n }\n await refreshParentData()\n flash(translate('customers.people.detail.deals.success', 'Deal created.'), 'success')\n } finally {\n setPendingAction(null)\n popLoading()\n }\n },\n [onCountDelta, popLoading, pushLoading, refreshParentData, runWriteMutation, scope, translate],\n )\n\n const handleUnlink = React.useCallback(\n async (deal: NormalizedDeal) => {\n if (!scope) return\n\n const nextPersonIds =\n scope.kind === 'person'\n ? deal.personIds.filter((id) => id !== scope.entityId)\n : deal.personIds\n const nextCompanyIds =\n scope.kind === 'company'\n ? deal.companyIds.filter((id) => id !== scope.entityId)\n : deal.companyIds\n\n const confirmed = await confirm({\n title:\n scope.kind === 'person'\n ? translate('customers.people.detail.deals.unlinkConfirmPerson', 'Unlink this deal from this person?')\n : translate('customers.people.detail.deals.unlinkConfirmCompany', 'Unlink this deal from this company?'),\n description: translate(\n 'customers.people.detail.deals.unlinkDescription',\n 'The deal will remain available from its own detail page.',\n ),\n confirmText: translate('customers.people.card.unlink', 'Unlink'),\n cancelText: translate('customers.people.detail.deals.cancel', 'Cancel'),\n })\n if (!confirmed) return\n\n setPendingAction({ kind: 'remove', id: deal.id })\n pushLoading()\n try {\n const payload: Record<string, unknown> = {\n id: deal.id,\n personIds: nextPersonIds,\n companyIds: nextCompanyIds,\n }\n await runWriteMutation(\n () =>\n updateCrud('customers/deals', payload, {\n errorMessage: translate('customers.people.detail.deals.unlinkError', 'Failed to unlink deal.'),\n }),\n {\n ...payload,\n operation: 'unlinkDeal',\n },\n )\n setDeals((prev) => prev.filter((item) => item.id !== deal.id))\n onCountDelta?.(-1)\n await refreshParentData()\n flash(translate('customers.people.detail.deals.unlinkSuccess', 'Deal unlinked.'), 'success')\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : translate('customers.people.detail.deals.unlinkError', 'Failed to unlink deal.')\n flash(message, 'error')\n } finally {\n setPendingAction(null)\n popLoading()\n }\n },\n [confirm, onCountDelta, popLoading, pushLoading, refreshParentData, runWriteMutation, scope, translate],\n )\n\n const handleDialogSubmit = React.useCallback(\n async (payload: DealFormSubmitPayload) => {\n try {\n await handleCreate(payload)\n closeDialog()\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : translate('customers.people.detail.deals.error', 'Failed to save deal.')\n flash(message, 'error')\n }\n },\n [closeDialog, handleCreate, translate],\n )\n\n React.useEffect(() => {\n if (!onActionChange) return\n const disabled = !scope || isLoading || pendingAction !== null\n const action: SectionAction = {\n label: addActionLabel,\n onClick: () => {\n if (!disabled) openCreateDialog()\n },\n disabled,\n }\n onActionChange(action)\n return () => {\n onActionChange(null)\n }\n }, [addActionLabel, isLoading, onActionChange, openCreateDialog, pendingAction, scope])\n\n const isFormPending = pendingAction?.kind === 'create'\n\n const sortedDeals = React.useMemo(() => {\n return [...deals].sort((a, b) => {\n const timeA = a.updatedAt ?? a.createdAt ?? ''\n const timeB = b.updatedAt ?? b.createdAt ?? ''\n return timeB.localeCompare(timeA)\n })\n }, [deals])\n\n return (\n <div className=\"mt-4 space-y-4\">\n {loadError ? (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive\">\n {loadError}\n </div>\n ) : null}\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openLinkDialog}\n disabled={!scope || pendingAction !== null}\n >\n <Link2 className=\"mr-1.5 size-3.5\" />\n {t('customers.people.detail.deals.linkExisting', 'Link existing deal')}\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n onClick={openCreateDialog}\n disabled={!scope || pendingAction !== null}\n >\n <Plus className=\"mr-1.5 size-3.5\" />\n {addActionLabel}\n </Button>\n </div>\n {isLoading && sortedDeals.length === 0 ? (\n <LoadingMessage\n label={t('customers.people.detail.deals.loading', 'Loading deals\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : (\n <>\n {!isLoading && sortedDeals.length === 0 ? (\n <TabEmptyState\n title={emptyState.title}\n action={{\n label: emptyState.actionLabel,\n onClick: openCreateDialog,\n disabled: !scope || pendingAction !== null,\n }}\n />\n ) : null}\n <div className=\"space-y-4\">\n {sortedDeals.map((deal) => {\n const valueLabel = formatValueLabel(deal.valueAmount, deal.valueCurrency ?? null, emptyLabel)\n const expectedLabel = deal.expectedCloseAt ? formatDate(deal.expectedCloseAt) ?? emptyLabel : emptyLabel\n const probabilityLabel =\n typeof deal.probability === 'number' ? `${deal.probability}%` : emptyLabel\n const isUnlinkPending = pendingAction?.kind === 'remove' && pendingAction.id === deal.id\n const statusLabel =\n deal.status && statusDictionaryMap\n ? statusDictionaryMap[deal.status]?.label ?? deal.status\n : deal.status ?? emptyLabel\n return (\n <article key={deal.id} className=\"group rounded-lg border bg-card p-4 shadow-xs transition hover:border-border/70\">\n <header className=\"flex flex-wrap items-center justify-between gap-2\">\n <div>\n <Link\n href={`/backend/customers/deals/${encodeURIComponent(deal.id)}`}\n className=\"text-base font-semibold text-foreground transition hover:text-primary hover:underline\"\n >\n {deal.title || emptyLabel}\n </Link>\n {deal.description ? (\n <p className=\"mt-1 text-sm text-muted-foreground whitespace-pre-wrap\">{deal.description}</p>\n ) : null}\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium uppercase text-muted-foreground\">\n {statusLabel}\n </span>\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n <IconButton asChild variant=\"ghost\" size=\"sm\" className=\"h-8 w-8\">\n <Link\n href={`/backend/customers/deals/${encodeURIComponent(deal.id)}`}\n aria-label={t('customers.people.detail.deals.edit', 'Open deal details')}\n >\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 rounded-lg px-3 text-xs\"\n onClick={(event) => {\n event.preventDefault()\n void handleUnlink(deal)\n }}\n disabled={pendingAction !== null}\n >\n {isUnlinkPending\n ? t('customers.people.detail.deals.unlinking', 'Unlinking\u2026')\n : t('customers.people.card.unlink', 'Unlink')}\n </Button>\n </div>\n </div>\n </header>\n <dl className=\"mt-3 grid gap-2 text-xs text-muted-foreground sm:grid-cols-2\">\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.pipelineStage', 'Pipeline stage')}\n </dt>\n <dd>{deal.pipelineStage ?? emptyLabel}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.probability', 'Probability')}\n </dt>\n <dd>{probabilityLabel}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.valueAmount', 'Value')}\n </dt>\n <dd>{valueLabel}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"font-medium\">\n {t('customers.people.detail.deals.fields.expectedCloseAt', 'Expected close')}\n </dt>\n <dd>{expectedLabel}</dd>\n </div>\n </dl>\n <CustomFieldValuesList\n entries={deal.customFields?.map((entry) => ({\n key: entry.key,\n value: entry.value,\n label: entry.label ?? undefined,\n }))}\n values={deal.customValues ?? undefined}\n resources={customFieldResources}\n emptyLabel={emptyLabel}\n itemKeyPrefix={`deal-${deal.id}-field`}\n className=\"mt-3\"\n />\n </article>\n )\n })}\n {isLoading && deals.length > 0 ? (\n <div className=\"flex justify-center\">\n <LoadingMessage\n label={t('customers.people.detail.deals.loading', 'Loading deals\u2026')}\n className=\"border-0 bg-transparent p-0 justify-center [&_span[role='status']]:h-5 [&_span[role='status']]:w-5\"\n />\n </div>\n ) : null}\n {!isLoading && hasMore ? (\n <div className=\"flex justify-center\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n loadDeals({ append: true }).catch(() => {})\n }}\n disabled={pendingAction !== null || isLoading}\n >\n {t('customers.people.detail.deals.loadMore', 'Load more deals')}\n </Button>\n </div>\n ) : null}\n </div>\n </>\n )}\n\n <DealDialog\n open={dialogOpen}\n mode=\"create\"\n onOpenChange={handleDialogOpenChange}\n initialValues={initialValues}\n onSubmit={async (payload) => {\n await handleDialogSubmit(payload)\n }}\n isSubmitting={Boolean(isFormPending)}\n />\n <LinkEntityDialog\n open={linkDialogOpen}\n onOpenChange={setLinkDialogOpen}\n adapter={dealLinkAdapter}\n initialSelectedIds={existingDealIds}\n onConfirm={handleLinkConfirm}\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n\nexport default DealsSection\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
InlineDictionaryEditor
|
|
7
7
|
} from "./InlineEditors.js";
|
|
8
8
|
import { AnnualRevenueField } from "./AnnualRevenueField.js";
|
|
9
|
-
const DEFAULT_CONTAINER_CLASS = "rounded border bg-muted/
|
|
9
|
+
const DEFAULT_CONTAINER_CLASS = "rounded border bg-muted/30 p-3";
|
|
10
10
|
const DEFAULT_TRIGGER_CLASS = "h-8 w-8 opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100";
|
|
11
11
|
function DetailFieldsSection({ fields, className }) {
|
|
12
12
|
return /* @__PURE__ */ jsx("div", { className: ["grid gap-4 sm:grid-cols-2 xl:grid-cols-3", className].filter(Boolean).join(" "), children: fields.map((field) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/DetailFieldsSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ComponentProps } from 'react'\nimport {\n InlineTextEditor,\n InlineMultilineEditor,\n InlineDictionaryEditor,\n type InlineFieldType,\n type InlineMultilineDisplayRenderer,\n} from './InlineEditors'\nimport { AnnualRevenueField } from './AnnualRevenueField'\nimport type { InlineFieldProps } from './InlineEditors'\nimport type { CustomerDictionaryKind } from '../../lib/dictionaries'\n\ntype EditorVariant = ComponentProps<typeof InlineTextEditor>['variant']\n\ntype DetailFieldCommon = {\n key: string\n label: string\n emptyLabel: string\n gridClassName?: string\n editorVariant?: EditorVariant\n activateOnClick?: boolean\n containerClassName?: string\n triggerClassName?: string\n hideLabel?: boolean\n}\n\nexport type DetailTextFieldConfig = DetailFieldCommon & {\n kind: 'text'\n value: string | null | undefined\n placeholder: string\n onSave: (value: string | null) => Promise<void>\n inputType?: InlineFieldType\n validator?: InlineFieldProps['validator']\n renderDisplay?: InlineFieldProps['renderDisplay']\n recordId?: string\n}\n\nexport type DetailMultilineFieldConfig = DetailFieldCommon & {\n kind: 'multiline'\n value: string | null | undefined\n placeholder: string\n onSave: (value: string | null) => Promise<void>\n validator?: (value: string) => string | null\n renderDisplay?: InlineMultilineDisplayRenderer\n}\n\nexport type DetailDictionaryFieldConfig = DetailFieldCommon & {\n kind: 'dictionary'\n value: string | null | undefined\n dictionaryKind: CustomerDictionaryKind\n onSave: (value: string | null) => Promise<void>\n selectClassName?: string\n}\n\nexport type DetailAnnualRevenueFieldConfig = DetailFieldCommon & {\n kind: 'annualRevenue'\n value: string | null | undefined\n currency: string | null\n validator?: NonNullable<InlineFieldProps['validator']>\n onSave: (payload: { amount: number | null; currency: string | null }) => Promise<void>\n}\n\nexport type DetailFieldConfig =\n | DetailTextFieldConfig\n | DetailMultilineFieldConfig\n | DetailDictionaryFieldConfig\n | DetailAnnualRevenueFieldConfig\n\nexport type DetailFieldsSectionProps = {\n fields: DetailFieldConfig[]\n className?: string\n}\n\nconst DEFAULT_CONTAINER_CLASS = 'rounded border bg-muted/
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ComponentProps } from 'react'\nimport {\n InlineTextEditor,\n InlineMultilineEditor,\n InlineDictionaryEditor,\n type InlineFieldType,\n type InlineMultilineDisplayRenderer,\n} from './InlineEditors'\nimport { AnnualRevenueField } from './AnnualRevenueField'\nimport type { InlineFieldProps } from './InlineEditors'\nimport type { CustomerDictionaryKind } from '../../lib/dictionaries'\n\ntype EditorVariant = ComponentProps<typeof InlineTextEditor>['variant']\n\ntype DetailFieldCommon = {\n key: string\n label: string\n emptyLabel: string\n gridClassName?: string\n editorVariant?: EditorVariant\n activateOnClick?: boolean\n containerClassName?: string\n triggerClassName?: string\n hideLabel?: boolean\n}\n\nexport type DetailTextFieldConfig = DetailFieldCommon & {\n kind: 'text'\n value: string | null | undefined\n placeholder: string\n onSave: (value: string | null) => Promise<void>\n inputType?: InlineFieldType\n validator?: InlineFieldProps['validator']\n renderDisplay?: InlineFieldProps['renderDisplay']\n recordId?: string\n}\n\nexport type DetailMultilineFieldConfig = DetailFieldCommon & {\n kind: 'multiline'\n value: string | null | undefined\n placeholder: string\n onSave: (value: string | null) => Promise<void>\n validator?: (value: string) => string | null\n renderDisplay?: InlineMultilineDisplayRenderer\n}\n\nexport type DetailDictionaryFieldConfig = DetailFieldCommon & {\n kind: 'dictionary'\n value: string | null | undefined\n dictionaryKind: CustomerDictionaryKind\n onSave: (value: string | null) => Promise<void>\n selectClassName?: string\n}\n\nexport type DetailAnnualRevenueFieldConfig = DetailFieldCommon & {\n kind: 'annualRevenue'\n value: string | null | undefined\n currency: string | null\n validator?: NonNullable<InlineFieldProps['validator']>\n onSave: (payload: { amount: number | null; currency: string | null }) => Promise<void>\n}\n\nexport type DetailFieldConfig =\n | DetailTextFieldConfig\n | DetailMultilineFieldConfig\n | DetailDictionaryFieldConfig\n | DetailAnnualRevenueFieldConfig\n\nexport type DetailFieldsSectionProps = {\n fields: DetailFieldConfig[]\n className?: string\n}\n\nconst DEFAULT_CONTAINER_CLASS = 'rounded border bg-muted/30 p-3'\nconst DEFAULT_TRIGGER_CLASS =\n 'h-8 w-8 opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100'\n\nexport function DetailFieldsSection({ fields, className }: DetailFieldsSectionProps) {\n return (\n <div className={['grid gap-4 sm:grid-cols-2 xl:grid-cols-3', className].filter(Boolean).join(' ')}>\n {fields.map((field) => {\n const variant = field.editorVariant ?? 'muted'\n const activateOnClick = field.activateOnClick ?? true\n const containerClassName = field.containerClassName ?? DEFAULT_CONTAINER_CLASS\n const triggerClassName = field.triggerClassName ?? DEFAULT_TRIGGER_CLASS\n const wrapperClassName = field.gridClassName ?? undefined\n\n if (field.kind === 'text') {\n return (\n <div key={field.key} className={wrapperClassName}>\n <InlineTextEditor\n label={field.label}\n value={field.value}\n placeholder={field.placeholder}\n emptyLabel={field.emptyLabel}\n onSave={field.onSave}\n type={field.inputType}\n validator={field.validator}\n renderDisplay={field.renderDisplay}\n recordId={field.recordId}\n variant={variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n hideLabel={field.hideLabel}\n />\n </div>\n )\n }\n\n if (field.kind === 'multiline') {\n return (\n <div key={field.key} className={wrapperClassName}>\n <InlineMultilineEditor\n label={field.label}\n value={field.value}\n placeholder={field.placeholder}\n emptyLabel={field.emptyLabel}\n onSave={field.onSave}\n validator={field.validator}\n variant={variant === 'plain' ? 'default' : variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n renderDisplay={field.renderDisplay}\n />\n </div>\n )\n }\n\n if (field.kind === 'annualRevenue') {\n return (\n <div key={field.key} className={wrapperClassName}>\n <AnnualRevenueField\n label={field.label}\n amount={field.value ?? null}\n currency={field.currency ?? null}\n emptyLabel={field.emptyLabel}\n validator={field.validator}\n onSave={field.onSave}\n />\n </div>\n )\n }\n\n return (\n <div key={field.key} className={wrapperClassName}>\n { /* InlineDictionaryEditor does not support 'plain'; fallback to default */ }\n <InlineDictionaryEditor\n label={field.label}\n value={field.value}\n emptyLabel={field.emptyLabel}\n onSave={field.onSave}\n kind={field.dictionaryKind}\n variant={variant === 'plain' ? 'default' : variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n selectClassName={field.selectClassName}\n />\n </div>\n )\n })}\n </div>\n )\n}\n"],
|
|
5
5
|
"mappings": ";AA6Fc;AAzFd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,0BAA0B;AAiEnC,MAAM,0BAA0B;AAChC,MAAM,wBACJ;AAEK,SAAS,oBAAoB,EAAE,QAAQ,UAAU,GAA6B;AACnF,SACE,oBAAC,SAAI,WAAW,CAAC,4CAA4C,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC7F,iBAAO,IAAI,CAAC,UAAU;AACrB,UAAM,UAAU,MAAM,iBAAiB;AACvC,UAAM,kBAAkB,MAAM,mBAAmB;AACjD,UAAM,qBAAqB,MAAM,sBAAsB;AACvD,UAAM,mBAAmB,MAAM,oBAAoB;AACnD,UAAM,mBAAmB,MAAM,iBAAiB;AAEhD,QAAI,MAAM,SAAS,QAAQ;AACzB,aACE,oBAAC,SAAoB,WAAW,kBAC9B;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,OAAO,MAAM;AAAA,UACb,aAAa,MAAM;AAAA,UACnB,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM;AAAA,UACd,MAAM,MAAM;AAAA,UACZ,WAAW,MAAM;AAAA,UACjB,eAAe,MAAM;AAAA,UACrB,UAAU,MAAM;AAAA,UAChB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,WAAW,MAAM;AAAA;AAAA,MACnB,KAhBQ,MAAM,GAiBhB;AAAA,IAEJ;AAEA,QAAI,MAAM,SAAS,aAAa;AAC9B,aACE,oBAAC,SAAoB,WAAW,kBAC9B;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,OAAO,MAAM;AAAA,UACb,aAAa,MAAM;AAAA,UACnB,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM;AAAA,UACd,WAAW,MAAM;AAAA,UACjB,SAAS,YAAY,UAAU,YAAY;AAAA,UAC3C;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,MAAM;AAAA;AAAA,MACvB,KAbQ,MAAM,GAchB;AAAA,IAEJ;AAEA,QAAI,MAAM,SAAS,iBAAiB;AAClC,aACE,oBAAC,SAAoB,WAAW,kBAC9B;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM,SAAS;AAAA,UACvB,UAAU,MAAM,YAAY;AAAA,UAC5B,YAAY,MAAM;AAAA,UAClB,WAAW,MAAM;AAAA,UACjB,QAAQ,MAAM;AAAA;AAAA,MAChB,KARQ,MAAM,GAShB;AAAA,IAEJ;AAEA,WACE,oBAAC,SAAoB,WAAW,kBAE9B;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,YAAY,MAAM;AAAA,QAClB,QAAQ,MAAM;AAAA,QACd,MAAM,MAAM;AAAA,QACZ,SAAS,YAAY,UAAU,YAAY;AAAA,QAC3C;AAAA,QACA;AAAA,QACA;AAAA,QACA,iBAAiB,MAAM;AAAA;AAAA,IACzB,KAbQ,MAAM,GAchB;AAAA,EAEJ,CAAC,GACH;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -482,8 +482,8 @@ function InlineNextInteractionEditor({
|
|
|
482
482
|
{
|
|
483
483
|
type: "datetime-local",
|
|
484
484
|
className: cn(
|
|
485
|
-
"w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
486
|
-
dateError ? "border-destructive
|
|
485
|
+
"w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
486
|
+
dateError ? "border-destructive aria-invalid:border-destructive aria-invalid:ring-destructive" : null
|
|
487
487
|
),
|
|
488
488
|
value: draftDate,
|
|
489
489
|
"aria-invalid": dateError ? "true" : void 0,
|
|
@@ -502,8 +502,8 @@ function InlineNextInteractionEditor({
|
|
|
502
502
|
{
|
|
503
503
|
placeholder: t("customers.people.detail.inline.nextInteractionName"),
|
|
504
504
|
className: cn(
|
|
505
|
-
"w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
506
|
-
nameError ? "border-destructive
|
|
505
|
+
"w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
506
|
+
nameError ? "border-destructive aria-invalid:border-destructive aria-invalid:ring-destructive" : null
|
|
507
507
|
),
|
|
508
508
|
value: draftName,
|
|
509
509
|
"aria-invalid": nameError ? "true" : void 0,
|
|
@@ -521,7 +521,7 @@ function InlineNextInteractionEditor({
|
|
|
521
521
|
"input",
|
|
522
522
|
{
|
|
523
523
|
placeholder: t("customers.people.detail.inline.nextInteractionRef"),
|
|
524
|
-
className: "w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
524
|
+
className: "w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
525
525
|
value: draftRefId,
|
|
526
526
|
onChange: (event) => {
|
|
527
527
|
if (submitError) setSubmitError(null);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/InlineEditors.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { AtSign, Briefcase, Loader2, Pencil, X } from 'lucide-react'\nimport ReactMarkdown from 'react-markdown'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport type { PluggableList } from 'unified'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport remarkGfm from 'remark-gfm'\nimport { useEmailDuplicateCheck } from '../../backend/hooks/useEmailDuplicateCheck'\nimport { lookupPhoneDuplicate } from '../../utils/phoneDuplicates'\nimport {\n InlineMultilineEditor as UiInlineMultilineEditor,\n InlineTextEditor as UiInlineTextEditor,\n type InlineFieldType as UiInlineFieldType,\n type InlineMultilineEditorProps,\n type InlineTextEditorProps,\n InlineSelectEditor as UiInlineSelectEditor,\n type InlineSelectOption,\n} from '@open-mercato/ui/backend/detail/InlineEditors'\nimport {\n DictionaryValue,\n ICON_SUGGESTIONS,\n renderDictionaryColor,\n renderDictionaryIcon,\n type CustomerDictionaryKind,\n} from '../../lib/dictionaries'\nimport { DictionarySelectField } from '../formConfig'\nimport { AppearanceSelector } from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'\nimport { createDictionarySelectLabels } from './utils'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport {\n invalidateCustomerDictionary,\n useCustomerDictionary,\n} from './hooks/useCustomerDictionary'\n\nexport type InlineFieldType = UiInlineFieldType\nexport type InlineFieldProps = InlineTextEditorProps\nexport type InlineMultilineDisplayRenderer = NonNullable<InlineMultilineEditorProps['renderDisplay']>\n\nexport function InlineTextEditor(props: InlineFieldProps) {\n const { type = 'text', validator, recordId } = props\n const t = useT()\n const normalizeText = React.useCallback((value: unknown) => {\n if (typeof value === 'string') return value\n if (value === null || value === undefined) return ''\n return String(value)\n }, [])\n const [draft, setDraft] = React.useState<string>(() => normalizeText(props.value))\n const setDraftValue = React.useCallback((value: unknown) => {\n setDraft(normalizeText(value))\n }, [normalizeText])\n const [editing, setEditing] = React.useState(false)\n const currentRecordId = React.useMemo(() => (typeof recordId === 'string' ? recordId : null), [recordId])\n const isEmailField = type === 'email'\n const isPhoneField = type === 'tel'\n const trimmedDraft = React.useMemo(() => draft.trim(), [draft])\n const validationError = React.useMemo(() => {\n if (!validator) return null\n return validator(trimmedDraft)\n }, [trimmedDraft, validator])\n const isValidEmailForLookup = React.useMemo(() => {\n if (!isEmailField) return false\n if (!trimmedDraft.length) return false\n return !validationError\n }, [isEmailField, trimmedDraft, validationError])\n const { duplicate: emailDuplicate, checking: emailChecking } = useEmailDuplicateCheck(draft, {\n recordId: currentRecordId,\n disabled: !editing || !isValidEmailForLookup,\n matchMode: 'prefix',\n })\n const [phoneDuplicate, setPhoneDuplicate] = React.useState<{ id: string; label: string; href?: string } | null>(null)\n const [phoneChecking, setPhoneChecking] = React.useState(false)\n\n React.useEffect(() => {\n if (!editing) {\n setDraftValue(props.value)\n }\n }, [editing, props.value, setDraftValue])\n\n React.useEffect(() => {\n if (!editing || !isPhoneField) {\n setPhoneDuplicate(null)\n setPhoneChecking(false)\n return\n }\n const digits = draft.replace(/\\\\D/g, '')\n if (digits.length < 7) {\n setPhoneDuplicate(null)\n setPhoneChecking(false)\n return\n }\n let cancelled = false\n setPhoneChecking(true)\n void lookupPhoneDuplicate(digits, { recordId: currentRecordId })\n .then((result) => {\n if (cancelled) return\n setPhoneDuplicate(result)\n })\n .catch(() => {\n if (cancelled) return\n setPhoneDuplicate(null)\n })\n .finally(() => {\n if (cancelled) return\n setPhoneChecking(false)\n })\n return () => {\n cancelled = true\n }\n }, [currentRecordId, draft, editing, isPhoneField])\n\n return (\n <UiInlineTextEditor\n {...props}\n type={type}\n validator={validator}\n value={normalizeText(props.value)}\n onDraftChange={setDraftValue}\n onEditingChange={setEditing}\n renderBelowInput={({ resolvedType, error }) => {\n if (resolvedType === 'email') {\n if (error || !editing || !isValidEmailForLookup) return null\n if (emailDuplicate) {\n return (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.detail.inline.emailDuplicate', undefined, { name: emailDuplicate.displayName })}{' '}\n <Link\n className=\"font-medium text-primary underline underline-offset-2\"\n href={`/backend/customers/people-v2/${emailDuplicate.id}`}\n >\n {t('customers.people.detail.inline.emailDuplicateLink')}\n </Link>\n </p>\n )\n }\n if (emailChecking) {\n return <p className=\"text-xs text-muted-foreground\">{t('customers.people.detail.inline.emailChecking')}</p>\n }\n }\n if (resolvedType === 'tel') {\n if (error || !editing) return null\n if (phoneDuplicate) {\n return (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.form.phoneDuplicateNotice', undefined, { name: phoneDuplicate.label })}{' '}\n {phoneDuplicate.href ? (\n <Link\n className=\"font-medium text-primary underline underline-offset-2\"\n href={phoneDuplicate.href}\n >\n {t('customers.people.form.phoneDuplicateLink')}\n </Link>\n ) : null}\n </p>\n )\n }\n if (phoneChecking) {\n return <p className=\"text-xs text-muted-foreground\">{t('customers.people.form.phoneChecking')}</p>\n }\n }\n return null\n }}\n />\n )\n}\nexport const InlineMultilineEditor = UiInlineMultilineEditor\nexport const InlineSelectEditor = UiInlineSelectEditor\n\nconst MARKDOWN_PREVIEW_PLUGINS: PluggableList = [remarkGfm]\n\nfunction createSocialRenderDisplay(IconComponent: typeof Briefcase): NonNullable<InlineFieldProps['renderDisplay']> {\n // eslint-disable-next-line react/display-name\n return ({ value, emptyLabel }) => {\n const raw = typeof value === 'string' ? value.trim() : ''\n if (!raw.length) {\n return <span className=\"text-sm text-muted-foreground\">{emptyLabel}</span>\n }\n const display = raw.replace(/^https?:\\/\\/(www\\.)?/i, '').replace(/\\/$/, '')\n return (\n <a\n className=\"inline-flex items-center gap-2 text-sm text-primary hover:text-primary/90 hover:underline\"\n href={raw}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n <IconComponent aria-hidden className=\"h-4 w-4\" />\n <span className=\"truncate\">{display}</span>\n </a>\n )\n }\n}\n\nexport const renderLinkedInDisplay = createSocialRenderDisplay(Briefcase)\nexport const renderTwitterDisplay = createSocialRenderDisplay(AtSign)\n\nexport const renderMultilineMarkdownDisplay: InlineMultilineDisplayRenderer = ({ value, emptyLabel }) => {\n const raw = typeof value === 'string' ? value : ''\n const trimmed = raw.trim()\n if (!trimmed.length) {\n return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n }\n return (\n <div className=\"text-sm text-foreground [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs\">\n <ReactMarkdown remarkPlugins={MARKDOWN_PREVIEW_PLUGINS}>\n {raw}\n </ReactMarkdown>\n </div>\n )\n}\n\ntype DictionaryEditorProps = {\n label: string\n value: string | null | undefined\n emptyLabel: string\n onSave: (value: string | null) => Promise<void>\n kind: CustomerDictionaryKind\n variant?: 'default' | 'muted'\n activateOnClick?: boolean\n containerClassName?: string\n triggerClassName?: string\n selectClassName?: string\n}\n\nexport function InlineDictionaryEditor({\n label,\n value,\n emptyLabel,\n onSave,\n kind,\n variant = 'default',\n activateOnClick = false,\n containerClassName,\n triggerClassName,\n selectClassName,\n}: DictionaryEditorProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const translate = React.useCallback(\n (key: string, fallback: string) => {\n const result = t(key)\n return result === key ? fallback : result\n },\n [t],\n )\n const dictionaryLabels = React.useMemo(() => createDictionarySelectLabels(kind, translate), [kind, translate])\n const scopeVersion = useOrganizationScopeVersion()\n const dictionaryQuery = useCustomerDictionary(kind, scopeVersion)\n const dictionaryMap = dictionaryQuery.data?.map ?? null\n const selectOptions = React.useMemo<InlineSelectOption[]>(() => {\n if (!dictionaryMap) return []\n return Object.values(dictionaryMap).map((entry) => ({ value: entry.value, label: entry.label }))\n }, [dictionaryMap])\n\n const handleSave = React.useCallback(\n async (nextValue: string | null) => {\n try {\n await onSave(nextValue)\n await invalidateCustomerDictionary(queryClient, kind)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.inline.error')\n flash(message, 'error')\n }\n },\n [kind, onSave, queryClient, t],\n )\n\n return (\n <UiInlineSelectEditor\n label={label}\n value={value}\n emptyLabel={emptyLabel}\n options={selectOptions}\n onSave={handleSave}\n variant={variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n hideLabel={false}\n renderEditor={({ value: selectValue, onChange }) => (\n <>\n <DictionarySelectField\n kind={kind}\n value={selectValue.length ? selectValue : undefined}\n onChange={(next) => onChange(next ?? '')}\n labels={dictionaryLabels}\n selectClassName={selectClassName}\n />\n {dictionaryQuery.isError ? (\n <p className=\"text-xs text-status-error-text\">\n {dictionaryQuery.error instanceof Error\n ? dictionaryQuery.error.message\n : translate('customers.people.form.dictionary.errorLoad', 'Failed to load options')}\n </p>\n ) : null}\n </>\n )}\n renderDisplay={({ value: currentValue }) => {\n if (dictionaryMap) {\n return (\n <DictionaryValue\n value={currentValue}\n map={dictionaryMap}\n fallback={<span className=\"text-sm text-muted-foreground\">{emptyLabel}</span>}\n className=\"text-sm\"\n iconWrapperClassName=\"inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-card\"\n iconClassName=\"h-4 w-4\"\n colorClassName=\"h-3 w-3 rounded-full\"\n />\n )\n }\n if (currentValue && currentValue.length) {\n return <span className=\"break-words text-sm\">{currentValue}</span>\n }\n if (dictionaryQuery.isLoading) {\n return (\n <span className=\"text-sm text-muted-foreground\">\n {translate('customers.people.form.dictionary.loading', 'Loading\u2026')}\n </span>\n )\n }\n return <span className=\"text-sm text-muted-foreground\">{emptyLabel}</span>\n }}\n />\n )\n}\n\nexport type NextInteractionPayload = {\n at: string\n name: string\n refId?: string | null\n icon?: string | null\n color?: string | null\n}\n\ntype NextInteractionEditorProps = {\n label: string\n valueAt: string | null | undefined\n valueName: string | null | undefined\n valueRefId: string | null | undefined\n valueIcon: string | null | undefined\n valueColor: string | null | undefined\n emptyLabel: string\n onSave: (next: NextInteractionPayload | null) => Promise<void>\n activateOnClick?: boolean\n}\n\nexport function InlineNextInteractionEditor({\n label,\n valueAt,\n valueName,\n valueRefId,\n valueIcon,\n valueColor,\n emptyLabel,\n onSave,\n activateOnClick = false,\n}: NextInteractionEditorProps) {\n const t = useT()\n const [editing, setEditing] = React.useState(false)\n const [draftDate, setDraftDate] = React.useState<string>(() => (valueAt ? valueAt.slice(0, 16) : ''))\n const [draftName, setDraftName] = React.useState(valueName ?? '')\n const [draftRefId, setDraftRefId] = React.useState(valueRefId ?? '')\n const [draftIcon, setDraftIcon] = React.useState(valueIcon ?? '')\n const [draftColor, setDraftColor] = React.useState<string | null>(valueColor ?? null)\n const [dateError, setDateError] = React.useState<string | null>(null)\n const [nameError, setNameError] = React.useState<string | null>(null)\n const [submitError, setSubmitError] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const formRef = React.useRef<HTMLFormElement | null>(null)\n const dateErrorId = React.useId()\n const nameErrorId = React.useId()\n const containerClasses = cn('group relative rounded-lg border p-4', activateOnClick && !editing ? 'cursor-pointer' : null)\n const requiredMessage = React.useMemo(\n () => t('customers.people.detail.inline.required', 'This field is required'),\n [t],\n )\n\n React.useEffect(() => {\n if (!editing) {\n setDraftDate(valueAt ? valueAt.slice(0, 16) : '')\n setDraftName(valueName ?? '')\n setDraftRefId(valueRefId ?? '')\n setDraftIcon(valueIcon ?? '')\n setDraftColor(valueColor ?? null)\n setDateError(null)\n setNameError(null)\n setSubmitError(null)\n }\n }, [editing, valueAt, valueName, valueRefId, valueIcon, valueColor])\n\n const appearanceLabels = React.useMemo(\n () => ({\n colorLabel: t('customers.people.detail.inline.nextInteractionColorLabel'),\n colorHelp: t('customers.people.detail.inline.nextInteractionColorHelp'),\n colorClearLabel: t('customers.people.detail.inline.nextInteractionColorClear'),\n iconLabel: t('customers.people.detail.inline.nextInteractionIconLabel'),\n iconPlaceholder: t('customers.people.detail.inline.nextInteractionIconPlaceholder'),\n iconPickerTriggerLabel: t('customers.people.detail.inline.nextInteractionIconBrowse'),\n iconSearchPlaceholder: t('customers.people.detail.inline.nextInteractionIconSearchPlaceholder'),\n iconSearchEmptyLabel: t('customers.people.detail.inline.nextInteractionIconSearchEmpty'),\n iconSuggestionsLabel: t('customers.people.detail.inline.nextInteractionIconSuggestions'),\n iconClearLabel: t('customers.people.detail.inline.nextInteractionIconClear'),\n previewEmptyLabel: t('customers.people.detail.inline.nextInteractionAppearanceEmpty'),\n }),\n [t],\n )\n\n const handleSave = React.useCallback(async () => {\n setSubmitError(null)\n setDateError(null)\n setNameError(null)\n const trimmedName = draftName.trim()\n let hasError = false\n if (!draftDate) {\n setDateError(requiredMessage)\n hasError = true\n }\n if (!trimmedName.length) {\n setNameError(requiredMessage)\n hasError = true\n }\n if (hasError) return\n const parsedDate = new Date(draftDate)\n if (Number.isNaN(parsedDate.getTime())) {\n setDateError(t('customers.people.detail.inline.nextInteractionInvalid'))\n return\n }\n const iso = parsedDate.toISOString()\n const trimmedRef = draftRefId.trim()\n const trimmedIcon = draftIcon.trim()\n const normalizedColor = (() => {\n if (!draftColor) return null\n const trimmed = draftColor.trim().toLowerCase()\n return /^#([0-9a-f]{6})$/.test(trimmed) ? trimmed : null\n })()\n setSaving(true)\n try {\n await onSave({\n at: iso,\n name: trimmedName,\n refId: trimmedRef.length ? trimmedRef : null,\n icon: trimmedIcon.length ? trimmedIcon : null,\n color: normalizedColor,\n })\n setEditing(false)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.inline.error')\n setSubmitError(message)\n } finally {\n setSaving(false)\n }\n }, [draftColor, draftDate, draftIcon, draftName, draftRefId, onSave, requiredMessage, t])\n\n const handleFormSubmit = React.useCallback(\n (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (saving) return\n void handleSave()\n },\n [handleSave, saving],\n )\n\n const handleFormKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n setEditing(false)\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (saving) return\n try {\n formRef.current?.requestSubmit()\n } catch {\n void handleSave()\n }\n }\n },\n [handleSave, saving],\n )\n\n const handleActivate = React.useCallback(() => {\n if (!editing) setEditing(true)\n }, [editing])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (!activateOnClick || editing) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleActivate()\n }\n },\n [activateOnClick, editing, handleActivate],\n )\n\n const handleInteractiveClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (!activateOnClick || editing) return\n const target = event.target as HTMLElement\n const interactiveElement = target.closest('button, input, select, textarea, a, [role=\\\"link\\\"]')\n if (interactiveElement) {\n if (interactiveElement.tagName.toLowerCase() === 'a') {\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n return\n }\n event.preventDefault()\n } else {\n return\n }\n }\n handleActivate()\n },\n [activateOnClick, editing, handleActivate],\n )\n\n const interactiveProps: React.HTMLAttributes<HTMLDivElement> =\n activateOnClick && !editing\n ? {\n role: 'button' as const,\n tabIndex: 0,\n onClick: handleInteractiveClick,\n onKeyDown: handleKeyDown,\n }\n : {}\n\n return (\n <div className={containerClasses} onClick={handleInteractiveClick}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(\n 'absolute right-3 top-3 transition-opacity duration-150',\n editing\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100',\n )}\n onClick={(event) => {\n event.stopPropagation()\n setEditing((state) => !state)\n }}\n >\n {editing ? <X className=\"h-4 w-4\" /> : <Pencil className=\"h-4 w-4\" />}\n </Button>\n <div className=\"flex items-start gap-2\" {...interactiveProps}>\n <div className=\"flex-1\">\n <p className=\"text-xs uppercase tracking-wide text-muted-foreground\">{label}</p>\n {editing ? (\n <form\n ref={formRef}\n className=\"mt-2 space-y-4\"\n onSubmit={handleFormSubmit}\n onKeyDown={handleFormKeyDown}\n >\n <input\n type=\"datetime-local\"\n className={cn(\n 'w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',\n dateError ? 'border-destructive focus:border-destructive focus:ring-destructive/40' : null,\n )}\n value={draftDate}\n aria-invalid={dateError ? 'true' : undefined}\n aria-required=\"true\"\n aria-describedby={dateError ? dateErrorId : undefined}\n onChange={(event) => {\n if (dateError) setDateError(null)\n if (submitError) setSubmitError(null)\n setDraftDate(event.target.value)\n }}\n />\n {dateError ? (\n <p id={dateErrorId} className=\"text-xs text-destructive\">\n {dateError}\n </p>\n ) : null}\n <input\n placeholder={t('customers.people.detail.inline.nextInteractionName')}\n className={cn(\n 'w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',\n nameError ? 'border-destructive focus:border-destructive focus:ring-destructive/40' : null,\n )}\n value={draftName}\n aria-invalid={nameError ? 'true' : undefined}\n aria-required=\"true\"\n aria-describedby={nameError ? nameErrorId : undefined}\n onChange={(event) => {\n if (submitError) setSubmitError(null)\n if (nameError) setNameError(null)\n setDraftName(event.target.value)\n }}\n />\n {nameError ? (\n <p id={nameErrorId} className=\"text-xs text-destructive\">\n {nameError}\n </p>\n ) : null}\n <input\n placeholder={t('customers.people.detail.inline.nextInteractionRef')}\n className=\"w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring\"\n value={draftRefId}\n onChange={(event) => {\n if (submitError) setSubmitError(null)\n setDraftRefId(event.target.value)\n }}\n />\n <AppearanceSelector\n icon={draftIcon || null}\n color={draftColor}\n onIconChange={(next) => {\n if (submitError) setSubmitError(null)\n setDraftIcon(next ?? '')\n }}\n onColorChange={(next) => {\n if (submitError) setSubmitError(null)\n setDraftColor(next)\n }}\n iconSuggestions={ICON_SUGGESTIONS}\n disabled={saving}\n labels={appearanceLabels}\n />\n {submitError && !dateError && !nameError ? (\n <p className=\"text-xs text-destructive\">{submitError}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button type=\"submit\" size=\"sm\" disabled={saving}>\n {saving ? <Loader2 className=\"mr-2 h-3.5 w-3.5 animate-spin\" /> : null}\n {t('customers.people.detail.inline.saveShortcut', 'Save \u2318\u23CE / Ctrl+Enter')}\n </Button>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={() => setEditing(false)} disabled={saving}>\n {t('customers.people.detail.inline.cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n onClick={async () => {\n setDraftDate('')\n setDraftName('')\n setDraftRefId('')\n setDraftIcon('')\n setDraftColor(null)\n setDateError(null)\n setNameError(null)\n setSubmitError(null)\n setSaving(true)\n try {\n await onSave(null)\n setEditing(false)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.inline.error')\n setSubmitError(message)\n } finally {\n setSaving(false)\n }\n }}\n disabled={saving}\n >\n {t('customers.people.detail.inline.clear')}\n </Button>\n </div>\n </form>\n ) : (\n <div className=\"mt-1 text-sm\">\n {valueAt ? (\n <div className=\"flex items-start gap-3\">\n {valueIcon ? (\n <span className=\"inline-flex h-8 w-8 items-center justify-center rounded border border-border bg-card\">\n {renderDictionaryIcon(valueIcon, 'h-4 w-4')}\n </span>\n ) : null}\n <div className=\"flex-1\">\n <span className=\"block\">{formatDateTime(valueAt)}</span>\n {valueName ? <span className=\"text-xs text-muted-foreground\">{valueName}</span> : null}\n {valueRefId ? <span className=\"text-xs text-muted-foreground\">#{valueRefId}</span> : null}\n </div>\n {valueColor ? renderDictionaryColor(valueColor, 'h-3 w-3 rounded-full border border-border') : null}\n </div>\n ) : (\n <span className=\"text-muted-foreground\">{emptyLabel}</span>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { AtSign, Briefcase, Loader2, Pencil, X } from 'lucide-react'\nimport ReactMarkdown from 'react-markdown'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport type { PluggableList } from 'unified'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport remarkGfm from 'remark-gfm'\nimport { useEmailDuplicateCheck } from '../../backend/hooks/useEmailDuplicateCheck'\nimport { lookupPhoneDuplicate } from '../../utils/phoneDuplicates'\nimport {\n InlineMultilineEditor as UiInlineMultilineEditor,\n InlineTextEditor as UiInlineTextEditor,\n type InlineFieldType as UiInlineFieldType,\n type InlineMultilineEditorProps,\n type InlineTextEditorProps,\n InlineSelectEditor as UiInlineSelectEditor,\n type InlineSelectOption,\n} from '@open-mercato/ui/backend/detail/InlineEditors'\nimport {\n DictionaryValue,\n ICON_SUGGESTIONS,\n renderDictionaryColor,\n renderDictionaryIcon,\n type CustomerDictionaryKind,\n} from '../../lib/dictionaries'\nimport { DictionarySelectField } from '../formConfig'\nimport { AppearanceSelector } from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'\nimport { createDictionarySelectLabels } from './utils'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport {\n invalidateCustomerDictionary,\n useCustomerDictionary,\n} from './hooks/useCustomerDictionary'\n\nexport type InlineFieldType = UiInlineFieldType\nexport type InlineFieldProps = InlineTextEditorProps\nexport type InlineMultilineDisplayRenderer = NonNullable<InlineMultilineEditorProps['renderDisplay']>\n\nexport function InlineTextEditor(props: InlineFieldProps) {\n const { type = 'text', validator, recordId } = props\n const t = useT()\n const normalizeText = React.useCallback((value: unknown) => {\n if (typeof value === 'string') return value\n if (value === null || value === undefined) return ''\n return String(value)\n }, [])\n const [draft, setDraft] = React.useState<string>(() => normalizeText(props.value))\n const setDraftValue = React.useCallback((value: unknown) => {\n setDraft(normalizeText(value))\n }, [normalizeText])\n const [editing, setEditing] = React.useState(false)\n const currentRecordId = React.useMemo(() => (typeof recordId === 'string' ? recordId : null), [recordId])\n const isEmailField = type === 'email'\n const isPhoneField = type === 'tel'\n const trimmedDraft = React.useMemo(() => draft.trim(), [draft])\n const validationError = React.useMemo(() => {\n if (!validator) return null\n return validator(trimmedDraft)\n }, [trimmedDraft, validator])\n const isValidEmailForLookup = React.useMemo(() => {\n if (!isEmailField) return false\n if (!trimmedDraft.length) return false\n return !validationError\n }, [isEmailField, trimmedDraft, validationError])\n const { duplicate: emailDuplicate, checking: emailChecking } = useEmailDuplicateCheck(draft, {\n recordId: currentRecordId,\n disabled: !editing || !isValidEmailForLookup,\n matchMode: 'prefix',\n })\n const [phoneDuplicate, setPhoneDuplicate] = React.useState<{ id: string; label: string; href?: string } | null>(null)\n const [phoneChecking, setPhoneChecking] = React.useState(false)\n\n React.useEffect(() => {\n if (!editing) {\n setDraftValue(props.value)\n }\n }, [editing, props.value, setDraftValue])\n\n React.useEffect(() => {\n if (!editing || !isPhoneField) {\n setPhoneDuplicate(null)\n setPhoneChecking(false)\n return\n }\n const digits = draft.replace(/\\\\D/g, '')\n if (digits.length < 7) {\n setPhoneDuplicate(null)\n setPhoneChecking(false)\n return\n }\n let cancelled = false\n setPhoneChecking(true)\n void lookupPhoneDuplicate(digits, { recordId: currentRecordId })\n .then((result) => {\n if (cancelled) return\n setPhoneDuplicate(result)\n })\n .catch(() => {\n if (cancelled) return\n setPhoneDuplicate(null)\n })\n .finally(() => {\n if (cancelled) return\n setPhoneChecking(false)\n })\n return () => {\n cancelled = true\n }\n }, [currentRecordId, draft, editing, isPhoneField])\n\n return (\n <UiInlineTextEditor\n {...props}\n type={type}\n validator={validator}\n value={normalizeText(props.value)}\n onDraftChange={setDraftValue}\n onEditingChange={setEditing}\n renderBelowInput={({ resolvedType, error }) => {\n if (resolvedType === 'email') {\n if (error || !editing || !isValidEmailForLookup) return null\n if (emailDuplicate) {\n return (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.detail.inline.emailDuplicate', undefined, { name: emailDuplicate.displayName })}{' '}\n <Link\n className=\"font-medium text-primary underline underline-offset-2\"\n href={`/backend/customers/people-v2/${emailDuplicate.id}`}\n >\n {t('customers.people.detail.inline.emailDuplicateLink')}\n </Link>\n </p>\n )\n }\n if (emailChecking) {\n return <p className=\"text-xs text-muted-foreground\">{t('customers.people.detail.inline.emailChecking')}</p>\n }\n }\n if (resolvedType === 'tel') {\n if (error || !editing) return null\n if (phoneDuplicate) {\n return (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.form.phoneDuplicateNotice', undefined, { name: phoneDuplicate.label })}{' '}\n {phoneDuplicate.href ? (\n <Link\n className=\"font-medium text-primary underline underline-offset-2\"\n href={phoneDuplicate.href}\n >\n {t('customers.people.form.phoneDuplicateLink')}\n </Link>\n ) : null}\n </p>\n )\n }\n if (phoneChecking) {\n return <p className=\"text-xs text-muted-foreground\">{t('customers.people.form.phoneChecking')}</p>\n }\n }\n return null\n }}\n />\n )\n}\nexport const InlineMultilineEditor = UiInlineMultilineEditor\nexport const InlineSelectEditor = UiInlineSelectEditor\n\nconst MARKDOWN_PREVIEW_PLUGINS: PluggableList = [remarkGfm]\n\nfunction createSocialRenderDisplay(IconComponent: typeof Briefcase): NonNullable<InlineFieldProps['renderDisplay']> {\n // eslint-disable-next-line react/display-name\n return ({ value, emptyLabel }) => {\n const raw = typeof value === 'string' ? value.trim() : ''\n if (!raw.length) {\n return <span className=\"text-sm text-muted-foreground\">{emptyLabel}</span>\n }\n const display = raw.replace(/^https?:\\/\\/(www\\.)?/i, '').replace(/\\/$/, '')\n return (\n <a\n className=\"inline-flex items-center gap-2 text-sm text-primary hover:text-primary/90 hover:underline\"\n href={raw}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n <IconComponent aria-hidden className=\"h-4 w-4\" />\n <span className=\"truncate\">{display}</span>\n </a>\n )\n }\n}\n\nexport const renderLinkedInDisplay = createSocialRenderDisplay(Briefcase)\nexport const renderTwitterDisplay = createSocialRenderDisplay(AtSign)\n\nexport const renderMultilineMarkdownDisplay: InlineMultilineDisplayRenderer = ({ value, emptyLabel }) => {\n const raw = typeof value === 'string' ? value : ''\n const trimmed = raw.trim()\n if (!trimmed.length) {\n return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n }\n return (\n <div className=\"text-sm text-foreground [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs\">\n <ReactMarkdown remarkPlugins={MARKDOWN_PREVIEW_PLUGINS}>\n {raw}\n </ReactMarkdown>\n </div>\n )\n}\n\ntype DictionaryEditorProps = {\n label: string\n value: string | null | undefined\n emptyLabel: string\n onSave: (value: string | null) => Promise<void>\n kind: CustomerDictionaryKind\n variant?: 'default' | 'muted'\n activateOnClick?: boolean\n containerClassName?: string\n triggerClassName?: string\n selectClassName?: string\n}\n\nexport function InlineDictionaryEditor({\n label,\n value,\n emptyLabel,\n onSave,\n kind,\n variant = 'default',\n activateOnClick = false,\n containerClassName,\n triggerClassName,\n selectClassName,\n}: DictionaryEditorProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const translate = React.useCallback(\n (key: string, fallback: string) => {\n const result = t(key)\n return result === key ? fallback : result\n },\n [t],\n )\n const dictionaryLabels = React.useMemo(() => createDictionarySelectLabels(kind, translate), [kind, translate])\n const scopeVersion = useOrganizationScopeVersion()\n const dictionaryQuery = useCustomerDictionary(kind, scopeVersion)\n const dictionaryMap = dictionaryQuery.data?.map ?? null\n const selectOptions = React.useMemo<InlineSelectOption[]>(() => {\n if (!dictionaryMap) return []\n return Object.values(dictionaryMap).map((entry) => ({ value: entry.value, label: entry.label }))\n }, [dictionaryMap])\n\n const handleSave = React.useCallback(\n async (nextValue: string | null) => {\n try {\n await onSave(nextValue)\n await invalidateCustomerDictionary(queryClient, kind)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.inline.error')\n flash(message, 'error')\n }\n },\n [kind, onSave, queryClient, t],\n )\n\n return (\n <UiInlineSelectEditor\n label={label}\n value={value}\n emptyLabel={emptyLabel}\n options={selectOptions}\n onSave={handleSave}\n variant={variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n hideLabel={false}\n renderEditor={({ value: selectValue, onChange }) => (\n <>\n <DictionarySelectField\n kind={kind}\n value={selectValue.length ? selectValue : undefined}\n onChange={(next) => onChange(next ?? '')}\n labels={dictionaryLabels}\n selectClassName={selectClassName}\n />\n {dictionaryQuery.isError ? (\n <p className=\"text-xs text-status-error-text\">\n {dictionaryQuery.error instanceof Error\n ? dictionaryQuery.error.message\n : translate('customers.people.form.dictionary.errorLoad', 'Failed to load options')}\n </p>\n ) : null}\n </>\n )}\n renderDisplay={({ value: currentValue }) => {\n if (dictionaryMap) {\n return (\n <DictionaryValue\n value={currentValue}\n map={dictionaryMap}\n fallback={<span className=\"text-sm text-muted-foreground\">{emptyLabel}</span>}\n className=\"text-sm\"\n iconWrapperClassName=\"inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-card\"\n iconClassName=\"h-4 w-4\"\n colorClassName=\"h-3 w-3 rounded-full\"\n />\n )\n }\n if (currentValue && currentValue.length) {\n return <span className=\"break-words text-sm\">{currentValue}</span>\n }\n if (dictionaryQuery.isLoading) {\n return (\n <span className=\"text-sm text-muted-foreground\">\n {translate('customers.people.form.dictionary.loading', 'Loading\u2026')}\n </span>\n )\n }\n return <span className=\"text-sm text-muted-foreground\">{emptyLabel}</span>\n }}\n />\n )\n}\n\nexport type NextInteractionPayload = {\n at: string\n name: string\n refId?: string | null\n icon?: string | null\n color?: string | null\n}\n\ntype NextInteractionEditorProps = {\n label: string\n valueAt: string | null | undefined\n valueName: string | null | undefined\n valueRefId: string | null | undefined\n valueIcon: string | null | undefined\n valueColor: string | null | undefined\n emptyLabel: string\n onSave: (next: NextInteractionPayload | null) => Promise<void>\n activateOnClick?: boolean\n}\n\nexport function InlineNextInteractionEditor({\n label,\n valueAt,\n valueName,\n valueRefId,\n valueIcon,\n valueColor,\n emptyLabel,\n onSave,\n activateOnClick = false,\n}: NextInteractionEditorProps) {\n const t = useT()\n const [editing, setEditing] = React.useState(false)\n const [draftDate, setDraftDate] = React.useState<string>(() => (valueAt ? valueAt.slice(0, 16) : ''))\n const [draftName, setDraftName] = React.useState(valueName ?? '')\n const [draftRefId, setDraftRefId] = React.useState(valueRefId ?? '')\n const [draftIcon, setDraftIcon] = React.useState(valueIcon ?? '')\n const [draftColor, setDraftColor] = React.useState<string | null>(valueColor ?? null)\n const [dateError, setDateError] = React.useState<string | null>(null)\n const [nameError, setNameError] = React.useState<string | null>(null)\n const [submitError, setSubmitError] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const formRef = React.useRef<HTMLFormElement | null>(null)\n const dateErrorId = React.useId()\n const nameErrorId = React.useId()\n const containerClasses = cn('group relative rounded-lg border p-4', activateOnClick && !editing ? 'cursor-pointer' : null)\n const requiredMessage = React.useMemo(\n () => t('customers.people.detail.inline.required', 'This field is required'),\n [t],\n )\n\n React.useEffect(() => {\n if (!editing) {\n setDraftDate(valueAt ? valueAt.slice(0, 16) : '')\n setDraftName(valueName ?? '')\n setDraftRefId(valueRefId ?? '')\n setDraftIcon(valueIcon ?? '')\n setDraftColor(valueColor ?? null)\n setDateError(null)\n setNameError(null)\n setSubmitError(null)\n }\n }, [editing, valueAt, valueName, valueRefId, valueIcon, valueColor])\n\n const appearanceLabels = React.useMemo(\n () => ({\n colorLabel: t('customers.people.detail.inline.nextInteractionColorLabel'),\n colorHelp: t('customers.people.detail.inline.nextInteractionColorHelp'),\n colorClearLabel: t('customers.people.detail.inline.nextInteractionColorClear'),\n iconLabel: t('customers.people.detail.inline.nextInteractionIconLabel'),\n iconPlaceholder: t('customers.people.detail.inline.nextInteractionIconPlaceholder'),\n iconPickerTriggerLabel: t('customers.people.detail.inline.nextInteractionIconBrowse'),\n iconSearchPlaceholder: t('customers.people.detail.inline.nextInteractionIconSearchPlaceholder'),\n iconSearchEmptyLabel: t('customers.people.detail.inline.nextInteractionIconSearchEmpty'),\n iconSuggestionsLabel: t('customers.people.detail.inline.nextInteractionIconSuggestions'),\n iconClearLabel: t('customers.people.detail.inline.nextInteractionIconClear'),\n previewEmptyLabel: t('customers.people.detail.inline.nextInteractionAppearanceEmpty'),\n }),\n [t],\n )\n\n const handleSave = React.useCallback(async () => {\n setSubmitError(null)\n setDateError(null)\n setNameError(null)\n const trimmedName = draftName.trim()\n let hasError = false\n if (!draftDate) {\n setDateError(requiredMessage)\n hasError = true\n }\n if (!trimmedName.length) {\n setNameError(requiredMessage)\n hasError = true\n }\n if (hasError) return\n const parsedDate = new Date(draftDate)\n if (Number.isNaN(parsedDate.getTime())) {\n setDateError(t('customers.people.detail.inline.nextInteractionInvalid'))\n return\n }\n const iso = parsedDate.toISOString()\n const trimmedRef = draftRefId.trim()\n const trimmedIcon = draftIcon.trim()\n const normalizedColor = (() => {\n if (!draftColor) return null\n const trimmed = draftColor.trim().toLowerCase()\n return /^#([0-9a-f]{6})$/.test(trimmed) ? trimmed : null\n })()\n setSaving(true)\n try {\n await onSave({\n at: iso,\n name: trimmedName,\n refId: trimmedRef.length ? trimmedRef : null,\n icon: trimmedIcon.length ? trimmedIcon : null,\n color: normalizedColor,\n })\n setEditing(false)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.inline.error')\n setSubmitError(message)\n } finally {\n setSaving(false)\n }\n }, [draftColor, draftDate, draftIcon, draftName, draftRefId, onSave, requiredMessage, t])\n\n const handleFormSubmit = React.useCallback(\n (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (saving) return\n void handleSave()\n },\n [handleSave, saving],\n )\n\n const handleFormKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n setEditing(false)\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (saving) return\n try {\n formRef.current?.requestSubmit()\n } catch {\n void handleSave()\n }\n }\n },\n [handleSave, saving],\n )\n\n const handleActivate = React.useCallback(() => {\n if (!editing) setEditing(true)\n }, [editing])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (!activateOnClick || editing) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleActivate()\n }\n },\n [activateOnClick, editing, handleActivate],\n )\n\n const handleInteractiveClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (!activateOnClick || editing) return\n const target = event.target as HTMLElement\n const interactiveElement = target.closest('button, input, select, textarea, a, [role=\\\"link\\\"]')\n if (interactiveElement) {\n if (interactiveElement.tagName.toLowerCase() === 'a') {\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n return\n }\n event.preventDefault()\n } else {\n return\n }\n }\n handleActivate()\n },\n [activateOnClick, editing, handleActivate],\n )\n\n const interactiveProps: React.HTMLAttributes<HTMLDivElement> =\n activateOnClick && !editing\n ? {\n role: 'button' as const,\n tabIndex: 0,\n onClick: handleInteractiveClick,\n onKeyDown: handleKeyDown,\n }\n : {}\n\n return (\n <div className={containerClasses} onClick={handleInteractiveClick}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(\n 'absolute right-3 top-3 transition-opacity duration-150',\n editing\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100',\n )}\n onClick={(event) => {\n event.stopPropagation()\n setEditing((state) => !state)\n }}\n >\n {editing ? <X className=\"h-4 w-4\" /> : <Pencil className=\"h-4 w-4\" />}\n </Button>\n <div className=\"flex items-start gap-2\" {...interactiveProps}>\n <div className=\"flex-1\">\n <p className=\"text-xs uppercase tracking-wide text-muted-foreground\">{label}</p>\n {editing ? (\n <form\n ref={formRef}\n className=\"mt-2 space-y-4\"\n onSubmit={handleFormSubmit}\n onKeyDown={handleFormKeyDown}\n >\n <input\n type=\"datetime-local\"\n className={cn(\n 'w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n dateError ? 'border-destructive aria-invalid:border-destructive aria-invalid:ring-destructive' : null,\n )}\n value={draftDate}\n aria-invalid={dateError ? 'true' : undefined}\n aria-required=\"true\"\n aria-describedby={dateError ? dateErrorId : undefined}\n onChange={(event) => {\n if (dateError) setDateError(null)\n if (submitError) setSubmitError(null)\n setDraftDate(event.target.value)\n }}\n />\n {dateError ? (\n <p id={dateErrorId} className=\"text-xs text-destructive\">\n {dateError}\n </p>\n ) : null}\n <input\n placeholder={t('customers.people.detail.inline.nextInteractionName')}\n className={cn(\n 'w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n nameError ? 'border-destructive aria-invalid:border-destructive aria-invalid:ring-destructive' : null,\n )}\n value={draftName}\n aria-invalid={nameError ? 'true' : undefined}\n aria-required=\"true\"\n aria-describedby={nameError ? nameErrorId : undefined}\n onChange={(event) => {\n if (submitError) setSubmitError(null)\n if (nameError) setNameError(null)\n setDraftName(event.target.value)\n }}\n />\n {nameError ? (\n <p id={nameErrorId} className=\"text-xs text-destructive\">\n {nameError}\n </p>\n ) : null}\n <input\n placeholder={t('customers.people.detail.inline.nextInteractionRef')}\n className=\"w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n value={draftRefId}\n onChange={(event) => {\n if (submitError) setSubmitError(null)\n setDraftRefId(event.target.value)\n }}\n />\n <AppearanceSelector\n icon={draftIcon || null}\n color={draftColor}\n onIconChange={(next) => {\n if (submitError) setSubmitError(null)\n setDraftIcon(next ?? '')\n }}\n onColorChange={(next) => {\n if (submitError) setSubmitError(null)\n setDraftColor(next)\n }}\n iconSuggestions={ICON_SUGGESTIONS}\n disabled={saving}\n labels={appearanceLabels}\n />\n {submitError && !dateError && !nameError ? (\n <p className=\"text-xs text-destructive\">{submitError}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button type=\"submit\" size=\"sm\" disabled={saving}>\n {saving ? <Loader2 className=\"mr-2 h-3.5 w-3.5 animate-spin\" /> : null}\n {t('customers.people.detail.inline.saveShortcut', 'Save \u2318\u23CE / Ctrl+Enter')}\n </Button>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={() => setEditing(false)} disabled={saving}>\n {t('customers.people.detail.inline.cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n onClick={async () => {\n setDraftDate('')\n setDraftName('')\n setDraftRefId('')\n setDraftIcon('')\n setDraftColor(null)\n setDateError(null)\n setNameError(null)\n setSubmitError(null)\n setSaving(true)\n try {\n await onSave(null)\n setEditing(false)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.inline.error')\n setSubmitError(message)\n } finally {\n setSaving(false)\n }\n }}\n disabled={saving}\n >\n {t('customers.people.detail.inline.clear')}\n </Button>\n </div>\n </form>\n ) : (\n <div className=\"mt-1 text-sm\">\n {valueAt ? (\n <div className=\"flex items-start gap-3\">\n {valueIcon ? (\n <span className=\"inline-flex h-8 w-8 items-center justify-center rounded border border-border bg-card\">\n {renderDictionaryIcon(valueIcon, 'h-4 w-4')}\n </span>\n ) : null}\n <div className=\"flex-1\">\n <span className=\"block\">{formatDateTime(valueAt)}</span>\n {valueName ? <span className=\"text-xs text-muted-foreground\">{valueName}</span> : null}\n {valueRefId ? <span className=\"text-xs text-muted-foreground\">#{valueRefId}</span> : null}\n </div>\n {valueColor ? renderDictionaryColor(valueColor, 'h-3 w-3 rounded-full border border-border') : null}\n </div>\n ) : (\n <span className=\"text-muted-foreground\">{emptyLabel}</span>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -383,7 +383,7 @@ function TasksSection({
|
|
|
383
383
|
const isCanceled = task.status === "canceled";
|
|
384
384
|
const checkboxId = `person-task-${task.id}`;
|
|
385
385
|
const isPendingToggle = pendingTaskId === task.todoId;
|
|
386
|
-
return /* @__PURE__ */ jsxs("article", { className: "group space-y-3 rounded-lg border bg-card p-4 transition hover:border-border/
|
|
386
|
+
return /* @__PURE__ */ jsxs("article", { className: "group space-y-3 rounded-lg border bg-card p-4 transition hover:border-border/70", children: [
|
|
387
387
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
|
|
388
388
|
/* @__PURE__ */ jsx(
|
|
389
389
|
TimelineItemHeader,
|