@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.223
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/README.md +2 -1
- package/package.json +13 -13
- package/src/lib/components/activity-list.js +12 -5
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.d.ts +1 -4
- package/src/lib/components/companies-bulk-bar.js +7 -20
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/companies-section.d.ts +3 -3
- package/src/lib/components/companies-section.js +149 -46
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +281 -20
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-properties-card.js +99 -8
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +3 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +33 -10
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-properties-card.d.ts +0 -69
- package/src/lib/components/contact-properties-card.js +350 -60
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-reports-to-field.d.ts +46 -0
- package/src/lib/components/contact-reports-to-field.js +67 -0
- package/src/lib/components/contact-reports-to-field.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
- package/src/lib/components/contacts-bulk-bar.js +7 -19
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +46 -33
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-call-actions.d.ts +10 -0
- package/src/lib/components/crm-call-actions.js +26 -9
- package/src/lib/components/crm-call-actions.js.map +1 -1
- package/src/lib/components/crm-next-activity-column.js +1 -1
- package/src/lib/components/crm-next-activity-column.js.map +1 -1
- package/src/lib/components/crm-record-picker.d.ts +15 -0
- package/src/lib/components/crm-record-picker.js +16 -10
- package/src/lib/components/crm-record-picker.js.map +1 -1
- package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
- package/src/lib/components/crm-transfer-buttons.js +167 -0
- package/src/lib/components/crm-transfer-buttons.js.map +1 -0
- package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
- package/src/lib/components/deal-contact-roles-card.js +368 -0
- package/src/lib/components/deal-contact-roles-card.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +3 -2
- package/src/lib/components/deal-detail-page.js +25 -6
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.d.ts +10 -0
- package/src/lib/components/deal-edit-drawer.js +219 -10
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-properties-card.d.ts +7 -0
- package/src/lib/components/deal-properties-card.js +34 -3
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/deal-stage-card.d.ts +4 -0
- package/src/lib/components/deal-stage-card.js +10 -2
- package/src/lib/components/deal-stage-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.d.ts +0 -3
- package/src/lib/components/deals-bulk-bar.js +7 -20
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/deals-section.d.ts +3 -3
- package/src/lib/components/deals-section.js +123 -63
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.js +31 -17
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/lead-convert-dialog.d.ts +4 -0
- package/src/lib/components/lead-convert-dialog.js +23 -1
- package/src/lib/components/lead-convert-dialog.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +8 -2
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-profile-fields.d.ts +71 -0
- package/src/lib/components/lead-profile-fields.js +386 -0
- package/src/lib/components/lead-profile-fields.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +2 -1
- package/src/lib/components/lead-properties-card.js +85 -176
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-source-select.d.ts +4 -23
- package/src/lib/components/lead-source-select.js +9 -73
- package/src/lib/components/lead-source-select.js.map +1 -1
- package/src/lib/components/lead-status-chip.d.ts +9 -3
- package/src/lib/components/lead-status-chip.js +8 -4
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-status-options.d.ts +20 -0
- package/src/lib/components/lead-status-options.js +51 -0
- package/src/lib/components/lead-status-options.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
- package/src/lib/components/lead-unqualify-dialog.js +50 -18
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.d.ts +0 -3
- package/src/lib/components/leads-bulk-bar.js +29 -29
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +120 -68
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/linked-deals-card.d.ts +4 -0
- package/src/lib/components/linked-deals-card.js +47 -3
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/log-activity-dialog.js +43 -3
- package/src/lib/components/log-activity-dialog.js.map +1 -1
- package/src/lib/components/new-contact-drawer.d.ts +8 -0
- package/src/lib/components/new-contact-drawer.js +108 -6
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/new-lead-drawer.d.ts +9 -2
- package/src/lib/components/new-lead-drawer.js +88 -121
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/picklist-select.d.ts +36 -0
- package/src/lib/components/picklist-select.js +114 -0
- package/src/lib/components/picklist-select.js.map +1 -0
- package/src/lib/components/picklist-values-card.d.ts +34 -0
- package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
- package/src/lib/components/picklist-values-card.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
- package/src/lib/components/pipeline-stages-dialog.js +26 -4
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
- package/src/lib/components/pipelines-dialog.js +15 -6
- package/src/lib/components/pipelines-dialog.js.map +1 -1
- package/src/lib/components/recent-activity-feed.js +20 -5
- package/src/lib/components/recent-activity-feed.js.map +1 -1
- package/src/lib/components/recipes-card.js +4 -2
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-tasks-card.js +15 -8
- package/src/lib/components/record-tasks-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.d.ts +6 -0
- package/src/lib/components/reports/forecast-card.js +101 -3
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
- package/src/lib/components/reports/lead-funnel-card.js +9 -5
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/lead-source-card.d.ts +23 -0
- package/src/lib/components/reports/lead-source-card.js +318 -0
- package/src/lib/components/reports/lead-source-card.js.map +1 -0
- package/src/lib/components/reports/pipeline-card.js +2 -1
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/report-export.js +1 -1
- package/src/lib/components/reports/report-export.js.map +1 -1
- package/src/lib/components/reports-section.js +8 -0
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
- package/src/lib/components/sharing-rule-drawer.js +108 -19
- package/src/lib/components/sharing-rule-drawer.js.map +1 -1
- package/src/lib/components/sharing-rules-card.js +11 -3
- package/src/lib/components/sharing-rules-card.js.map +1 -1
- package/src/lib/components/task-cells.d.ts +9 -1
- package/src/lib/components/task-cells.js +9 -4
- package/src/lib/components/task-cells.js.map +1 -1
- package/src/lib/components/task-edit-drawer.js +111 -43
- package/src/lib/components/task-edit-drawer.js.map +1 -1
- package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
- package/src/lib/components/tasks-bulk-bar.js +7 -20
- package/src/lib/components/tasks-bulk-bar.js.map +1 -1
- package/src/lib/components/tasks-calendar.d.ts +3 -0
- package/src/lib/components/tasks-calendar.js +8 -1
- package/src/lib/components/tasks-calendar.js.map +1 -1
- package/src/lib/components/tasks-section.d.ts +3 -3
- package/src/lib/components/tasks-section.js +58 -43
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +2 -9
- package/src/lib/constants/api-routes.js +1 -7
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +11 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/constants/company-filters.d.ts +14 -0
- package/src/lib/constants/company-filters.js +51 -1
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +2 -0
- package/src/lib/constants/contact-filters.js +18 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.d.ts +22 -1
- package/src/lib/constants/deal-filters.js +67 -4
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +3 -0
- package/src/lib/declarations.console-server.js +35 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +35 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.d.ts +11 -0
- package/src/lib/declarations.server.js +97 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-company-picklists.d.ts +15 -0
- package/src/lib/hooks/use-company-picklists.js +55 -0
- package/src/lib/hooks/use-company-picklists.js.map +1 -0
- package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
- package/src/lib/hooks/use-crm-picklist.js +53 -0
- package/src/lib/hooks/use-crm-picklist.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
- package/src/lib/hooks/use-crm-record-names.js +45 -19
- package/src/lib/hooks/use-crm-record-names.js.map +1 -1
- package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
- package/src/lib/hooks/use-crm-task-picklists.js +45 -0
- package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +14 -0
- package/src/lib/hooks/use-deals.js +29 -0
- package/src/lib/hooks/use-deals.js.map +1 -1
- package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
- package/src/lib/hooks/use-lead-picklists.js +46 -0
- package/src/lib/hooks/use-lead-picklists.js.map +1 -0
- package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
- package/src/lib/hooks/use-lead-source-picklist.js +12 -22
- package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
- package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
- package/src/lib/hooks/use-lead-status-picklist.js +40 -0
- package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
- package/src/lib/model/companies.d.ts +32 -3
- package/src/lib/model/companies.js +133 -16
- package/src/lib/model/companies.js.map +1 -1
- package/src/lib/model/contact-merge.d.ts +27 -3
- package/src/lib/model/contact-merge.js +81 -7
- package/src/lib/model/contact-merge.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +17 -0
- package/src/lib/model/contact-record.js +17 -2
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-update.d.ts +31 -2
- package/src/lib/model/contact-update.js +14 -0
- package/src/lib/model/contact-update.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +5 -0
- package/src/lib/model/crm-list-query.js +8 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/crm-next-activity.d.ts +107 -0
- package/src/lib/model/crm-next-activity.js +140 -0
- package/src/lib/model/crm-next-activity.js.map +1 -0
- package/src/lib/model/crm-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +259 -0
- package/src/lib/model/crm-recipes.js.map +1 -0
- package/src/lib/model/crm-record-counts.d.ts +36 -0
- package/src/lib/model/crm-record-counts.js +54 -0
- package/src/lib/model/crm-record-counts.js.map +1 -0
- package/src/lib/model/crm-record-lists.d.ts +23 -0
- package/src/lib/model/crm-record-lists.js +161 -0
- package/src/lib/model/crm-record-lists.js.map +1 -0
- package/src/lib/model/crm-reports.d.ts +73 -3
- package/src/lib/model/crm-reports.js +120 -1
- package/src/lib/model/crm-reports.js.map +1 -1
- package/src/lib/model/crm-sharing.d.ts +21 -3
- package/src/lib/model/crm-sharing.js +27 -6
- package/src/lib/model/crm-sharing.js.map +1 -1
- package/src/lib/model/crm-task-reminders.d.ts +152 -0
- package/src/lib/model/crm-task-reminders.js +161 -0
- package/src/lib/model/crm-task-reminders.js.map +1 -0
- package/src/lib/model/deal-board-model.d.ts +8 -2
- package/src/lib/model/deal-board-model.js +15 -2
- package/src/lib/model/deal-board-model.js.map +1 -1
- package/src/lib/model/deal-contact-roles.d.ts +45 -0
- package/src/lib/model/deal-contact-roles.js +73 -0
- package/src/lib/model/deal-contact-roles.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +56 -0
- package/src/lib/model/deal-form-model.js +79 -7
- package/src/lib/model/deal-form-model.js.map +1 -1
- package/src/lib/model/download-text-file.d.ts +23 -0
- package/src/lib/model/download-text-file.js +34 -0
- package/src/lib/model/download-text-file.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +36 -2
- package/src/lib/model/lead-filters.js +123 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/lead-source-direction.d.ts +38 -0
- package/src/lib/model/lead-source-direction.js +52 -0
- package/src/lib/model/lead-source-direction.js.map +1 -0
- package/src/lib/model/next-activity.d.ts +1 -1
- package/src/lib/model/next-activity.js.map +1 -1
- package/src/lib/model/picklist-values.d.ts +119 -0
- package/src/lib/model/picklist-values.js +256 -0
- package/src/lib/model/picklist-values.js.map +1 -0
- package/src/lib/model/record-facts-disclosed.d.ts +81 -0
- package/src/lib/model/record-facts-disclosed.js +281 -0
- package/src/lib/model/record-facts-disclosed.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +215 -34
- package/src/lib/model/record-facts.js +307 -128
- package/src/lib/model/record-facts.js.map +1 -1
- package/src/lib/model/task-picklists.d.ts +52 -0
- package/src/lib/model/task-picklists.js +83 -0
- package/src/lib/model/task-picklists.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +13 -0
- package/src/lib/model/task-routes.js +23 -4
- package/src/lib/model/task-routes.js.map +1 -1
- package/src/lib/plan-entitlements.js +6 -4
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +17 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts.js +220 -14
- package/src/lib/server/api-v1/contacts.js.map +1 -1
- package/src/lib/server/api-v1/crm-activities.js +14 -1
- package/src/lib/server/api-v1/crm-activities.js.map +1 -1
- package/src/lib/server/api-v1/crm-companies.js +123 -14
- package/src/lib/server/api-v1/crm-companies.js.map +1 -1
- package/src/lib/server/api-v1/crm-deals.js +291 -26
- package/src/lib/server/api-v1/crm-deals.js.map +1 -1
- package/src/lib/server/api-v1/crm-leads.js +144 -38
- package/src/lib/server/api-v1/crm-leads.js.map +1 -1
- package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
- package/src/lib/server/api-v1/crm-pipelines.js +8 -2
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
- package/src/lib/server/api-v1/crm-tasks.js +78 -34
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +0 -11
- package/src/lib/server/api-v1/openapi.js +171 -16
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/assign-contact-owner.d.ts +188 -0
- package/src/lib/server/assign-contact-owner.js +446 -0
- package/src/lib/server/assign-contact-owner.js.map +1 -0
- package/src/lib/server/associate-company-by-domain.d.ts +70 -0
- package/src/lib/server/associate-company-by-domain.js +153 -0
- package/src/lib/server/associate-company-by-domain.js.map +1 -0
- package/src/lib/server/automation-steps.d.ts +140 -0
- package/src/lib/server/automation-steps.js +669 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.d.ts +17 -0
- package/src/lib/server/capture-contact.js +95 -26
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/capture-host-contact.d.ts +104 -0
- package/src/lib/server/capture-host-contact.js +151 -0
- package/src/lib/server/capture-host-contact.js.map +1 -0
- package/src/lib/server/company-children.d.ts +25 -0
- package/src/lib/server/company-children.js +42 -0
- package/src/lib/server/company-children.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +18 -0
- package/src/lib/server/company-delete.js +44 -21
- package/src/lib/server/company-delete.js.map +1 -1
- package/src/lib/server/contact-email-history.js +21 -19
- package/src/lib/server/contact-email-history.js.map +1 -1
- package/src/lib/server/contact-remove.js +27 -2
- package/src/lib/server/contact-remove.js.map +1 -1
- package/src/lib/server/contact-reports-to.d.ts +61 -0
- package/src/lib/server/contact-reports-to.js +114 -0
- package/src/lib/server/contact-reports-to.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +42 -1
- package/src/lib/server/contact-update.js +204 -23
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/convert-host-lead.d.ts +46 -1
- package/src/lib/server/convert-host-lead.js +173 -15
- package/src/lib/server/convert-host-lead.js.map +1 -1
- package/src/lib/server/convert-open-lead.js +12 -5
- package/src/lib/server/convert-open-lead.js.map +1 -1
- package/src/lib/server/crm-email-activity.d.ts +118 -0
- package/src/lib/server/crm-email-activity.js +148 -0
- package/src/lib/server/crm-email-activity.js.map +1 -0
- package/src/lib/server/crm-inbound-email.js +1 -1
- package/src/lib/server/crm-inbound-email.js.map +1 -1
- package/src/lib/server/crm-next-activity.d.ts +74 -0
- package/src/lib/server/crm-next-activity.js +194 -0
- package/src/lib/server/crm-next-activity.js.map +1 -0
- package/src/lib/server/crm-record-indexes.d.ts +45 -0
- package/src/lib/server/crm-record-indexes.js +78 -0
- package/src/lib/server/crm-record-indexes.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +9 -1
- package/src/lib/server/crm-sharing.js +23 -3
- package/src/lib/server/crm-sharing.js.map +1 -1
- package/src/lib/server/deal-contact-roles.d.ts +27 -0
- package/src/lib/server/deal-contact-roles.js +89 -0
- package/src/lib/server/deal-contact-roles.js.map +1 -0
- package/src/lib/server/email-send.js +2 -1
- package/src/lib/server/email-send.js.map +1 -1
- package/src/lib/server/lead-convert.d.ts +5 -0
- package/src/lib/server/lead-convert.js +19 -3
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +17 -2
- package/src/lib/server/lead-create.js +46 -14
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +6 -0
- package/src/lib/server/lead-nurturing.js +57 -20
- package/src/lib/server/lead-nurturing.js.map +1 -1
- package/src/lib/server/lead-picklists.d.ts +35 -0
- package/src/lib/server/lead-picklists.js +52 -0
- package/src/lib/server/lead-picklists.js.map +1 -0
- package/src/lib/server/lead-source-picklist.d.ts +1 -13
- package/src/lib/server/lead-source-picklist.js +6 -33
- package/src/lib/server/lead-source-picklist.js.map +1 -1
- package/src/lib/server/merge-contacts.js +23 -5
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/next-activity-routes.js +2 -1
- package/src/lib/server/next-activity-routes.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +53 -0
- package/src/lib/server/person-eraser.js +149 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/person-records.d.ts +69 -0
- package/src/lib/server/person-records.js +317 -0
- package/src/lib/server/person-records.js.map +1 -0
- package/src/lib/server/person-refund.d.ts +97 -0
- package/src/lib/server/person-refund.js +221 -0
- package/src/lib/server/person-refund.js.map +1 -0
- package/src/lib/server/picklist-values.d.ts +21 -0
- package/src/lib/server/picklist-values.js +308 -0
- package/src/lib/server/picklist-values.js.map +1 -0
- package/src/lib/server/read-picklist.d.ts +44 -0
- package/src/lib/server/read-picklist.js +65 -0
- package/src/lib/server/read-picklist.js.map +1 -0
- package/src/lib/server/recipe-routes.js +22 -8
- package/src/lib/server/recipe-routes.js.map +1 -1
- package/src/lib/server/record-email-state.js +7 -1
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.d.ts +3 -0
- package/src/lib/server/record-facts.js +233 -31
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-origin.d.ts +27 -0
- package/src/lib/server/record-origin.js +104 -0
- package/src/lib/server/record-origin.js.map +1 -0
- package/src/lib/server/record-timeline.js +40 -3
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/suite-gate.js +4 -3
- package/src/lib/server/suite-gate.js.map +1 -1
- package/src/lib/server/task-reminders-route.js +2 -1
- package/src/lib/server/task-reminders-route.js.map +1 -1
- package/src/lib/server/task-routes.js +33 -17
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.d.ts +5 -1
- package/src/lib/server-deal-stage.js +10 -5
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +8 -3
- package/src/lib/server.js +47 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +55 -0
- package/src/lib/testing/memory-firestore.js +335 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
- package/src/lib/testing/stand-in-email-send-index.js +49 -0
- package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
- package/src/lib/transfer/activities.d.ts +30 -0
- package/src/lib/transfer/activities.js +132 -0
- package/src/lib/transfer/activities.js.map +1 -0
- package/src/lib/transfer/aliases.d.ts +22 -0
- package/src/lib/transfer/aliases.js +1342 -0
- package/src/lib/transfer/aliases.js.map +1 -0
- package/src/lib/transfer/common.d.ts +222 -0
- package/src/lib/transfer/common.js +832 -0
- package/src/lib/transfer/common.js.map +1 -0
- package/src/lib/transfer/companies.d.ts +43 -0
- package/src/lib/transfer/companies.js +549 -0
- package/src/lib/transfer/companies.js.map +1 -0
- package/src/lib/transfer/contacts.d.ts +31 -0
- package/src/lib/transfer/contacts.js +727 -0
- package/src/lib/transfer/contacts.js.map +1 -0
- package/src/lib/transfer/deals.d.ts +66 -0
- package/src/lib/transfer/deals.js +698 -0
- package/src/lib/transfer/deals.js.map +1 -0
- package/src/lib/transfer/email-templates-package.d.ts +40 -0
- package/src/lib/transfer/email-templates-package.js +76 -0
- package/src/lib/transfer/email-templates-package.js.map +1 -0
- package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
- package/src/lib/transfer/email-templates-package.server.js +176 -0
- package/src/lib/transfer/email-templates-package.server.js.map +1 -0
- package/src/lib/transfer/fields.d.ts +102 -0
- package/src/lib/transfer/fields.js +1755 -0
- package/src/lib/transfer/fields.js.map +1 -0
- package/src/lib/transfer/leads.d.ts +30 -0
- package/src/lib/transfer/leads.js +582 -0
- package/src/lib/transfer/leads.js.map +1 -0
- package/src/lib/transfer/pipelines.d.ts +21 -0
- package/src/lib/transfer/pipelines.js +172 -0
- package/src/lib/transfer/pipelines.js.map +1 -0
- package/src/lib/transfer/register.d.ts +47 -0
- package/src/lib/transfer/register.js +277 -0
- package/src/lib/transfer/register.js.map +1 -0
- package/src/lib/transfer/tasks.d.ts +30 -0
- package/src/lib/transfer/tasks.js +418 -0
- package/src/lib/transfer/tasks.js.map +1 -0
- package/src/lib/transfer/ui.d.ts +20 -0
- package/src/lib/transfer/ui.js +42 -0
- package/src/lib/transfer/ui.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +0 -42
- package/src/lib/components/company-import-drawer.js +0 -99
- package/src/lib/components/company-import-drawer.js.map +0 -1
- package/src/lib/components/contact-import-drawer.d.ts +0 -46
- package/src/lib/components/contact-import-drawer.js +0 -149
- package/src/lib/components/contact-import-drawer.js.map +0 -1
- package/src/lib/components/crm-export-all-button.d.ts +0 -39
- package/src/lib/components/crm-export-all-button.js +0 -98
- package/src/lib/components/crm-export-all-button.js.map +0 -1
- package/src/lib/components/csv-import-drawer.d.ts +0 -139
- package/src/lib/components/csv-import-drawer.js +0 -757
- package/src/lib/components/csv-import-drawer.js.map +0 -1
- package/src/lib/components/deal-import-drawer.d.ts +0 -24
- package/src/lib/components/deal-import-drawer.js +0 -83
- package/src/lib/components/deal-import-drawer.js.map +0 -1
- package/src/lib/components/lead-import-drawer.d.ts +0 -27
- package/src/lib/components/lead-import-drawer.js +0 -118
- package/src/lib/components/lead-import-drawer.js.map +0 -1
- package/src/lib/components/lead-source-values-card.d.ts +0 -27
- package/src/lib/components/lead-source-values-card.js.map +0 -1
- package/src/lib/components/task-import-drawer.d.ts +0 -24
- package/src/lib/components/task-import-drawer.js +0 -82
- package/src/lib/components/task-import-drawer.js.map +0 -1
- package/src/lib/model/companies-csv.d.ts +0 -36
- package/src/lib/model/companies-csv.js +0 -38
- package/src/lib/model/companies-csv.js.map +0 -1
- package/src/lib/model/contacts-csv.d.ts +0 -54
- package/src/lib/model/contacts-csv.js +0 -67
- package/src/lib/model/contacts-csv.js.map +0 -1
- package/src/lib/model/crm-company-import.d.ts +0 -154
- package/src/lib/model/crm-company-import.js +0 -331
- package/src/lib/model/crm-company-import.js.map +0 -1
- package/src/lib/model/crm-csv.d.ts +0 -239
- package/src/lib/model/crm-csv.js +0 -467
- package/src/lib/model/crm-csv.js.map +0 -1
- package/src/lib/model/crm-deal-import.d.ts +0 -112
- package/src/lib/model/crm-deal-import.js +0 -263
- package/src/lib/model/crm-deal-import.js.map +0 -1
- package/src/lib/model/crm-import.d.ts +0 -250
- package/src/lib/model/crm-import.js +0 -467
- package/src/lib/model/crm-import.js.map +0 -1
- package/src/lib/model/crm-lead-import.d.ts +0 -138
- package/src/lib/model/crm-lead-import.js +0 -446
- package/src/lib/model/crm-lead-import.js.map +0 -1
- package/src/lib/model/crm-task-import.d.ts +0 -94
- package/src/lib/model/crm-task-import.js +0 -275
- package/src/lib/model/crm-task-import.js.map +0 -1
- package/src/lib/model/deals-csv.d.ts +0 -32
- package/src/lib/model/deals-csv.js +0 -36
- package/src/lib/model/deals-csv.js.map +0 -1
- package/src/lib/model/import-column-shapes.d.ts +0 -36
- package/src/lib/model/import-column-shapes.js +0 -76
- package/src/lib/model/import-column-shapes.js.map +0 -1
- package/src/lib/model/lead-source-values.d.ts +0 -82
- package/src/lib/model/lead-source-values.js +0 -149
- package/src/lib/model/lead-source-values.js.map +0 -1
- package/src/lib/model/leads-csv.d.ts +0 -40
- package/src/lib/model/leads-csv.js +0 -40
- package/src/lib/model/leads-csv.js.map +0 -1
- package/src/lib/model/tasks-csv.d.ts +0 -28
- package/src/lib/model/tasks-csv.js +0 -28
- package/src/lib/model/tasks-csv.js.map +0 -1
- package/src/lib/server/companies-import.d.ts +0 -57
- package/src/lib/server/companies-import.js +0 -242
- package/src/lib/server/companies-import.js.map +0 -1
- package/src/lib/server/contacts-import.d.ts +0 -109
- package/src/lib/server/contacts-import.js +0 -393
- package/src/lib/server/contacts-import.js.map +0 -1
- package/src/lib/server/deals-import.d.ts +0 -76
- package/src/lib/server/deals-import.js +0 -248
- package/src/lib/server/deals-import.js.map +0 -1
- package/src/lib/server/export-route.d.ts +0 -60
- package/src/lib/server/export-route.js +0 -447
- package/src/lib/server/export-route.js.map +0 -1
- package/src/lib/server/import-context.d.ts +0 -87
- package/src/lib/server/import-context.js +0 -182
- package/src/lib/server/import-context.js.map +0 -1
- package/src/lib/server/lead-source-values.d.ts +0 -50
- package/src/lib/server/lead-source-values.js +0 -191
- package/src/lib/server/lead-source-values.js.map +0 -1
- package/src/lib/server/leads-import.d.ts +0 -84
- package/src/lib/server/leads-import.js +0 -367
- package/src/lib/server/leads-import.js.map +0 -1
- package/src/lib/server/tasks-import.d.ts +0 -54
- package/src/lib/server/tasks-import.js +0 -160
- package/src/lib/server/tasks-import.js.map +0 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import * as Aglyn from "@aglyn/aglyn";
|
|
20
|
+
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { percent, Section } from "@aglyn/shared-ui-jsx/components/measured-figures.component";
|
|
22
|
+
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
23
|
+
import { ceilingedWindow } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
24
|
+
import { Alert, Stack, TableBody, TableCell, TableHead, TableRow, Typography } from "@mui/material";
|
|
25
|
+
import { getDocs, limit, orderBy, query } from "firebase/firestore";
|
|
26
|
+
import { useMemo } from "react";
|
|
27
|
+
import { useFirestore } from "@aglyn/tenant-feature-instance";
|
|
28
|
+
import { useCrmOrgMount } from "../../hooks/use-crm-org-mount.js";
|
|
29
|
+
import { useLeadSourcePicklist } from "../../hooks/use-lead-source-picklist.js";
|
|
30
|
+
import { LEAD_SOURCE_DIRECTION_LABELS, LEAD_SOURCE_UNSPECIFIED_LABEL } from "../../model/lead-source-direction.js";
|
|
31
|
+
import { leadsByLeadSource } from "../../model/crm-reports.js";
|
|
32
|
+
import { LEAD_CEILING, leadsBetween } from "./lead-funnel-card.js";
|
|
33
|
+
import { ReportExport } from "./report-export.js";
|
|
34
|
+
import { reportFilename } from "./report-format.js";
|
|
35
|
+
import { reportCacheKey } from "./report-scope.js";
|
|
36
|
+
import { ReportStatTile } from "./report-stat-tile.js";
|
|
37
|
+
import { useAggregateRead, useWindowRead } from "./use-aggregate-read.js";
|
|
38
|
+
/** The CSV's columns: one file for both tables. */ const COLUMNS = [
|
|
39
|
+
'Kind',
|
|
40
|
+
'Label',
|
|
41
|
+
'Direction',
|
|
42
|
+
'Leads',
|
|
43
|
+
'Qualified',
|
|
44
|
+
'Conversion'
|
|
45
|
+
];
|
|
46
|
+
const LEAD_SOURCE_HELP = Aglyn.pluginDocsHelp('crmReports', {
|
|
47
|
+
anchor: '#lead-sources',
|
|
48
|
+
excerpt: 'The leads first seen in the period by their lead source, and by the ' + 'direction each source is grouped under on the Fields page — Inbound, ' + 'Outbound or Unspecified — with how many of each were qualified.'
|
|
49
|
+
});
|
|
50
|
+
const directionLabel = (direction)=>direction ? LEAD_SOURCE_DIRECTION_LABELS[direction] : LEAD_SOURCE_UNSPECIFIED_LABEL;
|
|
51
|
+
/**
|
|
52
|
+
* Lead sources (AGL-3511): of the leads first seen in the period, how many
|
|
53
|
+
* came from each lead source and in each direction, and how many of each
|
|
54
|
+
* were qualified.
|
|
55
|
+
*
|
|
56
|
+
* The SAME window the Lead funnel places — the period's newest leads by
|
|
57
|
+
* `firstSeenAtMs`, under the same remembered key — so the two cards read
|
|
58
|
+
* the period once between them and can never disagree about which leads
|
|
59
|
+
* it holds. The lead source is grouped through the org's own list as it
|
|
60
|
+
* stands now: a value moved to Outbound on the Fields page reports as
|
|
61
|
+
* Outbound from that moment, for every lead that holds it.
|
|
62
|
+
*/ export function LeadSourceCard(props) {
|
|
63
|
+
const { report, hostId } = props;
|
|
64
|
+
const mount = useCrmOrgMount();
|
|
65
|
+
if (hostId) return /*#__PURE__*/ _jsx(SiteLeadSourceCard, {
|
|
66
|
+
report: report,
|
|
67
|
+
hostId: hostId
|
|
68
|
+
});
|
|
69
|
+
if (mount && (!mount.hostsReady || mount.hosts.length > 0)) {
|
|
70
|
+
return /*#__PURE__*/ _jsx(OrgLeadSourceCard, {
|
|
71
|
+
report: report
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
75
|
+
header: 'Lead sources',
|
|
76
|
+
help: LEAD_SOURCE_HELP,
|
|
77
|
+
contentGutterX: true,
|
|
78
|
+
contentGutterY: true,
|
|
79
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
80
|
+
variant: "body2",
|
|
81
|
+
color: "text.secondary",
|
|
82
|
+
children: mount ? 'Leads live under a site, and this organization has no sites yet.' : 'Leads live under a site, and this report is mounted under none.'
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
LeadSourceCard.displayName = 'LeadSourceCard';
|
|
87
|
+
/** One site's window, under the Lead funnel's own key. */ function SiteLeadSourceCard(props) {
|
|
88
|
+
const { report, hostId } = props;
|
|
89
|
+
const { range } = report;
|
|
90
|
+
const firestore = useFirestore();
|
|
91
|
+
const window = useWindowRead(()=>query(leadsBetween(firestore, report.scope[1], report.tokens, range.from, range.to), orderBy('firstSeenAtMs', 'desc'), limit(LEAD_CEILING + 1)), LEAD_CEILING, [
|
|
92
|
+
firestore,
|
|
93
|
+
hostId,
|
|
94
|
+
range
|
|
95
|
+
], {
|
|
96
|
+
cacheKey: reportCacheKey(report, `leads:window:${hostId}`)
|
|
97
|
+
});
|
|
98
|
+
return /*#__PURE__*/ _jsx(LeadSourceBody, {
|
|
99
|
+
report: report,
|
|
100
|
+
window: window
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
SiteLeadSourceCard.displayName = 'SiteLeadSourceCard';
|
|
104
|
+
/** The organization's one unscoped window, under the Lead funnel's own key. */ function OrgLeadSourceCard(props) {
|
|
105
|
+
const { report } = props;
|
|
106
|
+
const { range } = report;
|
|
107
|
+
const firestore = useFirestore();
|
|
108
|
+
const merged = useAggregateRead(()=>getDocs(query(leadsBetween(firestore, report.scope[1], null, range.from, range.to), orderBy('firstSeenAtMs', 'desc'), limit(LEAD_CEILING + 1))).then((snapshot)=>ceilingedWindow(snapshot.docs.map((document)=>_extends({}, document.data(), {
|
|
109
|
+
$id: document.id
|
|
110
|
+
})), LEAD_CEILING)), [
|
|
111
|
+
firestore,
|
|
112
|
+
report.scope,
|
|
113
|
+
range
|
|
114
|
+
], {
|
|
115
|
+
cacheKey: reportCacheKey(report, 'leads:window:org')
|
|
116
|
+
});
|
|
117
|
+
const window = useMemo(()=>{
|
|
118
|
+
var _ref, _ref1;
|
|
119
|
+
var _merged_value, _merged_value1;
|
|
120
|
+
return {
|
|
121
|
+
rows: (_ref = (_merged_value = merged.value) == null ? void 0 : _merged_value.rows) != null ? _ref : [],
|
|
122
|
+
truncated: (_ref1 = (_merged_value1 = merged.value) == null ? void 0 : _merged_value1.truncated) != null ? _ref1 : false,
|
|
123
|
+
status: merged.status
|
|
124
|
+
};
|
|
125
|
+
}, [
|
|
126
|
+
merged
|
|
127
|
+
]);
|
|
128
|
+
return /*#__PURE__*/ _jsx(LeadSourceBody, {
|
|
129
|
+
report: report,
|
|
130
|
+
window: window
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
OrgLeadSourceCard.displayName = 'OrgLeadSourceCard';
|
|
134
|
+
/** The card as drawn, whichever level fed it. Pure over the window and the org's list. */ function LeadSourceBody(props) {
|
|
135
|
+
var _ref, _ref1, _ref2;
|
|
136
|
+
var _direction, _direction1, _direction2, _direction3, _direction4;
|
|
137
|
+
const { report, window } = props;
|
|
138
|
+
const { period } = report;
|
|
139
|
+
const { picklist } = useLeadSourcePicklist(report.scope[1]);
|
|
140
|
+
const breakdown = useMemo(()=>leadsByLeadSource(window.rows, picklist), [
|
|
141
|
+
window,
|
|
142
|
+
picklist
|
|
143
|
+
]);
|
|
144
|
+
const read = window.status === 'success';
|
|
145
|
+
const direction = (id)=>breakdown.directions.find((row)=>row.direction === id);
|
|
146
|
+
const shareOf = (count)=>read && breakdown.total > 0 && count !== undefined ? `${percent(count / breakdown.total)} of the period's leads` : undefined;
|
|
147
|
+
const sourceLabel = (label, listed)=>label ? listed ? label : `${label} (not in the list)` : 'No lead source';
|
|
148
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
149
|
+
header: 'Lead sources',
|
|
150
|
+
help: LEAD_SOURCE_HELP,
|
|
151
|
+
contentGutterX: true,
|
|
152
|
+
contentGutterY: true,
|
|
153
|
+
HeaderProps: {
|
|
154
|
+
action: /*#__PURE__*/ _jsx(ReportExport, {
|
|
155
|
+
filename: reportFilename('lead-sources', period),
|
|
156
|
+
columns: COLUMNS,
|
|
157
|
+
rows: ()=>[
|
|
158
|
+
...breakdown.directions.map((row)=>[
|
|
159
|
+
'Direction',
|
|
160
|
+
directionLabel(row.direction),
|
|
161
|
+
'',
|
|
162
|
+
row.leads,
|
|
163
|
+
row.qualified,
|
|
164
|
+
row.leads ? percent(row.rate) : ''
|
|
165
|
+
]),
|
|
166
|
+
...breakdown.rows.map((row)=>[
|
|
167
|
+
'Lead source',
|
|
168
|
+
sourceLabel(row.label, row.listed),
|
|
169
|
+
directionLabel(row.direction),
|
|
170
|
+
row.leads,
|
|
171
|
+
row.qualified,
|
|
172
|
+
percent(row.rate)
|
|
173
|
+
])
|
|
174
|
+
],
|
|
175
|
+
disabled: !read || !breakdown.total
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
179
|
+
spacing: 2,
|
|
180
|
+
children: [
|
|
181
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
182
|
+
direction: "row",
|
|
183
|
+
spacing: 3,
|
|
184
|
+
sx: {
|
|
185
|
+
flexWrap: 'wrap'
|
|
186
|
+
},
|
|
187
|
+
children: [
|
|
188
|
+
/*#__PURE__*/ _jsx(ReportStatTile, {
|
|
189
|
+
label: LEAD_SOURCE_DIRECTION_LABELS.inbound,
|
|
190
|
+
value: read ? ((_ref = (_direction = direction('inbound')) == null ? void 0 : _direction.leads) != null ? _ref : 0).toLocaleString() : null,
|
|
191
|
+
note: shareOf((_direction1 = direction('inbound')) == null ? void 0 : _direction1.leads)
|
|
192
|
+
}),
|
|
193
|
+
/*#__PURE__*/ _jsx(ReportStatTile, {
|
|
194
|
+
label: LEAD_SOURCE_DIRECTION_LABELS.outbound,
|
|
195
|
+
value: read ? ((_ref1 = (_direction2 = direction('outbound')) == null ? void 0 : _direction2.leads) != null ? _ref1 : 0).toLocaleString() : null,
|
|
196
|
+
note: shareOf((_direction3 = direction('outbound')) == null ? void 0 : _direction3.leads)
|
|
197
|
+
}),
|
|
198
|
+
/*#__PURE__*/ _jsx(ReportStatTile, {
|
|
199
|
+
label: LEAD_SOURCE_UNSPECIFIED_LABEL,
|
|
200
|
+
value: read ? ((_ref2 = (_direction4 = direction(null)) == null ? void 0 : _direction4.leads) != null ? _ref2 : 0).toLocaleString() : null,
|
|
201
|
+
note: 'no lead source, or one in no group'
|
|
202
|
+
})
|
|
203
|
+
]
|
|
204
|
+
}),
|
|
205
|
+
window.status === 'error' ? /*#__PURE__*/ _jsx(Alert, {
|
|
206
|
+
severity: "warning",
|
|
207
|
+
children: 'The period’s leads could not be read.'
|
|
208
|
+
}) : null,
|
|
209
|
+
/*#__PURE__*/ _jsx(Section, {
|
|
210
|
+
title: 'By direction',
|
|
211
|
+
children: /*#__PURE__*/ _jsxs(ScrollTable, {
|
|
212
|
+
size: "small",
|
|
213
|
+
children: [
|
|
214
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
215
|
+
children: /*#__PURE__*/ _jsx(TableRow, {
|
|
216
|
+
children: [
|
|
217
|
+
'Direction',
|
|
218
|
+
'Leads',
|
|
219
|
+
'Qualified',
|
|
220
|
+
'Conversion'
|
|
221
|
+
].map((column, index)=>/*#__PURE__*/ _jsx(TableCell, {
|
|
222
|
+
align: index ? 'right' : 'left',
|
|
223
|
+
children: column
|
|
224
|
+
}, column))
|
|
225
|
+
})
|
|
226
|
+
}),
|
|
227
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
228
|
+
children: breakdown.directions.map((row)=>{
|
|
229
|
+
var _row_direction;
|
|
230
|
+
return /*#__PURE__*/ _jsxs(TableRow, {
|
|
231
|
+
children: [
|
|
232
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
233
|
+
children: directionLabel(row.direction)
|
|
234
|
+
}),
|
|
235
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
236
|
+
align: "right",
|
|
237
|
+
children: read ? row.leads.toLocaleString() : '—'
|
|
238
|
+
}),
|
|
239
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
240
|
+
align: "right",
|
|
241
|
+
children: read ? row.qualified.toLocaleString() : '—'
|
|
242
|
+
}),
|
|
243
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
244
|
+
align: "right",
|
|
245
|
+
children: read && row.leads ? percent(row.rate) : '—'
|
|
246
|
+
})
|
|
247
|
+
]
|
|
248
|
+
}, (_row_direction = row.direction) != null ? _row_direction : 'none');
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
})
|
|
253
|
+
}),
|
|
254
|
+
/*#__PURE__*/ _jsxs(Section, {
|
|
255
|
+
title: 'By lead source',
|
|
256
|
+
children: [
|
|
257
|
+
breakdown.rows.length ? /*#__PURE__*/ _jsxs(ScrollTable, {
|
|
258
|
+
size: "small",
|
|
259
|
+
children: [
|
|
260
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
261
|
+
children: /*#__PURE__*/ _jsx(TableRow, {
|
|
262
|
+
children: [
|
|
263
|
+
'Lead source',
|
|
264
|
+
'Direction',
|
|
265
|
+
'Leads',
|
|
266
|
+
'Qualified',
|
|
267
|
+
'Conversion'
|
|
268
|
+
].map((column, index)=>/*#__PURE__*/ _jsx(TableCell, {
|
|
269
|
+
align: index > 1 ? 'right' : 'left',
|
|
270
|
+
children: column
|
|
271
|
+
}, column))
|
|
272
|
+
})
|
|
273
|
+
}),
|
|
274
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
275
|
+
children: breakdown.rows.map((row)=>/*#__PURE__*/ _jsxs(TableRow, {
|
|
276
|
+
children: [
|
|
277
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
278
|
+
children: sourceLabel(row.label, row.listed)
|
|
279
|
+
}),
|
|
280
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
281
|
+
children: directionLabel(row.direction)
|
|
282
|
+
}),
|
|
283
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
284
|
+
align: "right",
|
|
285
|
+
children: row.leads.toLocaleString()
|
|
286
|
+
}),
|
|
287
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
288
|
+
align: "right",
|
|
289
|
+
children: row.qualified.toLocaleString()
|
|
290
|
+
}),
|
|
291
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
292
|
+
align: "right",
|
|
293
|
+
children: percent(row.rate)
|
|
294
|
+
})
|
|
295
|
+
]
|
|
296
|
+
}, row.label || 'none'))
|
|
297
|
+
})
|
|
298
|
+
]
|
|
299
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
300
|
+
variant: "body2",
|
|
301
|
+
color: "text.secondary",
|
|
302
|
+
children: window.status === 'loading' ? 'Reading…' : 'No leads first seen in this period.'
|
|
303
|
+
}),
|
|
304
|
+
read && window.truncated ? /*#__PURE__*/ _jsx(Typography, {
|
|
305
|
+
variant: "caption",
|
|
306
|
+
color: "text.secondary",
|
|
307
|
+
children: `Read from the ${window.rows.length.toLocaleString()} most recently captured leads in the period.`
|
|
308
|
+
}) : null
|
|
309
|
+
]
|
|
310
|
+
})
|
|
311
|
+
]
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
LeadSourceBody.displayName = 'LeadSourceBody';
|
|
316
|
+
export default LeadSourceCard;
|
|
317
|
+
|
|
318
|
+
//# sourceMappingURL=lead-source-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/components/reports/lead-source-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n percent,\n Section,\n} from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { ceilingedWindow } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport {\n Alert,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport { getDocs, limit, orderBy, query } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useCrmOrgMount } from '../../hooks/use-crm-org-mount'\nimport { useLeadSourcePicklist } from '../../hooks/use-lead-source-picklist'\nimport {\n LEAD_SOURCE_DIRECTION_LABELS,\n LEAD_SOURCE_UNSPECIFIED_LABEL,\n type LeadSourceDirection,\n} from '../../model/lead-source-direction'\nimport { leadsByLeadSource } from '../../model/crm-reports'\nimport { LEAD_CEILING, leadsBetween } from './lead-funnel-card'\nimport { ReportExport } from './report-export'\nimport { reportFilename } from './report-format'\nimport { type CrmReportScope, reportCacheKey } from './report-scope'\nimport { ReportStatTile } from './report-stat-tile'\nimport { useAggregateRead, useWindowRead, type WindowRead } from './use-aggregate-read'\n\ntype LeadRow = Record<string, unknown> & Aglyn.CrmLeadFields & { $id: string }\n\n/** The CSV's columns: one file for both tables. */\nconst COLUMNS = ['Kind', 'Label', 'Direction', 'Leads', 'Qualified', 'Conversion'] as const\n\nconst LEAD_SOURCE_HELP = Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#lead-sources',\n excerpt:\n 'The leads first seen in the period by their lead source, and by the ' +\n 'direction each source is grouped under on the Fields page — Inbound, ' +\n 'Outbound or Unspecified — with how many of each were qualified.',\n})\n\nconst directionLabel = (direction: LeadSourceDirection | null): string =>\n direction ? LEAD_SOURCE_DIRECTION_LABELS[direction] : LEAD_SOURCE_UNSPECIFIED_LABEL\n\nexport interface LeadSourceCardProps {\n report: CrmReportScope\n /** The site whose leads are read, or `null` at the organization level — as the Lead funnel takes it. */\n hostId: string | null\n}\n\n/**\n * Lead sources (AGL-3511): of the leads first seen in the period, how many\n * came from each lead source and in each direction, and how many of each\n * were qualified.\n *\n * The SAME window the Lead funnel places — the period's newest leads by\n * `firstSeenAtMs`, under the same remembered key — so the two cards read\n * the period once between them and can never disagree about which leads\n * it holds. The lead source is grouped through the org's own list as it\n * stands now: a value moved to Outbound on the Fields page reports as\n * Outbound from that moment, for every lead that holds it.\n */\nexport function LeadSourceCard(props: LeadSourceCardProps) {\n const { report, hostId } = props\n const mount = useCrmOrgMount()\n if (hostId) return <SiteLeadSourceCard report={report} hostId={hostId} />\n if (mount && (!mount.hostsReady || mount.hosts.length > 0)) {\n return <OrgLeadSourceCard report={report} />\n }\n return (\n <CardDisplay header={'Lead sources'} help={LEAD_SOURCE_HELP} contentGutterX contentGutterY>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mount\n ? 'Leads live under a site, and this organization has no sites yet.'\n : 'Leads live under a site, and this report is mounted under none.'}\n </Typography>\n </CardDisplay>\n )\n}\nLeadSourceCard.displayName = 'LeadSourceCard'\n\n/** One site's window, under the Lead funnel's own key. */\nfunction SiteLeadSourceCard(props: { report: CrmReportScope; hostId: string }) {\n const { report, hostId } = props\n const { range } = report\n const firestore = useFirestore()\n const window = useWindowRead<LeadRow>(\n () =>\n query(\n leadsBetween(firestore, report.scope[1], report.tokens, range.from, range.to),\n orderBy('firstSeenAtMs', 'desc'),\n limit(LEAD_CEILING + 1),\n ),\n LEAD_CEILING,\n [firestore, hostId, range],\n { cacheKey: reportCacheKey(report, `leads:window:${hostId}`) },\n )\n return <LeadSourceBody report={report} window={window} />\n}\nSiteLeadSourceCard.displayName = 'SiteLeadSourceCard'\n\n/** The organization's one unscoped window, under the Lead funnel's own key. */\nfunction OrgLeadSourceCard(props: { report: CrmReportScope }) {\n const { report } = props\n const { range } = report\n const firestore = useFirestore()\n const merged = useAggregateRead<{ rows: LeadRow[]; truncated: boolean }>(\n () =>\n getDocs(\n query(\n leadsBetween(firestore, report.scope[1], null, range.from, range.to),\n orderBy('firstSeenAtMs', 'desc'),\n limit(LEAD_CEILING + 1),\n ),\n ).then((snapshot) =>\n ceilingedWindow(\n snapshot.docs.map(\n (document) => ({ ...document.data(), $id: document.id }) as unknown as LeadRow,\n ),\n LEAD_CEILING,\n ),\n ),\n [firestore, report.scope, range],\n { cacheKey: reportCacheKey(report, 'leads:window:org') },\n )\n const window = useMemo<WindowRead<LeadRow>>(\n () => ({\n rows: merged.value?.rows ?? [],\n truncated: merged.value?.truncated ?? false,\n status: merged.status,\n }),\n [merged],\n )\n return <LeadSourceBody report={report} window={window} />\n}\nOrgLeadSourceCard.displayName = 'OrgLeadSourceCard'\n\n/** The card as drawn, whichever level fed it. Pure over the window and the org's list. */\nfunction LeadSourceBody(props: { report: CrmReportScope; window: WindowRead<LeadRow> }) {\n const { report, window } = props\n const { period } = report\n const { picklist } = useLeadSourcePicklist(report.scope[1])\n const breakdown = useMemo(() => leadsByLeadSource(window.rows, picklist), [window, picklist])\n const read = window.status === 'success'\n const direction = (id: LeadSourceDirection | null) =>\n breakdown.directions.find((row) => row.direction === id)\n const shareOf = (count: number | undefined): string | undefined =>\n read && breakdown.total > 0 && count !== undefined\n ? `${percent(count / breakdown.total)} of the period's leads`\n : undefined\n const sourceLabel = (label: string, listed: boolean) =>\n label ? (listed ? label : `${label} (not in the list)`) : 'No lead source'\n\n return (\n <CardDisplay\n header={'Lead sources'}\n help={LEAD_SOURCE_HELP}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <ReportExport\n filename={reportFilename('lead-sources', period)}\n columns={COLUMNS}\n rows={() => [\n ...breakdown.directions.map((row) => [\n 'Direction',\n directionLabel(row.direction),\n '',\n row.leads,\n row.qualified,\n row.leads ? percent(row.rate) : '',\n ]),\n ...breakdown.rows.map((row) => [\n 'Lead source',\n sourceLabel(row.label, row.listed),\n directionLabel(row.direction),\n row.leads,\n row.qualified,\n percent(row.rate),\n ]),\n ]}\n disabled={!read || !breakdown.total}\n />\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={LEAD_SOURCE_DIRECTION_LABELS.inbound}\n value={read ? (direction('inbound')?.leads ?? 0).toLocaleString() : null}\n note={shareOf(direction('inbound')?.leads)}\n />\n <ReportStatTile\n label={LEAD_SOURCE_DIRECTION_LABELS.outbound}\n value={read ? (direction('outbound')?.leads ?? 0).toLocaleString() : null}\n note={shareOf(direction('outbound')?.leads)}\n />\n <ReportStatTile\n label={LEAD_SOURCE_UNSPECIFIED_LABEL}\n value={read ? (direction(null)?.leads ?? 0).toLocaleString() : null}\n note={'no lead source, or one in no group'}\n />\n </Stack>\n {window.status === 'error' ? (\n <Alert severity=\"warning\">{'The period’s leads could not be read.'}</Alert>\n ) : null}\n <Section title={'By direction'}>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n {['Direction', 'Leads', 'Qualified', 'Conversion'].map((column, index) => (\n <TableCell key={column} align={index ? 'right' : 'left'}>\n {column}\n </TableCell>\n ))}\n </TableRow>\n </TableHead>\n <TableBody>\n {breakdown.directions.map((row) => (\n <TableRow key={row.direction ?? 'none'}>\n <TableCell>{directionLabel(row.direction)}</TableCell>\n <TableCell align=\"right\">{read ? row.leads.toLocaleString() : '—'}</TableCell>\n <TableCell align=\"right\">{read ? row.qualified.toLocaleString() : '—'}</TableCell>\n <TableCell align=\"right\">{read && row.leads ? percent(row.rate) : '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Section>\n <Section title={'By lead source'}>\n {breakdown.rows.length ? (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n {['Lead source', 'Direction', 'Leads', 'Qualified', 'Conversion'].map(\n (column, index) => (\n <TableCell key={column} align={index > 1 ? 'right' : 'left'}>\n {column}\n </TableCell>\n ),\n )}\n </TableRow>\n </TableHead>\n <TableBody>\n {breakdown.rows.map((row) => (\n <TableRow key={row.label || 'none'}>\n <TableCell>{sourceLabel(row.label, row.listed)}</TableCell>\n <TableCell>{directionLabel(row.direction)}</TableCell>\n <TableCell align=\"right\">{row.leads.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.qualified.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{percent(row.rate)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {window.status === 'loading' ? 'Reading…' : 'No leads first seen in this period.'}\n </Typography>\n )}\n {read && window.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Read from the ${window.rows.length.toLocaleString()} most recently captured leads in the period.`}\n </Typography>\n ) : null}\n </Section>\n </Stack>\n </CardDisplay>\n )\n}\nLeadSourceBody.displayName = 'LeadSourceBody'\n\nexport default LeadSourceCard\n"],"names":["Aglyn","CardDisplay","percent","Section","ScrollTable","ceilingedWindow","Alert","Stack","TableBody","TableCell","TableHead","TableRow","Typography","getDocs","limit","orderBy","query","useMemo","useFirestore","useCrmOrgMount","useLeadSourcePicklist","LEAD_SOURCE_DIRECTION_LABELS","LEAD_SOURCE_UNSPECIFIED_LABEL","leadsByLeadSource","LEAD_CEILING","leadsBetween","ReportExport","reportFilename","reportCacheKey","ReportStatTile","useAggregateRead","useWindowRead","COLUMNS","LEAD_SOURCE_HELP","pluginDocsHelp","anchor","excerpt","directionLabel","direction","LeadSourceCard","props","report","hostId","mount","SiteLeadSourceCard","hostsReady","hosts","length","OrgLeadSourceCard","header","help","contentGutterX","contentGutterY","variant","color","displayName","range","firestore","window","scope","tokens","from","to","cacheKey","LeadSourceBody","merged","then","snapshot","docs","map","document","data","$id","id","rows","value","truncated","status","period","picklist","breakdown","read","directions","find","row","shareOf","count","total","undefined","sourceLabel","label","listed","HeaderProps","action","filename","columns","leads","qualified","rate","disabled","spacing","sx","flexWrap","inbound","toLocaleString","note","outbound","severity","title","size","column","index","align"],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
18
|
import * as Aglyn from "@aglyn/aglyn";
|
|
19
|
+
import { stuckDeals } from "../../model/crm-next-activity.js";
|
|
19
20
|
import { money } from "@aglyn/shared-ui-jsx/components/measured-figures.component";
|
|
20
21
|
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
22
|
import { Section } from "@aglyn/shared-ui-jsx/components/measured-figures.component";
|
|
@@ -149,7 +150,7 @@ const TOP_DEALS = 10;
|
|
|
149
150
|
pipelineWindow
|
|
150
151
|
]);
|
|
151
152
|
// Open deals with nothing scheduled (AGL-2661), off the same window.
|
|
152
|
-
const stuck = useMemo(()=>
|
|
153
|
+
const stuck = useMemo(()=>stuckDeals(dealWindow.rows), [
|
|
153
154
|
dealWindow.rows
|
|
154
155
|
]);
|
|
155
156
|
const currency = summary.currency.currency;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/components/reports/pipeline-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { money } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Section } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Alert,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n count,\n getAggregateFromServer,\n limit,\n orderBy,\n query,\n sum,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { collectionCeiling } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { ReportBreakdown } from './report-breakdown'\nimport { ReportExport } from './report-export'\nimport { plural, reportFilename, shortDate } from './report-format'\nimport {\n type CrmReportScope,\n reportCacheKey,\n scopedCollection,\n visibleToClause,\n} from './report-scope'\nimport { ReportStatTile } from './report-stat-tile'\nimport { useAggregateRead, useWindowRead } from './use-aggregate-read'\nimport { currencyOfDeals, pipelineTotals } from '../../model/crm-reports'\n\n/**\n * How many open deals the by-stage chart, the forecast and the top-deals\n * table are read from.\n *\n * The count and the face value of the pipeline are server aggregates and\n * never meet this bound. The forecast cannot be: it weights each deal by\n * ITS stage's odds, which is a join the server will not do, so it reads the\n * deals and says so when there were more than this.\n */\nconst OPEN_DEAL_CEILING = 1000\n/** More pipelines than this is a shape nobody has asked for; the probe says if it happens. */\nconst PIPELINE_CEILING = 20\nconst TOP_DEALS = 10\n\n/** The top-deals table's columns, which are also the CSV's — the amount split from its currency there. */\nconst TOP_DEAL_COLUMNS = ['Deal', 'Stage', 'Expected close', 'Amount', 'Currency'] as const\n\ntype DealRow = Aglyn.CrmDeal & { $id: string }\ntype PipelineRow = Aglyn.CrmPipeline & { $id: string }\n\nexport interface PipelineCardProps {\n report: CrmReportScope\n}\n\n/**\n * The open pipeline: how many deals, what they add up to, what they are\n * worth at their stage odds, where they sit, and the largest of them\n * (AGL-2604).\n *\n * Not period-scoped, deliberately. A pipeline is a stock, not a flow — what\n * is open NOW — and a \"pipeline over the last 30 days\" would be a report on\n * deals created in a window, which is a different question with a different\n * answer. The period picker governs the cards that measure flows.\n */\nexport function PipelineCard(props: PipelineCardProps) {\n const { report } = props\n const { scope, tokens, routes, nowMs } = report\n const firestore = useFirestore()\n\n const open = useAggregateRead(\n () =>\n getAggregateFromServer(\n query(\n scopedCollection(firestore, scope, 'deals'),\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n ),\n { count: count(), amountCents: sum('amountCents') },\n ).then((snapshot) => ({\n count: Number(snapshot.data().count ?? 0),\n amountCents: Number(snapshot.data().amountCents ?? 0),\n })),\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'pipeline:open') },\n )\n\n /**\n * The open deals, most recently updated first — the order the\n * `(visibleTo, status, updatedAt)` index the deals list already uses can\n * serve, so the report adds no index for this read.\n */\n const dealWindow = useWindowRead<DealRow>(\n () =>\n query(\n scopedCollection(firestore, scope, 'deals'),\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n orderBy('updatedAt', 'desc'),\n limit(OPEN_DEAL_CEILING + 1),\n ),\n OPEN_DEAL_CEILING,\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'pipeline:deals') },\n )\n const pipelineWindow = useWindowRead<PipelineRow>(\n () =>\n collectionCeiling(\n query(\n scopedCollection(firestore, scope, 'pipelines'),\n ...visibleToClause(tokens),\n ),\n PIPELINE_CEILING,\n ),\n PIPELINE_CEILING,\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'pipeline:pipelines') },\n )\n const dealsStatus = dealWindow.status\n\n const summary = useMemo(() => {\n const byPipeline = new Map<string, DealRow[]>()\n for (const deal of dealWindow.rows) {\n const key = deal.pipelineId ?? ''\n const list = byPipeline.get(key) ?? []\n list.push(deal)\n byPipeline.set(key, list)\n }\n // Every active pipeline is charted even with nothing open; an archived\n // one only while a deal still sits open in it — the forecast card's\n // rule, so the two cards name the same pipelines.\n const pipelines = pipelineWindow.rows\n .filter((pipeline) => !Aglyn.isPipelineArchived(pipeline) || byPipeline.has(pipeline.$id))\n .map((pipeline) => ({\n pipeline,\n totals: pipelineTotals(byPipeline.get(pipeline.$id) ?? [], pipeline),\n }))\n const known = new Set(pipelineWindow.rows.map((pipeline) => pipeline.$id))\n const orphans = dealWindow.rows.filter(\n (deal) => !known.has(deal.pipelineId ?? ''),\n )\n const stageName = (deal: DealRow): string =>\n Aglyn.dealStageById(\n pipelineWindow.rows.find((pipeline) => pipeline.$id === deal.pipelineId),\n deal.stageId,\n )?.name ?? deal.stageId\n const top = [...dealWindow.rows]\n .sort(\n (a, b) => Number(b.amountCents ?? 0) - Number(a.amountCents ?? 0),\n )\n .slice(0, TOP_DEALS)\n return {\n pipelines,\n orphanTotals: pipelineTotals(orphans, null),\n weightedCents: pipelines.reduce(\n (total, entry) => total + entry.totals.weightedCents,\n 0,\n ),\n top,\n stageName,\n currency: currencyOfDeals(dealWindow.rows),\n }\n }, [dealWindow, pipelineWindow])\n // Open deals with nothing scheduled (AGL-2661), off the same window.\n const stuck = useMemo(() => Aglyn.stuckDeals(dealWindow.rows), [dealWindow.rows])\n\n const currency = summary.currency.currency\n const dealsRead = dealsStatus === 'success'\n\n return (\n <CardDisplay\n header={'Pipeline'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#pipeline',\n excerpt:\n 'Every open deal: how many, what they add up to, and what they ' +\n 'are worth once each is weighted by the odds of its stage. Not ' +\n 'tied to the period — a pipeline is what is open now.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <ReportExport\n filename={reportFilename('top-open-deals')}\n columns={TOP_DEAL_COLUMNS}\n rows={() =>\n summary.top.map((deal) => [\n deal.title || deal.$id,\n summary.stageName(deal),\n typeof deal.expectedCloseAtMs === 'number' && deal.expectedCloseAtMs > 0\n ? new Date(deal.expectedCloseAtMs).toISOString().slice(0, 10)\n : '',\n (Number(deal.amountCents ?? 0) / 100).toFixed(2),\n String(deal.currency || currency).toUpperCase(),\n ])\n }\n disabled={!dealsRead || !summary.top.length}\n />\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Open deals'}\n value={open.value ? open.value.count.toLocaleString() : null}\n note={'counted on the server'}\n href={routes.section('deals')}\n />\n <ReportStatTile\n label={'Pipeline value'}\n value={open.value ? money(open.value.amountCents, currency) : null}\n note={'face value of every open deal'}\n href={routes.section('deals')}\n />\n <ReportStatTile\n label={'Weighted forecast'}\n value={dealsRead ? money(summary.weightedCents, currency) : null}\n note={\n dealWindow.truncated\n ? `from the ${OPEN_DEAL_CEILING.toLocaleString()} most recently updated`\n : 'each deal at its stage odds'\n }\n />\n {/*\n Open deals with no open task against them (AGL-2661): the ones\n nobody has a next step for. Painted as a warning above zero,\n because a deal with nothing planned is a deal going nowhere; the\n tile opens the deals list, whose \"No next activity\" chip lists\n them.\n */}\n <ReportStatTile\n label={'Stuck deals'}\n value={dealsRead ? stuck.count.toLocaleString() : null}\n note={'open, with no next activity'}\n riseIsGood={false}\n color={stuck.count > 0 ? 'warning.main' : undefined}\n href={routes.section('deals')}\n />\n </Stack>\n {open.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The deal totals could not be read.'}\n </Alert>\n ) : null}\n {summary.currency.mixed ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Deals are in more than one currency; the totals add them as numbers and show ${currency.toUpperCase()}, the most common.`}\n </Typography>\n ) : null}\n {dealsRead && !summary.pipelines.length && !dealWindow.rows.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No open deals yet.'}\n </Typography>\n ) : null}\n {summary.pipelines.map(({ pipeline, totals }) => (\n <Section key={pipeline.$id} title={pipeline.name || 'Pipeline'}>\n <ReportBreakdown\n rows={[\n ...totals.stages.map((row) => ({\n key: row.stage.id,\n label: row.stage.name,\n value: row.amountCents,\n display: money(row.amountCents, currency),\n note: `${plural(row.count, 'deal')} · ${money(row.weightedCents, currency)} weighted`,\n })),\n ...(totals.unplaced.count\n ? [\n {\n key: '$unplaced',\n label: 'Stage no longer in this pipeline',\n value: totals.unplaced.amountCents,\n display: money(totals.unplaced.amountCents, currency),\n note: `${plural(totals.unplaced.count, 'deal')} · not forecast`,\n color: 'warning.main',\n },\n ]\n : []),\n ]}\n emptyText={'This pipeline has no open stages.'}\n />\n </Section>\n ))}\n {summary.orphanTotals.count ? (\n <Section title={'Deals in no pipeline'}>\n <ReportBreakdown\n rows={[\n {\n key: '$orphans',\n label: 'No pipeline',\n value: summary.orphanTotals.amountCents,\n display: money(summary.orphanTotals.amountCents, currency),\n note: `${plural(summary.orphanTotals.count, 'deal')} · not forecast`,\n color: 'warning.main',\n },\n ]}\n emptyText={''}\n />\n </Section>\n ) : null}\n {pipelineWindow.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Only the first ${PIPELINE_CEILING} pipelines are charted.`}\n </Typography>\n ) : null}\n {summary.top.length ? (\n <Section title={'Top open deals'}>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Deal'}</TableCell>\n <TableCell>{'Stage'}</TableCell>\n <TableCell>{'Expected close'}</TableCell>\n <TableCell align=\"right\">{'Amount'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {summary.top.map((deal) => (\n <TableRow key={deal.$id}>\n <TableCell>\n <AppLink href={routes.deal(deal.$id)}>\n {deal.title || deal.$id}\n </AppLink>\n </TableCell>\n <TableCell>{summary.stageName(deal)}</TableCell>\n <TableCell>{shortDate(deal.expectedCloseAtMs)}</TableCell>\n <TableCell align=\"right\">\n {money(\n Number(deal.amountCents ?? 0),\n String(deal.currency || currency),\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {/* What the ranking, and so the export, is drawn from when that is not everything. */}\n {dealWindow.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Ranked within the ${OPEN_DEAL_CEILING.toLocaleString()} most recently updated open deals.`}\n </Typography>\n ) : null}\n </Section>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nPipelineCard.displayName = 'PipelineCard'\n\nexport default PipelineCard\n"],"names":["Aglyn","money","AppLink","CardDisplay","Section","ScrollTable","Alert","Stack","TableBody","TableCell","TableHead","TableRow","Typography","count","getAggregateFromServer","limit","orderBy","query","sum","where","useMemo","useFirestore","collectionCeiling","ReportBreakdown","ReportExport","plural","reportFilename","shortDate","reportCacheKey","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","useWindowRead","currencyOfDeals","pipelineTotals","OPEN_DEAL_CEILING","PIPELINE_CEILING","TOP_DEALS","TOP_DEAL_COLUMNS","PipelineCard","props","report","scope","tokens","routes","nowMs","firestore","open","amountCents","then","snapshot","Number","data","cacheKey","dealWindow","pipelineWindow","dealsStatus","status","summary","byPipeline","Map","deal","rows","key","pipelineId","list","get","push","set","pipelines","filter","pipeline","isPipelineArchived","has","$id","map","totals","known","Set","orphans","stageName","dealStageById","find","stageId","name","top","sort","a","b","slice","orphanTotals","weightedCents","reduce","total","entry","currency","stuck","stuckDeals","dealsRead","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","filename","columns","title","expectedCloseAtMs","Date","toISOString","toFixed","String","toUpperCase","disabled","length","spacing","direction","sx","flexWrap","label","value","toLocaleString","note","href","section","truncated","riseIsGood","color","undefined","severity","mixed","variant","stages","row","stage","id","display","unplaced","emptyText","size","align","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/components/reports/pipeline-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { stuckDeals } from '../../model/crm-next-activity'\nimport { money } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Section } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Alert,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n count,\n getAggregateFromServer,\n limit,\n orderBy,\n query,\n sum,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { collectionCeiling } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { ReportBreakdown } from './report-breakdown'\nimport { ReportExport } from './report-export'\nimport { plural, reportFilename, shortDate } from './report-format'\nimport {\n type CrmReportScope,\n reportCacheKey,\n scopedCollection,\n visibleToClause,\n} from './report-scope'\nimport { ReportStatTile } from './report-stat-tile'\nimport { useAggregateRead, useWindowRead } from './use-aggregate-read'\nimport { currencyOfDeals, pipelineTotals } from '../../model/crm-reports'\n\n/**\n * How many open deals the by-stage chart, the forecast and the top-deals\n * table are read from.\n *\n * The count and the face value of the pipeline are server aggregates and\n * never meet this bound. The forecast cannot be: it weights each deal by\n * ITS stage's odds, which is a join the server will not do, so it reads the\n * deals and says so when there were more than this.\n */\nconst OPEN_DEAL_CEILING = 1000\n/** More pipelines than this is a shape nobody has asked for; the probe says if it happens. */\nconst PIPELINE_CEILING = 20\nconst TOP_DEALS = 10\n\n/** The top-deals table's columns, which are also the CSV's — the amount split from its currency there. */\nconst TOP_DEAL_COLUMNS = ['Deal', 'Stage', 'Expected close', 'Amount', 'Currency'] as const\n\ntype DealRow = Aglyn.CrmDeal & { $id: string }\ntype PipelineRow = Aglyn.CrmPipeline & { $id: string }\n\nexport interface PipelineCardProps {\n report: CrmReportScope\n}\n\n/**\n * The open pipeline: how many deals, what they add up to, what they are\n * worth at their stage odds, where they sit, and the largest of them\n * (AGL-2604).\n *\n * Not period-scoped, deliberately. A pipeline is a stock, not a flow — what\n * is open NOW — and a \"pipeline over the last 30 days\" would be a report on\n * deals created in a window, which is a different question with a different\n * answer. The period picker governs the cards that measure flows.\n */\nexport function PipelineCard(props: PipelineCardProps) {\n const { report } = props\n const { scope, tokens, routes, nowMs } = report\n const firestore = useFirestore()\n\n const open = useAggregateRead(\n () =>\n getAggregateFromServer(\n query(\n scopedCollection(firestore, scope, 'deals'),\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n ),\n { count: count(), amountCents: sum('amountCents') },\n ).then((snapshot) => ({\n count: Number(snapshot.data().count ?? 0),\n amountCents: Number(snapshot.data().amountCents ?? 0),\n })),\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'pipeline:open') },\n )\n\n /**\n * The open deals, most recently updated first — the order the\n * `(visibleTo, status, updatedAt)` index the deals list already uses can\n * serve, so the report adds no index for this read.\n */\n const dealWindow = useWindowRead<DealRow>(\n () =>\n query(\n scopedCollection(firestore, scope, 'deals'),\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n orderBy('updatedAt', 'desc'),\n limit(OPEN_DEAL_CEILING + 1),\n ),\n OPEN_DEAL_CEILING,\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'pipeline:deals') },\n )\n const pipelineWindow = useWindowRead<PipelineRow>(\n () =>\n collectionCeiling(\n query(\n scopedCollection(firestore, scope, 'pipelines'),\n ...visibleToClause(tokens),\n ),\n PIPELINE_CEILING,\n ),\n PIPELINE_CEILING,\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'pipeline:pipelines') },\n )\n const dealsStatus = dealWindow.status\n\n const summary = useMemo(() => {\n const byPipeline = new Map<string, DealRow[]>()\n for (const deal of dealWindow.rows) {\n const key = deal.pipelineId ?? ''\n const list = byPipeline.get(key) ?? []\n list.push(deal)\n byPipeline.set(key, list)\n }\n // Every active pipeline is charted even with nothing open; an archived\n // one only while a deal still sits open in it — the forecast card's\n // rule, so the two cards name the same pipelines.\n const pipelines = pipelineWindow.rows\n .filter((pipeline) => !Aglyn.isPipelineArchived(pipeline) || byPipeline.has(pipeline.$id))\n .map((pipeline) => ({\n pipeline,\n totals: pipelineTotals(byPipeline.get(pipeline.$id) ?? [], pipeline),\n }))\n const known = new Set(pipelineWindow.rows.map((pipeline) => pipeline.$id))\n const orphans = dealWindow.rows.filter(\n (deal) => !known.has(deal.pipelineId ?? ''),\n )\n const stageName = (deal: DealRow): string =>\n Aglyn.dealStageById(\n pipelineWindow.rows.find((pipeline) => pipeline.$id === deal.pipelineId),\n deal.stageId,\n )?.name ?? deal.stageId\n const top = [...dealWindow.rows]\n .sort(\n (a, b) => Number(b.amountCents ?? 0) - Number(a.amountCents ?? 0),\n )\n .slice(0, TOP_DEALS)\n return {\n pipelines,\n orphanTotals: pipelineTotals(orphans, null),\n weightedCents: pipelines.reduce(\n (total, entry) => total + entry.totals.weightedCents,\n 0,\n ),\n top,\n stageName,\n currency: currencyOfDeals(dealWindow.rows),\n }\n }, [dealWindow, pipelineWindow])\n // Open deals with nothing scheduled (AGL-2661), off the same window.\n const stuck = useMemo(() => stuckDeals(dealWindow.rows), [dealWindow.rows])\n\n const currency = summary.currency.currency\n const dealsRead = dealsStatus === 'success'\n\n return (\n <CardDisplay\n header={'Pipeline'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#pipeline',\n excerpt:\n 'Every open deal: how many, what they add up to, and what they ' +\n 'are worth once each is weighted by the odds of its stage. Not ' +\n 'tied to the period — a pipeline is what is open now.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <ReportExport\n filename={reportFilename('top-open-deals')}\n columns={TOP_DEAL_COLUMNS}\n rows={() =>\n summary.top.map((deal) => [\n deal.title || deal.$id,\n summary.stageName(deal),\n typeof deal.expectedCloseAtMs === 'number' && deal.expectedCloseAtMs > 0\n ? new Date(deal.expectedCloseAtMs).toISOString().slice(0, 10)\n : '',\n (Number(deal.amountCents ?? 0) / 100).toFixed(2),\n String(deal.currency || currency).toUpperCase(),\n ])\n }\n disabled={!dealsRead || !summary.top.length}\n />\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Open deals'}\n value={open.value ? open.value.count.toLocaleString() : null}\n note={'counted on the server'}\n href={routes.section('deals')}\n />\n <ReportStatTile\n label={'Pipeline value'}\n value={open.value ? money(open.value.amountCents, currency) : null}\n note={'face value of every open deal'}\n href={routes.section('deals')}\n />\n <ReportStatTile\n label={'Weighted forecast'}\n value={dealsRead ? money(summary.weightedCents, currency) : null}\n note={\n dealWindow.truncated\n ? `from the ${OPEN_DEAL_CEILING.toLocaleString()} most recently updated`\n : 'each deal at its stage odds'\n }\n />\n {/*\n Open deals with no open task against them (AGL-2661): the ones\n nobody has a next step for. Painted as a warning above zero,\n because a deal with nothing planned is a deal going nowhere; the\n tile opens the deals list, whose \"No next activity\" chip lists\n them.\n */}\n <ReportStatTile\n label={'Stuck deals'}\n value={dealsRead ? stuck.count.toLocaleString() : null}\n note={'open, with no next activity'}\n riseIsGood={false}\n color={stuck.count > 0 ? 'warning.main' : undefined}\n href={routes.section('deals')}\n />\n </Stack>\n {open.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The deal totals could not be read.'}\n </Alert>\n ) : null}\n {summary.currency.mixed ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Deals are in more than one currency; the totals add them as numbers and show ${currency.toUpperCase()}, the most common.`}\n </Typography>\n ) : null}\n {dealsRead && !summary.pipelines.length && !dealWindow.rows.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No open deals yet.'}\n </Typography>\n ) : null}\n {summary.pipelines.map(({ pipeline, totals }) => (\n <Section key={pipeline.$id} title={pipeline.name || 'Pipeline'}>\n <ReportBreakdown\n rows={[\n ...totals.stages.map((row) => ({\n key: row.stage.id,\n label: row.stage.name,\n value: row.amountCents,\n display: money(row.amountCents, currency),\n note: `${plural(row.count, 'deal')} · ${money(row.weightedCents, currency)} weighted`,\n })),\n ...(totals.unplaced.count\n ? [\n {\n key: '$unplaced',\n label: 'Stage no longer in this pipeline',\n value: totals.unplaced.amountCents,\n display: money(totals.unplaced.amountCents, currency),\n note: `${plural(totals.unplaced.count, 'deal')} · not forecast`,\n color: 'warning.main',\n },\n ]\n : []),\n ]}\n emptyText={'This pipeline has no open stages.'}\n />\n </Section>\n ))}\n {summary.orphanTotals.count ? (\n <Section title={'Deals in no pipeline'}>\n <ReportBreakdown\n rows={[\n {\n key: '$orphans',\n label: 'No pipeline',\n value: summary.orphanTotals.amountCents,\n display: money(summary.orphanTotals.amountCents, currency),\n note: `${plural(summary.orphanTotals.count, 'deal')} · not forecast`,\n color: 'warning.main',\n },\n ]}\n emptyText={''}\n />\n </Section>\n ) : null}\n {pipelineWindow.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Only the first ${PIPELINE_CEILING} pipelines are charted.`}\n </Typography>\n ) : null}\n {summary.top.length ? (\n <Section title={'Top open deals'}>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Deal'}</TableCell>\n <TableCell>{'Stage'}</TableCell>\n <TableCell>{'Expected close'}</TableCell>\n <TableCell align=\"right\">{'Amount'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {summary.top.map((deal) => (\n <TableRow key={deal.$id}>\n <TableCell>\n <AppLink href={routes.deal(deal.$id)}>\n {deal.title || deal.$id}\n </AppLink>\n </TableCell>\n <TableCell>{summary.stageName(deal)}</TableCell>\n <TableCell>{shortDate(deal.expectedCloseAtMs)}</TableCell>\n <TableCell align=\"right\">\n {money(\n Number(deal.amountCents ?? 0),\n String(deal.currency || currency),\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {/* What the ranking, and so the export, is drawn from when that is not everything. */}\n {dealWindow.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Ranked within the ${OPEN_DEAL_CEILING.toLocaleString()} most recently updated open deals.`}\n </Typography>\n ) : null}\n </Section>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nPipelineCard.displayName = 'PipelineCard'\n\nexport default PipelineCard\n"],"names":["Aglyn","stuckDeals","money","AppLink","CardDisplay","Section","ScrollTable","Alert","Stack","TableBody","TableCell","TableHead","TableRow","Typography","count","getAggregateFromServer","limit","orderBy","query","sum","where","useMemo","useFirestore","collectionCeiling","ReportBreakdown","ReportExport","plural","reportFilename","shortDate","reportCacheKey","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","useWindowRead","currencyOfDeals","pipelineTotals","OPEN_DEAL_CEILING","PIPELINE_CEILING","TOP_DEALS","TOP_DEAL_COLUMNS","PipelineCard","props","report","scope","tokens","routes","nowMs","firestore","open","amountCents","then","snapshot","Number","data","cacheKey","dealWindow","pipelineWindow","dealsStatus","status","summary","byPipeline","Map","deal","rows","key","pipelineId","list","get","push","set","pipelines","filter","pipeline","isPipelineArchived","has","$id","map","totals","known","Set","orphans","stageName","dealStageById","find","stageId","name","top","sort","a","b","slice","orphanTotals","weightedCents","reduce","total","entry","currency","stuck","dealsRead","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","filename","columns","title","expectedCloseAtMs","Date","toISOString","toFixed","String","toUpperCase","disabled","length","spacing","direction","sx","flexWrap","label","value","toLocaleString","note","href","section","truncated","riseIsGood","color","undefined","severity","mixed","variant","stages","row","stage","id","display","unplaced","emptyText","size","align","displayName"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ import { csvDocument } from "@aglyn/aglyn";
|
|
|
19
19
|
import { mdiDownloadOutline } from "@aglyn/shared-data-mdi";
|
|
20
20
|
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
21
21
|
import { Button } from "@mui/material";
|
|
22
|
-
import { downloadTextFile } from "../../model/
|
|
22
|
+
import { downloadTextFile } from "../../model/download-text-file.js";
|
|
23
23
|
/**
|
|
24
24
|
* Export CSV for one report table (AGL-2624), in its card's header.
|
|
25
25
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/components/reports/report-export.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { csvDocument } from '@aglyn/aglyn'\nimport { mdiDownloadOutline } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { Button } from '@mui/material'\nimport { downloadTextFile } from '../../model/
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/components/reports/report-export.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { csvDocument } from '@aglyn/aglyn'\nimport { mdiDownloadOutline } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { Button } from '@mui/material'\nimport { downloadTextFile } from '../../model/download-text-file'\n\nexport interface ReportExportProps {\n /** The file's name — see `reportFilename`. */\n filename: string\n /** The header row, in the table's column order. */\n columns: readonly string[]\n /**\n * The rows, one array per line in the header's order, built when the\n * button is pressed rather than on every render: a table of a thousand\n * rows is serialized once, on demand, and never while nobody asked.\n */\n rows: () => ReadonlyArray<ReadonlyArray<unknown>>\n /** Nothing to write yet — the window is still reading, or came back empty. */\n disabled?: boolean\n}\n\n/**\n * Export CSV for one report table (AGL-2624), in its card's header.\n *\n * Client-side, from the window the card has already loaded: a report table\n * is at most a thousand grouped rows, and the file is those rows exactly as\n * drawn — the same names, the same counts, the same order — so a reader can\n * check the export against the screen. It is NOT a fresh read, so when the\n * card's window was full the card says so in a caption beneath the table;\n * an export that quietly wrote a sample as if it were the whole would be\n * the one thing that caption exists to prevent.\n */\nexport function ReportExport(props: ReportExportProps) {\n const { filename, columns, rows, disabled = false } = props\n return (\n <Button\n size=\"small\"\n startIcon={<MdiIcon path={mdiDownloadOutline.path} size={0.8} />}\n disabled={disabled}\n onClick={() =>\n downloadTextFile(filename, 'text/csv', csvDocument(columns, rows()))\n }\n >\n {'Export CSV'}\n </Button>\n )\n}\nReportExport.displayName = 'ReportExport'\n\nexport default ReportExport\n"],"names":["csvDocument","mdiDownloadOutline","MdiIcon","Button","downloadTextFile","ReportExport","props","filename","columns","rows","disabled","size","startIcon","path","onClick","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,WAAW,QAAQ,eAAc;AAC1C,SAASC,kBAAkB,QAAQ,yBAAwB;AAC3D,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,MAAM,QAAQ,gBAAe;AACtC,SAASC,gBAAgB,QAAQ,oCAAgC;AAiBjE;;;;;;;;;;CAUC,GACD,OAAO,SAASC,aAAaC,KAAwB;IACnD,MAAM,EAAEC,QAAQ,EAAEC,OAAO,EAAEC,IAAI,EAAEC,WAAW,KAAK,EAAE,GAAGJ;IACtD,qBACE,KAACH;QACCQ,MAAK;QACLC,yBAAW,KAACV;YAAQW,MAAMZ,mBAAmBY,IAAI;YAAEF,MAAM;;QACzDD,UAAUA;QACVI,SAAS,IACPV,iBAAiBG,UAAU,YAAYP,YAAYQ,SAASC;kBAG7D;;AAGP;AACAJ,aAAaU,WAAW,GAAG;AAE3B,eAAeV,aAAY"}
|
|
@@ -30,6 +30,7 @@ import { ContactsMixCard } from "./reports/contacts-mix-card.js";
|
|
|
30
30
|
import { ContactsTrendCard } from "./reports/contacts-trend-card.js";
|
|
31
31
|
import { ForecastCard } from "./reports/forecast-card.js";
|
|
32
32
|
import { LeadFunnelCard } from "./reports/lead-funnel-card.js";
|
|
33
|
+
import { LeadSourceCard } from "./reports/lead-source-card.js";
|
|
33
34
|
import { PipelineCard } from "./reports/pipeline-card.js";
|
|
34
35
|
import { SourceConversionCard } from "./reports/source-conversion-card.js";
|
|
35
36
|
import { WonLostByOwnerCard } from "./reports/won-lost-by-owner-card.js";
|
|
@@ -248,6 +249,13 @@ import { CRM_REPORT_PERIOD_LABELS, CRM_REPORT_PERIODS, crmReportRange } from "..
|
|
|
248
249
|
hostId: hostId
|
|
249
250
|
})
|
|
250
251
|
},
|
|
252
|
+
{
|
|
253
|
+
key: 'lead-sources',
|
|
254
|
+
children: /*#__PURE__*/ _jsx(LeadSourceCard, {
|
|
255
|
+
report: report,
|
|
256
|
+
hostId: hostId
|
|
257
|
+
})
|
|
258
|
+
},
|
|
251
259
|
{
|
|
252
260
|
key: 'pipeline',
|
|
253
261
|
children: /*#__PURE__*/ _jsx(PipelineCard, {
|