@open-mercato/core 0.6.5-develop.5337.1.534b781eac → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +1 -1
- package/dist/bootstrap.js +46 -6
- package/dist/bootstrap.js.map +2 -2
- package/dist/generated/entities/organization/index.js +2 -0
- package/dist/generated/entities/organization/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +1 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/crmFixtures.js +4 -0
- package/dist/helpers/integration/crmFixtures.js.map +2 -2
- package/dist/modules/attachments/api/library/route.js +2 -2
- package/dist/modules/attachments/api/library/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +2 -0
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +9 -5
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/attachments/lib/access.js +18 -0
- package/dist/modules/attachments/lib/access.js.map +2 -2
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +3 -2
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
- package/dist/modules/audit_logs/data/entities.js +2 -1
- package/dist/modules/audit_logs/data/entities.js.map +2 -2
- package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
- package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
- package/dist/modules/audit_logs/services/accessLogService.js +10 -0
- package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +9 -0
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/login.js +4 -13
- package/dist/modules/auth/api/login.js.map +2 -2
- package/dist/modules/auth/commands/users.js +20 -14
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/entities.js +4 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +35 -2
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/lib/consentIntegrity.js +3 -3
- package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
- package/dist/modules/auth/migrations/Migration20260610120000.js +30 -0
- package/dist/modules/auth/migrations/Migration20260610120000.js.map +7 -0
- package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
- package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
- package/dist/modules/auth/services/authService.js +5 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +3 -2
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/catalog/ai-tools/configuration-pack.js.map +1 -1
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +1 -1
- package/dist/modules/catalog/ai-tools/products-pack.js.map +1 -1
- package/dist/modules/catalog/ai-tools/variants-pack.js.map +1 -1
- package/dist/modules/communication_channels/data/entities.js.map +1 -1
- package/dist/modules/communication_channels/encryption.js.map +1 -1
- package/dist/modules/communication_channels/lib/thread-matcher.js.map +1 -1
- package/dist/modules/communication_channels/lib/thread-token.js.map +1 -1
- package/dist/modules/currencies/api/currencies/route.js +4 -3
- package/dist/modules/currencies/api/currencies/route.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles.js +2 -1
- package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
- package/dist/modules/customer_accounts/events.js +1 -1
- package/dist/modules/customer_accounts/events.js.map +1 -1
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +1 -1
- package/dist/modules/customers/acl.js +1 -1
- package/dist/modules/customers/acl.js.map +1 -1
- package/dist/modules/customers/ai-tools/companies-pack.js.map +1 -1
- package/dist/modules/customers/ai-tools/deals-pack.js.map +1 -1
- package/dist/modules/customers/ai-tools/people-pack.js.map +1 -1
- package/dist/modules/customers/api/companies/route.js +4 -4
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +43 -2
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/deals/summary/route.js +402 -0
- package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
- package/dist/modules/customers/api/people/route.js +4 -4
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +221 -56
- package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/cli.js +15 -9
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/commands/addresses.js +5 -5
- package/dist/modules/customers/commands/addresses.js.map +2 -2
- package/dist/modules/customers/commands/comments.js +5 -5
- package/dist/modules/customers/commands/comments.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +2 -2
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/entity-roles.js +2 -1
- package/dist/modules/customers/commands/entity-roles.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +8 -5
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/shared.js +21 -6
- package/dist/modules/customers/commands/shared.js.map +2 -2
- package/dist/modules/customers/commands/tags.js +3 -3
- package/dist/modules/customers/commands/tags.js.map +2 -2
- package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
- package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +100 -17
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +21 -8
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
- package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
- package/dist/modules/customers/lib/dealsMetrics.js +82 -0
- package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.js.map +1 -1
- package/dist/modules/data_sync/api/run.js +1 -1
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +214 -0
- package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
- package/dist/modules/directory/api/organizations/route.js +7 -0
- package/dist/modules/directory/api/organizations/route.js.map +3 -3
- package/dist/modules/directory/backend/directory/branding/page.js +214 -0
- package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
- package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
- package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
- package/dist/modules/directory/commands/organizations.js +8 -1
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/directory/data/entities.js +3 -0
- package/dist/modules/directory/data/entities.js.map +2 -2
- package/dist/modules/directory/data/validators.js +9 -0
- package/dist/modules/directory/data/validators.js.map +2 -2
- package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
- package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +59 -27
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +2 -1
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/entities.js +7 -0
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +26 -15
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
- package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
- package/dist/modules/payment_gateways/api/transactions/route.js +2 -4
- package/dist/modules/payment_gateways/api/transactions/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/[id]/route.js +7 -2
- package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/route.js +1 -1
- package/dist/modules/progress/api/jobs/route.js.map +2 -2
- package/dist/modules/progress/lib/progressServiceImpl.js +8 -2
- package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
- package/dist/modules/query_index/data/entities.js +2 -1
- package/dist/modules/query_index/data/entities.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +4 -2
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
- package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
- package/dist/modules/resources/api/resources.js +2 -3
- package/dist/modules/resources/api/resources.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +2 -2
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +7 -5
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
- package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
- package/dist/modules/staff/api/team-members.js +9 -2
- package/dist/modules/staff/api/team-members.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/commands/team-members.js +1 -1
- package/dist/modules/staff/commands/team-members.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +1 -1
- package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
- package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
- package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
- package/dist/modules/sync_excel/api/import/route.js +1 -1
- package/dist/modules/sync_excel/api/import/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/route.js +3 -2
- package/dist/modules/workflows/api/definitions/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
- package/generated/entities/organization/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +11 -12
- package/src/bootstrap.ts +65 -7
- package/src/helpers/integration/crmFixtures.ts +21 -1
- package/src/modules/attachments/AGENTS.md +79 -0
- package/src/modules/attachments/api/library/route.ts +2 -2
- package/src/modules/attachments/api/route.ts +2 -0
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +6 -6
- package/src/modules/attachments/lib/access.ts +36 -0
- package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +14 -2
- package/src/modules/audit_logs/data/entities.ts +1 -0
- package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
- package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
- package/src/modules/audit_logs/services/accessLogService.ts +15 -0
- package/src/modules/auth/api/admin/nav.ts +9 -0
- package/src/modules/auth/api/login.ts +13 -13
- package/src/modules/auth/commands/users.ts +32 -15
- package/src/modules/auth/data/entities.ts +13 -1
- package/src/modules/auth/i18n/de.json +0 -1
- package/src/modules/auth/i18n/en.json +0 -1
- package/src/modules/auth/i18n/es.json +0 -1
- package/src/modules/auth/i18n/pl.json +0 -1
- package/src/modules/auth/lib/backendChrome.tsx +37 -1
- package/src/modules/auth/lib/consentIntegrity.ts +6 -3
- package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -10
- package/src/modules/auth/migrations/Migration20260610120000.ts +53 -0
- package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
- package/src/modules/auth/services/authService.ts +24 -4
- package/src/modules/auth/services/rbacService.ts +11 -2
- package/src/modules/catalog/ai-tools/configuration-pack.ts +1 -1
- package/src/modules/catalog/ai-tools/prices-offers-pack.ts +1 -1
- package/src/modules/catalog/ai-tools/products-pack.ts +1 -1
- package/src/modules/catalog/ai-tools/variants-pack.ts +1 -1
- package/src/modules/communication_channels/data/entities.ts +2 -2
- package/src/modules/communication_channels/encryption.ts +1 -1
- package/src/modules/communication_channels/lib/adapter.ts +1 -1
- package/src/modules/communication_channels/lib/thread-matcher.ts +1 -1
- package/src/modules/communication_channels/lib/thread-token.ts +1 -1
- package/src/modules/currencies/api/currencies/route.ts +4 -3
- package/src/modules/customer_accounts/api/admin/roles.ts +2 -1
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
- package/src/modules/customer_accounts/events.ts +1 -1
- package/src/modules/customer_accounts/lib/resolveTenantContext.ts +2 -2
- package/src/modules/customers/acl.ts +1 -1
- package/src/modules/customers/ai-tools/companies-pack.ts +1 -1
- package/src/modules/customers/ai-tools/deals-pack.ts +1 -1
- package/src/modules/customers/ai-tools/people-pack.ts +1 -1
- package/src/modules/customers/api/companies/route.ts +4 -4
- package/src/modules/customers/api/deals/route.ts +51 -2
- package/src/modules/customers/api/deals/summary/route.ts +496 -0
- package/src/modules/customers/api/people/route.ts +4 -4
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
- package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
- package/src/modules/customers/cli.ts +15 -15
- package/src/modules/customers/commands/addresses.ts +5 -5
- package/src/modules/customers/commands/comments.ts +5 -5
- package/src/modules/customers/commands/deals.ts +2 -2
- package/src/modules/customers/commands/entity-roles.ts +2 -1
- package/src/modules/customers/commands/interactions.ts +8 -5
- package/src/modules/customers/commands/shared.ts +26 -4
- package/src/modules/customers/commands/tags.ts +3 -3
- package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
- package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
- package/src/modules/customers/components/detail/DealForm.tsx +121 -19
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
- package/src/modules/customers/components/detail/assignableStaff.ts +32 -8
- package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
- package/src/modules/customers/i18n/de.json +43 -0
- package/src/modules/customers/i18n/en.json +43 -0
- package/src/modules/customers/i18n/es.json +43 -0
- package/src/modules/customers/i18n/pl.json +43 -0
- package/src/modules/customers/lib/dealsMetrics.ts +159 -0
- package/src/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.ts +1 -1
- package/src/modules/data_sync/api/run.ts +1 -1
- package/src/modules/directory/api/organization-branding/route.ts +238 -0
- package/src/modules/directory/api/organizations/route.ts +7 -0
- package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
- package/src/modules/directory/commands/organizations.ts +9 -1
- package/src/modules/directory/data/entities.ts +3 -0
- package/src/modules/directory/data/validators.ts +12 -0
- package/src/modules/directory/i18n/de.json +21 -0
- package/src/modules/directory/i18n/en.json +21 -0
- package/src/modules/directory/i18n/es.json +21 -0
- package/src/modules/directory/i18n/pl.json +21 -0
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
- package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
- package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
- package/src/modules/directory/utils/organizationScope.ts +85 -30
- package/src/modules/entities/api/definitions.batch.ts +11 -7
- package/src/modules/entities/api/entities.ts +11 -0
- package/src/modules/entities/api/records.ts +46 -25
- package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
- package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
- package/src/modules/entities/i18n/de.json +1 -0
- package/src/modules/entities/i18n/en.json +1 -0
- package/src/modules/entities/i18n/es.json +1 -0
- package/src/modules/entities/i18n/pl.json +1 -0
- package/src/modules/payment_gateways/api/transactions/route.ts +2 -5
- package/src/modules/progress/api/jobs/[id]/route.ts +6 -1
- package/src/modules/progress/api/jobs/route.ts +1 -1
- package/src/modules/progress/lib/progressServiceImpl.ts +7 -1
- package/src/modules/query_index/data/entities.ts +1 -0
- package/src/modules/query_index/lib/engine.ts +11 -5
- package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
- package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
- package/src/modules/resources/api/resources.ts +2 -3
- package/src/modules/sales/api/documents/factory.ts +2 -2
- package/src/modules/sales/commands/documents.ts +7 -5
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
- package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
- package/src/modules/staff/AGENTS.md +1 -1
- package/src/modules/staff/api/team-members.ts +9 -2
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
- package/src/modules/staff/commands/team-members.ts +5 -2
- package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
- package/src/modules/sync_excel/api/import/route.ts +1 -1
- package/src/modules/workflows/api/definitions/route.ts +3 -2
- package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
- package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
- package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
- package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { CheckCircle } from 'lucide-react'
|
|
5
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
6
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
8
|
+
import { KpiCard, DeltaBadge, Sparkline } from '@open-mercato/ui/backend/charts'
|
|
9
|
+
import { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'
|
|
10
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
11
|
+
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
12
|
+
import type { DictionaryMap } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
|
|
13
|
+
import { PipelineStageBar } from './kpi/PipelineStageBar'
|
|
14
|
+
|
|
15
|
+
type DeltaDirection = 'up' | 'down' | 'unchanged'
|
|
16
|
+
|
|
17
|
+
type SummaryDelta = {
|
|
18
|
+
value: number
|
|
19
|
+
direction: DeltaDirection
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type DealsSummaryResponse = {
|
|
23
|
+
baseCurrencyCode: string | null
|
|
24
|
+
convertedAll: boolean
|
|
25
|
+
missingRateCurrencies: string[]
|
|
26
|
+
pipelineValue: {
|
|
27
|
+
value: number
|
|
28
|
+
delta: SummaryDelta
|
|
29
|
+
stages: { stage: string | null; count: number; value: number }[]
|
|
30
|
+
}
|
|
31
|
+
activeDeals: {
|
|
32
|
+
value: number
|
|
33
|
+
delta: SummaryDelta
|
|
34
|
+
ownersCount: number
|
|
35
|
+
needAttention: number
|
|
36
|
+
owners: { id: string; count: number }[]
|
|
37
|
+
ownersOverflow: number
|
|
38
|
+
}
|
|
39
|
+
wonThisQuarter: {
|
|
40
|
+
value: number
|
|
41
|
+
delta: SummaryDelta
|
|
42
|
+
dealsClosed: number
|
|
43
|
+
avgDeal: number
|
|
44
|
+
}
|
|
45
|
+
winRate: {
|
|
46
|
+
value: number
|
|
47
|
+
deltaPp: number
|
|
48
|
+
direction: DeltaDirection
|
|
49
|
+
previousValue: number
|
|
50
|
+
series: { period: string; rate: number }[]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type DealsKpiStripProps = {
|
|
55
|
+
ownerNames: Record<string, string>
|
|
56
|
+
stageDictionary: DictionaryMap
|
|
57
|
+
pipelineCount: number
|
|
58
|
+
className?: string
|
|
59
|
+
/** Bumped by the host when the active org scope changes — forces a KPI refetch so the cards never show another org's data. */
|
|
60
|
+
scopeVersion?: number
|
|
61
|
+
/** Bumped by the host on manual refresh / after mutations — forces a KPI refetch so totals stay in sync with the table. */
|
|
62
|
+
reloadToken?: number
|
|
63
|
+
onNeedsAttentionClick?: () => void
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const compactNumberFormatter = new Intl.NumberFormat(undefined, {
|
|
67
|
+
notation: 'compact',
|
|
68
|
+
maximumFractionDigits: 1,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
function formatCompact(value: number): string {
|
|
72
|
+
return compactNumberFormatter.format(value)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildCurrencySuffix(code: string | null, convertedAll: boolean): string {
|
|
76
|
+
if (!code) return convertedAll ? '' : '≈'
|
|
77
|
+
return convertedAll ? code : `≈ ${code}`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const KPI_TITLE_CLASS = 'text-xs font-semibold uppercase tracking-wide text-muted-foreground'
|
|
81
|
+
|
|
82
|
+
function DealKpiCard(props: React.ComponentProps<typeof KpiCard>) {
|
|
83
|
+
return <KpiCard titleClassName={KPI_TITLE_CLASS} {...props} />
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function KpiDeltaBadge({
|
|
87
|
+
direction,
|
|
88
|
+
value,
|
|
89
|
+
unit,
|
|
90
|
+
title,
|
|
91
|
+
}: {
|
|
92
|
+
direction: DeltaDirection
|
|
93
|
+
value: number
|
|
94
|
+
unit?: string
|
|
95
|
+
title: string
|
|
96
|
+
}) {
|
|
97
|
+
if (direction === 'unchanged' && value === 0) {
|
|
98
|
+
return (
|
|
99
|
+
<span
|
|
100
|
+
className="inline-flex items-center rounded-md bg-status-neutral-bg px-2 py-0.5 text-xs font-medium text-status-neutral-text"
|
|
101
|
+
title={title}
|
|
102
|
+
>
|
|
103
|
+
--
|
|
104
|
+
</span>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
return <DeltaBadge direction={direction} value={value} unit={unit} title={title} />
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
111
|
+
return typeof value === 'object' && value !== null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Guard the summary payload before rendering: a non-conforming response (an unrelated
|
|
115
|
+
// endpoint mock, an error body, or a future contract drift) must surface the error card,
|
|
116
|
+
// never crash the whole deals page by dereferencing missing sections/arrays.
|
|
117
|
+
function isDealsSummaryResponse(value: unknown): value is DealsSummaryResponse {
|
|
118
|
+
if (!isObject(value)) return false
|
|
119
|
+
const { pipelineValue, activeDeals, wonThisQuarter, winRate } = value
|
|
120
|
+
return (
|
|
121
|
+
isObject(pipelineValue) && Array.isArray(pipelineValue.stages) &&
|
|
122
|
+
isObject(activeDeals) && Array.isArray(activeDeals.owners) &&
|
|
123
|
+
isObject(wonThisQuarter) &&
|
|
124
|
+
isObject(winRate) && Array.isArray(winRate.series)
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function DealsKpiStrip({
|
|
129
|
+
ownerNames,
|
|
130
|
+
stageDictionary,
|
|
131
|
+
pipelineCount,
|
|
132
|
+
className,
|
|
133
|
+
scopeVersion,
|
|
134
|
+
reloadToken,
|
|
135
|
+
onNeedsAttentionClick,
|
|
136
|
+
}: DealsKpiStripProps) {
|
|
137
|
+
const t = useT()
|
|
138
|
+
const locale = useLocale()
|
|
139
|
+
const pluralCat = React.useCallback((count: number): string => {
|
|
140
|
+
try {
|
|
141
|
+
return new Intl.PluralRules(locale).select(count)
|
|
142
|
+
} catch {
|
|
143
|
+
return count === 1 ? 'one' : 'other'
|
|
144
|
+
}
|
|
145
|
+
}, [locale])
|
|
146
|
+
const pf = React.useCallback((base: string, count: number): string => {
|
|
147
|
+
const cat = pluralCat(count)
|
|
148
|
+
const key = `${base}.${cat}`
|
|
149
|
+
const out = t(key, { count })
|
|
150
|
+
return out === key ? t(`${base}.other`, { count }) : out
|
|
151
|
+
}, [t, pluralCat])
|
|
152
|
+
const [data, setData] = React.useState<DealsSummaryResponse | null>(null)
|
|
153
|
+
const [loading, setLoading] = React.useState(true)
|
|
154
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
155
|
+
const [retryToken, setRetryToken] = React.useState(0)
|
|
156
|
+
const previousScopeVersionRef = React.useRef(scopeVersion)
|
|
157
|
+
|
|
158
|
+
const retry = React.useCallback(() => {
|
|
159
|
+
setRetryToken((token) => token + 1)
|
|
160
|
+
}, [])
|
|
161
|
+
|
|
162
|
+
React.useEffect(() => {
|
|
163
|
+
let cancelled = false
|
|
164
|
+
const scopeChanged = previousScopeVersionRef.current !== scopeVersion
|
|
165
|
+
previousScopeVersionRef.current = scopeVersion
|
|
166
|
+
if (scopeChanged) setData(null)
|
|
167
|
+
setLoading(true)
|
|
168
|
+
setError(null)
|
|
169
|
+
apiCall<DealsSummaryResponse>('/api/customers/deals/summary')
|
|
170
|
+
.then((call) => {
|
|
171
|
+
if (cancelled) return
|
|
172
|
+
if (!call.ok || !isDealsSummaryResponse(call.result)) {
|
|
173
|
+
setError(t('customers.deals.list.kpi.error'))
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
setData(call.result)
|
|
177
|
+
})
|
|
178
|
+
.catch(() => {
|
|
179
|
+
if (cancelled) return
|
|
180
|
+
setError(t('customers.deals.list.kpi.error'))
|
|
181
|
+
})
|
|
182
|
+
.finally(() => {
|
|
183
|
+
if (!cancelled) setLoading(false)
|
|
184
|
+
})
|
|
185
|
+
return () => {
|
|
186
|
+
cancelled = true
|
|
187
|
+
}
|
|
188
|
+
}, [t, scopeVersion, reloadToken, retryToken])
|
|
189
|
+
|
|
190
|
+
const wrapperClassName = cn('space-y-2', className)
|
|
191
|
+
const gridClassName = 'grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4'
|
|
192
|
+
|
|
193
|
+
if (loading && !data) {
|
|
194
|
+
return (
|
|
195
|
+
<div className={wrapperClassName}>
|
|
196
|
+
<div className={gridClassName}>
|
|
197
|
+
<DealKpiCard loading title={t('customers.deals.list.kpi.pipelineValue')} value={null} />
|
|
198
|
+
<DealKpiCard loading title={t('customers.deals.list.kpi.activeDeals')} value={null} />
|
|
199
|
+
<DealKpiCard loading title={t('customers.deals.list.kpi.wonThisQuarter')} value={null} />
|
|
200
|
+
<DealKpiCard loading title={t('customers.deals.list.kpi.winRate')} value={null} />
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!data) {
|
|
207
|
+
const errorMessage = error ?? t('customers.deals.list.kpi.error')
|
|
208
|
+
return (
|
|
209
|
+
<div className={wrapperClassName}>
|
|
210
|
+
<div className="flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-4">
|
|
211
|
+
<p className="text-sm text-destructive">{errorMessage}</p>
|
|
212
|
+
<Button type="button" variant="destructive-outline" size="sm" onClick={retry}>
|
|
213
|
+
{t('customers.deals.list.kpi.retry')}
|
|
214
|
+
</Button>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const currencySuffix = buildCurrencySuffix(data.baseCurrencyCode, data.convertedAll)
|
|
221
|
+
const unassignedLabel = t('customers.deals.list.kpi.unassignedStage')
|
|
222
|
+
const deltaTooltip = t('customers.deals.list.kpi.deltaTooltip')
|
|
223
|
+
const deltaUnavailableTooltip = t('customers.deals.list.kpi.deltaUnavailable')
|
|
224
|
+
const scopeLabel = t('customers.deals.list.kpi.scopeAllPipelinesThisQuarter')
|
|
225
|
+
const unknownOwner = t('customers.deals.list.unknownOwner')
|
|
226
|
+
const currencyHint = !data.convertedAll
|
|
227
|
+
? data.baseCurrencyCode
|
|
228
|
+
? t('customers.deals.list.kpi.currencyApproxMissing', {
|
|
229
|
+
currencies: data.missingRateCurrencies.length ? data.missingRateCurrencies.join(', ') : currencySuffix,
|
|
230
|
+
})
|
|
231
|
+
: t('customers.deals.list.kpi.currencyApproxNoBase')
|
|
232
|
+
: null
|
|
233
|
+
const attentionLabel = pf('customers.deals.list.kpi.frag.needAttention', data.activeDeals.needAttention)
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div className={wrapperClassName}>
|
|
237
|
+
{error ? (
|
|
238
|
+
<div className="flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2">
|
|
239
|
+
<p className="text-xs text-destructive">{error}</p>
|
|
240
|
+
<Button type="button" variant="destructive-outline" size="2xs" onClick={retry}>
|
|
241
|
+
{t('customers.deals.list.kpi.retry')}
|
|
242
|
+
</Button>
|
|
243
|
+
</div>
|
|
244
|
+
) : null}
|
|
245
|
+
{loading ? (
|
|
246
|
+
<div className="flex items-center justify-end gap-2 text-xs text-muted-foreground">
|
|
247
|
+
<Spinner className="h-3 w-3" />
|
|
248
|
+
<span>{t('customers.deals.list.kpi.updating')}</span>
|
|
249
|
+
</div>
|
|
250
|
+
) : null}
|
|
251
|
+
<div className={gridClassName}>
|
|
252
|
+
<DealKpiCard
|
|
253
|
+
title={t('customers.deals.list.kpi.pipelineValue')}
|
|
254
|
+
value={data.pipelineValue.value}
|
|
255
|
+
formatValue={formatCompact}
|
|
256
|
+
suffix={currencySuffix}
|
|
257
|
+
headerAction={
|
|
258
|
+
<KpiDeltaBadge
|
|
259
|
+
direction={data.pipelineValue.delta.direction}
|
|
260
|
+
value={data.pipelineValue.delta.value}
|
|
261
|
+
title={data.pipelineValue.delta.direction === 'unchanged' && data.pipelineValue.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}
|
|
262
|
+
/>
|
|
263
|
+
}
|
|
264
|
+
footer={
|
|
265
|
+
<div className="space-y-2">
|
|
266
|
+
<p className="text-xs text-muted-foreground">{scopeLabel}</p>
|
|
267
|
+
<p className="text-xs text-muted-foreground">
|
|
268
|
+
{t('customers.deals.list.kpi.activeAcrossPipelines', {
|
|
269
|
+
deals: pf('customers.deals.list.kpi.frag.activeDeals', data.activeDeals.value),
|
|
270
|
+
pipelines: pf('customers.deals.list.kpi.frag.pipelines', pipelineCount),
|
|
271
|
+
})}
|
|
272
|
+
</p>
|
|
273
|
+
{currencyHint ? <p className="text-xs text-muted-foreground">{currencyHint}</p> : null}
|
|
274
|
+
<PipelineStageBar
|
|
275
|
+
stages={data.pipelineValue.stages}
|
|
276
|
+
stageDictionary={stageDictionary}
|
|
277
|
+
unassignedLabel={unassignedLabel}
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
}
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
<DealKpiCard
|
|
284
|
+
title={t('customers.deals.list.kpi.activeDeals')}
|
|
285
|
+
value={data.activeDeals.value}
|
|
286
|
+
formatValue={formatCompact}
|
|
287
|
+
headerAction={
|
|
288
|
+
<KpiDeltaBadge
|
|
289
|
+
direction={data.activeDeals.delta.direction}
|
|
290
|
+
value={data.activeDeals.delta.value}
|
|
291
|
+
title={data.activeDeals.delta.direction === 'unchanged' && data.activeDeals.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}
|
|
292
|
+
/>
|
|
293
|
+
}
|
|
294
|
+
footer={
|
|
295
|
+
<div className="space-y-2">
|
|
296
|
+
<p className="text-xs text-muted-foreground">{scopeLabel}</p>
|
|
297
|
+
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted-foreground">
|
|
298
|
+
<span>{pf('customers.deals.list.kpi.frag.owners', data.activeDeals.ownersCount)}</span>
|
|
299
|
+
<span aria-hidden="true">·</span>
|
|
300
|
+
{onNeedsAttentionClick && data.activeDeals.needAttention > 0 ? (
|
|
301
|
+
<Button
|
|
302
|
+
type="button"
|
|
303
|
+
variant="link"
|
|
304
|
+
size="2xs"
|
|
305
|
+
className="h-auto p-0 text-xs"
|
|
306
|
+
onClick={onNeedsAttentionClick}
|
|
307
|
+
>
|
|
308
|
+
{attentionLabel}
|
|
309
|
+
</Button>
|
|
310
|
+
) : (
|
|
311
|
+
<span>{attentionLabel}</span>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
314
|
+
{data.activeDeals.owners.length > 0 ? (
|
|
315
|
+
<AvatarStack max={4} size="sm" overflowCount={data.activeDeals.ownersOverflow}>
|
|
316
|
+
{data.activeDeals.owners.map((owner) => {
|
|
317
|
+
const ownerLabel = ownerNames[owner.id]?.trim() || unknownOwner
|
|
318
|
+
return <Avatar key={owner.id} label={ownerLabel} size="sm" />
|
|
319
|
+
})}
|
|
320
|
+
</AvatarStack>
|
|
321
|
+
) : null}
|
|
322
|
+
</div>
|
|
323
|
+
}
|
|
324
|
+
/>
|
|
325
|
+
|
|
326
|
+
<DealKpiCard
|
|
327
|
+
title={t('customers.deals.list.kpi.wonThisQuarter')}
|
|
328
|
+
value={data.wonThisQuarter.value}
|
|
329
|
+
formatValue={formatCompact}
|
|
330
|
+
suffix={currencySuffix}
|
|
331
|
+
headerAction={
|
|
332
|
+
<KpiDeltaBadge
|
|
333
|
+
direction={data.wonThisQuarter.delta.direction}
|
|
334
|
+
value={data.wonThisQuarter.delta.value}
|
|
335
|
+
title={data.wonThisQuarter.delta.direction === 'unchanged' && data.wonThisQuarter.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}
|
|
336
|
+
/>
|
|
337
|
+
}
|
|
338
|
+
footer={
|
|
339
|
+
<div className="space-y-1">
|
|
340
|
+
<p className="text-xs text-muted-foreground">{scopeLabel}</p>
|
|
341
|
+
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
342
|
+
<CheckCircle className="h-4 w-4 text-status-success-text" aria-hidden />
|
|
343
|
+
<span>
|
|
344
|
+
{pf('customers.deals.list.kpi.frag.dealsClosed', data.wonThisQuarter.dealsClosed)}
|
|
345
|
+
</span>
|
|
346
|
+
</div>
|
|
347
|
+
<p className="text-xs text-muted-foreground">
|
|
348
|
+
{t('customers.deals.list.kpi.avgDeal', {
|
|
349
|
+
value: `${formatCompact(data.wonThisQuarter.avgDeal)}${currencySuffix ? ` ${currencySuffix}` : ''}`,
|
|
350
|
+
})}
|
|
351
|
+
</p>
|
|
352
|
+
{currencyHint ? <p className="text-xs text-muted-foreground">{currencyHint}</p> : null}
|
|
353
|
+
</div>
|
|
354
|
+
}
|
|
355
|
+
/>
|
|
356
|
+
|
|
357
|
+
<DealKpiCard
|
|
358
|
+
title={t('customers.deals.list.kpi.winRate')}
|
|
359
|
+
value={data.winRate.value}
|
|
360
|
+
suffix="%"
|
|
361
|
+
headerAction={
|
|
362
|
+
<KpiDeltaBadge
|
|
363
|
+
direction={data.winRate.direction}
|
|
364
|
+
value={Math.abs(data.winRate.deltaPp)}
|
|
365
|
+
unit="pp"
|
|
366
|
+
title={data.winRate.previousValue === 0 ? deltaUnavailableTooltip : deltaTooltip}
|
|
367
|
+
/>
|
|
368
|
+
}
|
|
369
|
+
footer={
|
|
370
|
+
<div className="space-y-2">
|
|
371
|
+
<p className="text-xs text-muted-foreground">{scopeLabel}</p>
|
|
372
|
+
<p className="text-xs text-muted-foreground">
|
|
373
|
+
{t('customers.deals.list.kpi.fromLastQuarter', { value: data.winRate.previousValue })}
|
|
374
|
+
</p>
|
|
375
|
+
<div className="text-primary">
|
|
376
|
+
<Sparkline
|
|
377
|
+
values={data.winRate.series.map((point) => point.rate)}
|
|
378
|
+
ariaLabel={t('customers.deals.list.kpi.winRate')}
|
|
379
|
+
/>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
}
|
|
383
|
+
/>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export default DealsKpiStrip
|
|
@@ -116,7 +116,6 @@ export function ConfirmDealLostDialog({
|
|
|
116
116
|
|
|
117
117
|
<div className="space-y-6 px-7 py-6">
|
|
118
118
|
<Alert variant="warning" className="rounded-md">
|
|
119
|
-
<AlertTriangle className="size-4" />
|
|
120
119
|
<AlertTitle>
|
|
121
120
|
{t('customers.deals.detail.lost.warningTitle', 'This action closes the deal')}
|
|
122
121
|
</AlertTitle>
|
|
@@ -38,6 +38,9 @@ export type DealFormBaseValues = {
|
|
|
38
38
|
companyIds?: string[]
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
type PipelineOption = { id: string; name: string; isDefault: boolean }
|
|
42
|
+
type PipelineStageOption = { id: string; label: string; order: number }
|
|
43
|
+
|
|
41
44
|
export type DealFormSubmitPayload = {
|
|
42
45
|
base: DealFormBaseValues
|
|
43
46
|
custom: Record<string, unknown>
|
|
@@ -64,6 +67,8 @@ export type DealFormProps = {
|
|
|
64
67
|
showAssociationsGroup?: boolean
|
|
65
68
|
showVersionHistory?: boolean
|
|
66
69
|
showCancelAction?: boolean
|
|
70
|
+
initialPipelineOptions?: PipelineOption[]
|
|
71
|
+
initialPipelineStageOptions?: PipelineStageOption[]
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
type EntityOption = {
|
|
@@ -92,6 +97,84 @@ type EntityMultiSelectProps = {
|
|
|
92
97
|
|
|
93
98
|
const DEAL_ENTITY_IDS = [E.customers.customer_deal]
|
|
94
99
|
const CURRENCY_PRIORITY = ['EUR', 'USD', 'GBP', 'PLN'] as const
|
|
100
|
+
const PIPELINE_OPTIONS_TTL_MS = 60_000
|
|
101
|
+
const PIPELINE_STAGE_OPTIONS_TTL_MS = 30_000
|
|
102
|
+
|
|
103
|
+
type MetadataCacheEntry<T> = {
|
|
104
|
+
expiresAt: number
|
|
105
|
+
promise: Promise<T>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let pipelineOptionsCache: MetadataCacheEntry<PipelineOption[]> | null = null
|
|
109
|
+
const pipelineStageOptionsCache = new Map<string, MetadataCacheEntry<PipelineStageOption[]>>()
|
|
110
|
+
|
|
111
|
+
function isFreshCacheEntry<T>(entry: MetadataCacheEntry<T> | null | undefined): entry is MetadataCacheEntry<T> {
|
|
112
|
+
return Boolean(entry && entry.expiresAt > Date.now())
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizePipelineOptions(options: PipelineOption[] | undefined): PipelineOption[] {
|
|
116
|
+
const byId = new Map<string, PipelineOption>()
|
|
117
|
+
for (const option of options ?? []) {
|
|
118
|
+
if (!option.id) continue
|
|
119
|
+
byId.set(option.id, {
|
|
120
|
+
id: option.id,
|
|
121
|
+
name: option.name,
|
|
122
|
+
isDefault: option.isDefault === true,
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
return Array.from(byId.values())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function mergePipelineOptions(seed: PipelineOption[], loaded: PipelineOption[]): PipelineOption[] {
|
|
129
|
+
const byId = new Map<string, PipelineOption>()
|
|
130
|
+
for (const option of seed) byId.set(option.id, option)
|
|
131
|
+
for (const option of loaded) byId.set(option.id, option)
|
|
132
|
+
return Array.from(byId.values())
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizePipelineStageOptions(options: PipelineStageOption[] | undefined): PipelineStageOption[] {
|
|
136
|
+
return [...(options ?? [])]
|
|
137
|
+
.filter((option) => option.id)
|
|
138
|
+
.sort((left, right) => left.order - right.order)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function fetchPipelineOptions(): Promise<PipelineOption[]> {
|
|
142
|
+
if (isFreshCacheEntry(pipelineOptionsCache)) return pipelineOptionsCache.promise
|
|
143
|
+
const entry: MetadataCacheEntry<PipelineOption[]> = {
|
|
144
|
+
expiresAt: Date.now() + PIPELINE_OPTIONS_TTL_MS,
|
|
145
|
+
promise: apiCall<{ items: PipelineOption[] }>('/api/customers/pipelines')
|
|
146
|
+
.then((call) => (call.ok && call.result?.items ? normalizePipelineOptions(call.result.items) : [])),
|
|
147
|
+
}
|
|
148
|
+
pipelineOptionsCache = entry
|
|
149
|
+
try {
|
|
150
|
+
return await entry.promise
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (pipelineOptionsCache === entry) pipelineOptionsCache = null
|
|
153
|
+
throw error
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function fetchPipelineStageOptions(pipelineId: string): Promise<PipelineStageOption[]> {
|
|
158
|
+
const cached = pipelineStageOptionsCache.get(pipelineId)
|
|
159
|
+
if (isFreshCacheEntry(cached)) return cached.promise
|
|
160
|
+
const entry: MetadataCacheEntry<PipelineStageOption[]> = {
|
|
161
|
+
expiresAt: Date.now() + PIPELINE_STAGE_OPTIONS_TTL_MS,
|
|
162
|
+
promise: apiCall<{ items: PipelineStageOption[] }>(`/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(pipelineId)}`)
|
|
163
|
+
.then((call) => (call.ok && call.result?.items ? normalizePipelineStageOptions(call.result.items) : [])),
|
|
164
|
+
}
|
|
165
|
+
pipelineStageOptionsCache.set(pipelineId, entry)
|
|
166
|
+
try {
|
|
167
|
+
return await entry.promise
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (pipelineStageOptionsCache.get(pipelineId) === entry) pipelineStageOptionsCache.delete(pipelineId)
|
|
170
|
+
throw error
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function resetDealPipelineMetadataCacheForTests() {
|
|
175
|
+
pipelineOptionsCache = null
|
|
176
|
+
pipelineStageOptionsCache.clear()
|
|
177
|
+
}
|
|
95
178
|
|
|
96
179
|
const schema = z.object({
|
|
97
180
|
title: z
|
|
@@ -647,6 +730,8 @@ export function DealForm({
|
|
|
647
730
|
showAssociationsGroup = true,
|
|
648
731
|
showVersionHistory = true,
|
|
649
732
|
showCancelAction = true,
|
|
733
|
+
initialPipelineOptions,
|
|
734
|
+
initialPipelineStageOptions,
|
|
650
735
|
}: DealFormProps) {
|
|
651
736
|
const t = useT()
|
|
652
737
|
const [pending, setPending] = React.useState(false)
|
|
@@ -733,25 +818,36 @@ export function DealForm({
|
|
|
733
818
|
const disabled = pending || isSubmitting
|
|
734
819
|
const canDelete = mode === 'edit' && typeof onDelete === 'function'
|
|
735
820
|
|
|
736
|
-
|
|
737
|
-
|
|
821
|
+
const mountedRef = React.useRef(false)
|
|
822
|
+
const seedPipelineOptions = React.useMemo(
|
|
823
|
+
() => normalizePipelineOptions(initialPipelineOptions),
|
|
824
|
+
[initialPipelineOptions],
|
|
825
|
+
)
|
|
826
|
+
const seedPipelineStageOptions = React.useMemo(
|
|
827
|
+
() => Array.isArray(initialPipelineStageOptions) ? normalizePipelineStageOptions(initialPipelineStageOptions) : null,
|
|
828
|
+
[initialPipelineStageOptions],
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
const [pipelines, setPipelines] = React.useState<PipelineOption[]>(() => seedPipelineOptions)
|
|
832
|
+
const [pipelineStages, setPipelineStages] = React.useState<PipelineStageOption[]>(() => seedPipelineStageOptions ?? [])
|
|
738
833
|
|
|
739
|
-
|
|
740
|
-
|
|
834
|
+
React.useEffect(() => {
|
|
835
|
+
mountedRef.current = true
|
|
836
|
+
return () => {
|
|
837
|
+
mountedRef.current = false
|
|
838
|
+
}
|
|
839
|
+
}, [])
|
|
741
840
|
|
|
742
841
|
const loadStagesForPipeline = React.useCallback(async (pipelineId: string) => {
|
|
743
842
|
if (!pipelineId) {
|
|
744
|
-
setPipelineStages([])
|
|
843
|
+
if (mountedRef.current) setPipelineStages([])
|
|
745
844
|
return
|
|
746
845
|
}
|
|
747
846
|
try {
|
|
748
|
-
const
|
|
749
|
-
if (
|
|
750
|
-
const sorted = [...call.result.items].sort((a, b) => a.order - b.order)
|
|
751
|
-
setPipelineStages(sorted)
|
|
752
|
-
}
|
|
847
|
+
const stages = await fetchPipelineStageOptions(pipelineId)
|
|
848
|
+
if (mountedRef.current) setPipelineStages(stages)
|
|
753
849
|
} catch {
|
|
754
|
-
setPipelineStages([])
|
|
850
|
+
if (mountedRef.current) setPipelineStages([])
|
|
755
851
|
}
|
|
756
852
|
}, [])
|
|
757
853
|
|
|
@@ -759,24 +855,30 @@ export function DealForm({
|
|
|
759
855
|
let cancelled = false
|
|
760
856
|
;(async () => {
|
|
761
857
|
try {
|
|
762
|
-
const
|
|
763
|
-
if (cancelled) return
|
|
764
|
-
|
|
765
|
-
setPipelines(call.result.items)
|
|
766
|
-
}
|
|
858
|
+
const loaded = await fetchPipelineOptions()
|
|
859
|
+
if (cancelled || !mountedRef.current) return
|
|
860
|
+
setPipelines(mergePipelineOptions(seedPipelineOptions, loaded))
|
|
767
861
|
} catch {
|
|
768
|
-
|
|
862
|
+
if (!cancelled && mountedRef.current && seedPipelineOptions.length > 0) {
|
|
863
|
+
setPipelines(seedPipelineOptions)
|
|
864
|
+
}
|
|
769
865
|
}
|
|
770
866
|
})().catch(() => {})
|
|
771
867
|
return () => { cancelled = true }
|
|
772
|
-
}, [])
|
|
868
|
+
}, [seedPipelineOptions])
|
|
773
869
|
|
|
774
870
|
React.useEffect(() => {
|
|
775
871
|
const pid = initialValues?.pipelineId
|
|
776
872
|
if (typeof pid === 'string' && pid.length) {
|
|
873
|
+
if (seedPipelineStageOptions) {
|
|
874
|
+
setPipelineStages(seedPipelineStageOptions)
|
|
875
|
+
return
|
|
876
|
+
}
|
|
777
877
|
loadStagesForPipeline(pid).catch(() => {})
|
|
878
|
+
} else {
|
|
879
|
+
setPipelineStages([])
|
|
778
880
|
}
|
|
779
|
-
}, [initialValues?.pipelineId, loadStagesForPipeline])
|
|
881
|
+
}, [initialValues?.pipelineId, loadStagesForPipeline, seedPipelineStageOptions])
|
|
780
882
|
|
|
781
883
|
const baseFields = React.useMemo<CrudField[]>(() => [
|
|
782
884
|
{
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
History,
|
|
14
14
|
Paperclip,
|
|
15
15
|
Plus,
|
|
16
|
+
MapPin,
|
|
16
17
|
} from 'lucide-react'
|
|
17
18
|
import type { SectionAction } from '@open-mercato/ui/backend/detail'
|
|
18
19
|
|
|
@@ -21,6 +22,7 @@ export type PersonTabId =
|
|
|
21
22
|
| 'emails'
|
|
22
23
|
| 'deals'
|
|
23
24
|
| 'companies'
|
|
25
|
+
| 'addresses'
|
|
24
26
|
| 'tasks'
|
|
25
27
|
| 'changelog'
|
|
26
28
|
| 'files'
|
|
@@ -40,13 +42,14 @@ type PersonDetailTabsProps = {
|
|
|
40
42
|
activitiesCount?: number
|
|
41
43
|
dealsCount?: number
|
|
42
44
|
companiesCount?: number
|
|
45
|
+
addressesCount?: number
|
|
43
46
|
tasksCount?: number
|
|
44
47
|
filesCount?: number
|
|
45
48
|
sectionAction?: SectionAction | null
|
|
46
49
|
children: React.ReactNode
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
const SUPPORTED_TAB_IDS = new Set<PersonTabId>(['activities', 'emails', 'deals', 'companies', 'tasks', 'changelog', 'files'])
|
|
52
|
+
const SUPPORTED_TAB_IDS = new Set<PersonTabId>(['activities', 'emails', 'deals', 'companies', 'addresses', 'tasks', 'changelog', 'files'])
|
|
50
53
|
|
|
51
54
|
export function resolveLegacyTab(tab: string | null | undefined): PersonTabId {
|
|
52
55
|
if (!tab) return 'activities'
|
|
@@ -77,6 +80,7 @@ export function PersonDetailTabs({
|
|
|
77
80
|
activitiesCount = 0,
|
|
78
81
|
dealsCount = 0,
|
|
79
82
|
companiesCount = 0,
|
|
83
|
+
addressesCount = 0,
|
|
80
84
|
tasksCount = 0,
|
|
81
85
|
filesCount = 0,
|
|
82
86
|
sectionAction = null,
|
|
@@ -109,6 +113,12 @@ export function PersonDetailTabs({
|
|
|
109
113
|
icon: <Building2 className="size-4" />,
|
|
110
114
|
badge: <CountBadge count={companiesCount} />,
|
|
111
115
|
},
|
|
116
|
+
{
|
|
117
|
+
id: 'addresses',
|
|
118
|
+
label: t('customers.people.detail.tabs.addresses', 'Addresses'),
|
|
119
|
+
icon: <MapPin className="size-4" />,
|
|
120
|
+
badge: <CountBadge count={addressesCount} />,
|
|
121
|
+
},
|
|
112
122
|
{
|
|
113
123
|
id: 'tasks',
|
|
114
124
|
label: t('customers.people.detail.tabs.tasks', 'Tasks'),
|
|
@@ -128,7 +138,7 @@ export function PersonDetailTabs({
|
|
|
128
138
|
badge: <CountBadge count={filesCount} />,
|
|
129
139
|
},
|
|
130
140
|
],
|
|
131
|
-
[t, activitiesCount, dealsCount, companiesCount, tasksCount, filesCount],
|
|
141
|
+
[t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount],
|
|
132
142
|
)
|
|
133
143
|
|
|
134
144
|
const allTabs: TabDef[] = React.useMemo(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { Users, Phone, Check, Mail, Calendar,
|
|
4
|
+
import { Users, Phone, Check, Mail, Calendar, X, StickyNote } from 'lucide-react'
|
|
5
5
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { validatePhoneNumber } from '@open-mercato/shared/lib/phone'
|
|
@@ -498,7 +498,6 @@ export function ScheduleActivityDialog({
|
|
|
498
498
|
{/* Conflict warning */}
|
|
499
499
|
{state.conflict && (
|
|
500
500
|
<Alert variant="warning" className="rounded-lg">
|
|
501
|
-
<AlertTriangle className="size-5" />
|
|
502
501
|
<AlertTitle>
|
|
503
502
|
{t('customers.schedule.conflict.title', 'Calendar conflict')}
|
|
504
503
|
</AlertTitle>
|