@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
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
-
import { checkEntitlement,
|
|
19
|
+
import { checkEntitlement, planLabelGrantingFeature, pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
20
|
import { pluginRecordListHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
|
|
21
21
|
import { AppLink, CardDisplay, SrOnly } from "@aglyn/shared-ui-jsx";
|
|
22
22
|
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
@@ -26,6 +26,7 @@ import { Alert, Button, Drawer, MenuItem, Stack, TableBody, TableCell, TableHead
|
|
|
26
26
|
import { collection } from "firebase/firestore";
|
|
27
27
|
import { Fragment, useCallback, useEffect, useMemo, useState } from "react";
|
|
28
28
|
import { useCrmOrgMount } from "../hooks/use-crm-org-mount.js";
|
|
29
|
+
import { CRM_ACTION_RECIPES } from "../model/crm-recipes.js";
|
|
29
30
|
import { useCrmApi } from "./use-crm-api.js";
|
|
30
31
|
/** How many of a site's forms the picker offers before it says it stopped. */ const FORM_OPTION_CEILING = 100;
|
|
31
32
|
/**
|
|
@@ -147,10 +148,11 @@ InstalledOn.displayName = 'InstalledOn';
|
|
|
147
148
|
* pays no read. Live forms only: an archived form collects nothing, and a
|
|
148
149
|
* recipe keyed on it would never fire.
|
|
149
150
|
*/ function RecipeInstallForm(props) {
|
|
151
|
+
var _recipe_picks;
|
|
150
152
|
const { recipe, mount, status, onClose, onInstalled } = props;
|
|
151
153
|
const firestore = useFirestore();
|
|
152
154
|
const api = useCrmApi(null);
|
|
153
|
-
const needsForm = recipe.
|
|
155
|
+
const needsForm = ((_recipe_picks = recipe.picks) == null ? void 0 : _recipe_picks.kind) === 'form';
|
|
154
156
|
const [hostId, setHostId] = useState(()=>{
|
|
155
157
|
const pick = mount.createHostId;
|
|
156
158
|
return pick && standingOf(status, pick, recipe.id) !== 'installed' ? pick : '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recipes-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 {\n type AglynOrgBilling,\n checkEntitlement,\n CRM_ACTION_RECIPES,\n type CrmActionRecipe,\n type CrmActionRecipeId,\n planLabelGrantingFeature,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { pluginRecordListHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AppLink, CardDisplay, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Drawer,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport type {\n CrmRecipeInstallResult,\n CrmRecipeSiteStatus,\n} from '../constants/api-routes'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmApi } from './use-crm-api'\n\n/** How many of a site's forms the picker offers before it says it stopped. */\nconst FORM_OPTION_CEILING = 100\n\n/**\n * The site's list of actions — where an installed action lives and is\n * edited — at the address the plugin that keeps actions publishes, or `null`\n * for a site whose subdomain the mount could not answer or a workspace where\n * no plugin keeps actions, which is named and not linked.\n */\nfunction siteActionsHref(mount: CrmOrgMount, hostId: string): string | null {\n const subdomain = mount.siteSubdomain(hostId)\n return subdomain\n ? pluginRecordListHref('action', { orgSlug: mount.orgSlug, host: subdomain })\n : null\n}\n\n/**\n * Where a site stands with one recipe, read off the stamps the status route\n * answered. `maybe` is the third answer and the honest one: the site\n * carries actions saved before the stamp existed, any of which could have\n * started from the recipe, so the card neither lists the site as carrying\n * it nor offers the install as if to a blank site. `unread` is the answer\n * before the route has answered, or for a site it did not name.\n */\ntype SiteStanding = 'installed' | 'maybe' | 'clear' | 'unread'\n\nfunction standingOf(\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null,\n hostId: string,\n recipeId: CrmActionRecipeId,\n): SiteStanding {\n const site = status?.get(hostId)\n if (!site) return 'unread'\n if (site.installed.includes(recipeId)) return 'installed'\n return site.unstamped > 0 ? 'maybe' : 'clear'\n}\n\n/**\n * The stamps of every site of the org, read once the site list is known\n * and again after each install, so the card reports what the documents\n * say rather than what it last did.\n */\nfunction useRecipeStatus(mount: CrmOrgMount | null) {\n const api = useCrmApi(null)\n const [sites, setSites] = useState<Map<string, CrmRecipeSiteStatus> | null>(null)\n const [error, setError] = useState<string | null>(null)\n const refresh = useCallback(async () => {\n try {\n const { response, payload } = await api('recipe-status', {})\n if (!response.ok) {\n setError(payload?.error ?? 'Which sites carry a recipe could not be read.')\n return\n }\n const answered = (payload.sites ?? []) as CrmRecipeSiteStatus[]\n setSites(new Map(answered.map((site) => [site.hostId, site])))\n setError(null)\n } catch (caught) {\n console.error(caught)\n setError('Which sites carry a recipe could not be read.')\n }\n }, [api])\n const hostsReady = Boolean(mount?.hostsReady)\n useEffect(() => {\n if (hostsReady) void refresh()\n }, [hostsReady, refresh])\n return { sites, error, refresh }\n}\n\n/** A site's name, linked into its Actions page when the link is known. */\nfunction SiteLink(props: { mount: CrmOrgMount; hostId: string }) {\n const { mount, hostId } = props\n const href = siteActionsHref(mount, hostId)\n const name = mount.siteName(hostId)\n return href ? <AppLink href={href}>{name}</AppLink> : <>{name}</>\n}\nSiteLink.displayName = 'SiteLink'\n\n/** One recipe's \"Installed on\" cell: the sites that carry it, and the ones that may. */\nfunction InstalledOn(props: {\n mount: CrmOrgMount\n recipe: CrmActionRecipe\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n}) {\n const { mount, recipe, status } = props\n if (!status) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Reading…'}\n </Typography>\n )\n }\n const installed = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'installed',\n )\n const maybe = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'maybe',\n )\n return (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">\n {installed.length === 0\n ? 'Not installed on any site yet.'\n : installed.map((host, index) => (\n <Fragment key={host.id}>\n {index > 0 ? ', ' : null}\n <SiteLink mount={mount} hostId={host.id} />\n </Fragment>\n ))}\n </Typography>\n {maybe.length > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`May already have it: ${maybe.map((host) => host.name).join(', ')} — ` +\n 'actions there were saved before recipes were tracked.'}\n </Typography>\n ) : null}\n </Stack>\n )\n}\nInstalledOn.displayName = 'InstalledOn'\n\ninterface RecipeInstallFormProps {\n recipe: CrmActionRecipe\n mount: CrmOrgMount\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n onClose: () => void\n onInstalled: (result: CrmRecipeInstallResult, hostId: string) => void\n}\n\n/**\n * The install drawer's body: pick the site, pick the form a recipe that\n * needs one is keyed on, confirm. Mounted fresh on every opening — the\n * Drawer unmounts it when closed — so the picks start over each time from\n * the session's site, when that site does not already carry the recipe.\n *\n * The forms are read from the picked site only once a site is picked and\n * only for a recipe that needs one, so a reader installing the other three\n * pays no read. Live forms only: an archived form collects nothing, and a\n * recipe keyed on it would never fire.\n */\nfunction RecipeInstallForm(props: RecipeInstallFormProps) {\n const { recipe, mount, status, onClose, onInstalled } = props\n const firestore = useFirestore()\n const api = useCrmApi(null)\n const needsForm = recipe.needs === 'form'\n const [hostId, setHostId] = useState(() => {\n const pick = mount.createHostId\n return pick && standingOf(status, pick, recipe.id) !== 'installed' ? pick : ''\n })\n const [formId, setFormId] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const { data: formRead, status: formsStatus } = useFirestoreCollection<any>(\n () =>\n needsForm && hostId\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'forms'),\n FORM_OPTION_CEILING,\n )\n : null,\n [firestore, needsForm, hostId],\n { idField: '$id' },\n )\n const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow<any>(\n formRead,\n FORM_OPTION_CEILING,\n )\n const formOptions = useMemo(\n () =>\n (formDocs ?? [])\n .filter((form: any) => !form.archivedAt)\n .map((form: any) => ({\n id: form.$id as string,\n name: (String(form.displayName ?? '').trim() || form.$id) as string,\n }))\n .sort((a: { name: string }, b: { name: string }) =>\n a.name.localeCompare(b.name),\n ),\n [formDocs],\n )\n const formsLoading = needsForm && Boolean(hostId) && formsStatus === 'loading'\n\n const canInstall =\n Boolean(hostId) && (!needsForm || Boolean(formId)) && !busy && !formsLoading\n\n const handleInstall = async () => {\n if (!canInstall) return\n setBusy(true)\n setError(null)\n try {\n const { response, payload } = await api('recipe-install', {\n hostId,\n recipeId: recipe.id,\n ...(formId ? { formId } : {}),\n })\n if (!response.ok) {\n setError(payload?.error ?? 'The recipe could not be installed.')\n return\n }\n onInstalled(payload as CrmRecipeInstallResult, hostId)\n } catch (caught) {\n console.error(caught)\n setError('The recipe could not be installed.')\n } finally {\n setBusy(false)\n }\n }\n\n const siteSuffix = (id: string) => {\n const standing = standingOf(status, id, recipe.id)\n if (standing === 'installed') return ' — installed'\n if (standing === 'maybe') return ' — may already have it'\n return ''\n }\n\n return (\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{`Install “${recipe.title}”`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={hostId}\n onChange={(event) => {\n setHostId(event.target.value)\n setFormId('')\n setError(null)\n }}\n disabled={busy}\n helperText={\n mount.hosts.length === 0\n ? 'This organization has no sites yet.'\n : 'The action is written into this site’s Automation → Actions.'\n }\n fullWidth\n >\n {mount.hosts.map((host) => (\n <MenuItem\n key={host.id}\n value={host.id}\n disabled={standingOf(status, host.id, recipe.id) === 'installed'}\n >\n {`${host.name}${siteSuffix(host.id)}`}\n </MenuItem>\n ))}\n </TextField>\n {needsForm ? (\n <TextField\n select\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n disabled={!hostId || busy || formsLoading}\n helperText={\n !hostId\n ? 'Pick the site first; the recipe is keyed on one of its forms.'\n : formsLoading\n ? 'Loading the site’s forms…'\n : formOptions.length === 0\n ? 'This site has no live forms.'\n : formsTruncated\n ? `Showing the first ${FORM_OPTION_CEILING} forms.`\n : 'Contacts this form makes are tagged with its name.'\n }\n fullWidth\n >\n {formOptions.map((form) => (\n <MenuItem key={form.id} value={form.id}>\n {form.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The action is created enabled, exactly as the recipe defines it. ' +\n 'Change anything afterwards from the site’s Automation → Actions page.'}\n </Typography>\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!canInstall}\n onClick={() => void handleInstall()}\n >\n {busy ? 'Installing…' : 'Install'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nRecipeInstallForm.displayName = 'RecipeInstallForm'\n\nexport interface RecipesCardProps {\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Recipes\" — the CRM's ready-made automations, installed on a site from\n * the organization (AGL-2639).\n *\n * ## Why this card exists at the org level and nowhere else\n *\n * Automation is a site feature: a site's Actions page has a Recipes menu\n * that opens the editor prefilled, and the person saves what they see.\n * The org hub mounts over every site with no editor of its own, so it had\n * no recipes at all, and an org running five sites installed \"Welcome a\n * new lead\" five times by hand. This card is the org's door: every recipe,\n * the sites that already carry it, and Install, which writes the action\n * into the site the reader picks through the server — the server stamps\n * the recipe, refuses a site outside the org, and refuses a second copy.\n * Under a site the section does not mount it; the site's own menu is the\n * right door there.\n *\n * ## \"Installed on\" reads the stamp, and says when it cannot\n *\n * The status route answers, per site, the recipes a live action is stamped\n * with and how many live actions carry no stamp. A site with any of the\n * latter is named as one that MAY already have the recipe rather than\n * listed as clear: those actions were saved before the stamp existed and\n * the card cannot tell a hand-built welcome from an installed one.\n *\n * ## Creation opens a drawer\n *\n * Install is a button per row; the picks — the site, and for Tag by form\n * one of that site's forms — live in a drawer, never above the list.\n */\nexport function RecipesCard(props: RecipesCardProps) {\n const { org } = props\n const mount = useCrmOrgMount()\n const { enqueueSnackbar } = useSnackbar()\n const status = useRecipeStatus(mount)\n const refreshStatus = status.refresh\n const [installing, setInstalling] = useState<CrmActionRecipe | null>(null)\n const [lastInstall, setLastInstall] = useState<{\n hostId: string\n name: string\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const canInstall = Boolean(mount?.hostsReady) && entitled\n\n const handleInstalled = useCallback(\n (result: CrmRecipeInstallResult, hostId: string) => {\n setInstalling(null)\n setLastInstall({ hostId, name: result.name })\n enqueueSnackbar(\n `Installed “${result.name}” on ${mount?.siteName(hostId) ?? hostId}`,\n { variant: 'success', persist: false },\n )\n void refreshStatus()\n },\n [enqueueSnackbar, mount, refreshStatus],\n )\n\n // Mounted by the section only beneath the org hub's provider; the hooks\n // above run either way, so the render order never depends on the mount.\n if (!mount) return null\n\n const lastInstallHref = lastInstall ? siteActionsHref(mount, lastInstall.hostId) : null\n return (\n <CardDisplay\n header={'Recipes'}\n help={pluginDocsHelp('crmSettings', { anchor: '#recipes' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Ready-made automations for the CRM. Install one on a site and the ' +\n 'action is written into that site’s Automation → Actions as the ' +\n 'recipe defines it, where it can be edited like any other action.'}\n </Typography>\n {lastInstall ? (\n <Alert\n severity=\"success\"\n onClose={() => setLastInstall(null)}\n action={\n lastInstallHref ? (\n // A link dressed as a button: the anchor carries its own\n // href, so it keeps the link role a navigation door has.\n <Button\n component={AppLink as any}\n {...({\n componentVariant: 'naked',\n nativeButton: false,\n } as any)}\n href={lastInstallHref}\n size=\"small\"\n color=\"inherit\"\n >\n {'Open Automation → Actions'}\n </Button>\n ) : undefined\n }\n >\n {`Installed “${lastInstall.name}” on ${mount.siteName(lastInstall.hostId)}.`}\n </Alert>\n ) : null}\n {status.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {status.error}\n </Typography>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Recipe'}</TableCell>\n <TableCell>{'Installed on'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {CRM_ACTION_RECIPES.map((recipe) => (\n <TableRow key={recipe.id} hover>\n <TableCell sx={{ maxWidth: 360 }}>\n <Typography variant=\"body2\">{recipe.title}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n </TableCell>\n <TableCell>\n <InstalledOn mount={mount} recipe={recipe} status={status.sites} />\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 120 }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!canInstall}\n onClick={() => setInstalling(recipe)}\n >\n {'Install'}\n <SrOnly>{` ${recipe.title}`}</SrOnly>\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {!entitled ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`The actions builder requires the ${planLabelGrantingFeature('actions') ?? 'Pro'} ` +\n 'plan — see Billing to upgrade.'}\n </Typography>\n ) : null}\n {mount.hostsReady && mount.hosts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nowhere to install a recipe.'}\n </Typography>\n ) : null}\n </Stack>\n <Drawer anchor=\"right\" open={Boolean(installing)} onClose={() => setInstalling(null)}>\n {installing ? (\n <RecipeInstallForm\n recipe={installing}\n mount={mount}\n status={status.sites}\n onClose={() => setInstalling(null)}\n onInstalled={handleInstalled}\n />\n ) : null}\n </Drawer>\n </CardDisplay>\n )\n}\nRecipesCard.displayName = 'RecipesCard'\n\nexport default RecipesCard\n"],"names":["checkEntitlement","CRM_ACTION_RECIPES","planLabelGrantingFeature","pluginDocsHelp","pluginRecordListHref","AppLink","CardDisplay","SrOnly","ScrollTable","useSnackbar","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","Alert","Button","Drawer","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","Fragment","useCallback","useEffect","useMemo","useState","useCrmOrgMount","useCrmApi","FORM_OPTION_CEILING","siteActionsHref","mount","hostId","subdomain","siteSubdomain","orgSlug","host","standingOf","status","recipeId","site","get","installed","includes","unstamped","useRecipeStatus","api","sites","setSites","error","setError","refresh","payload","response","ok","answered","Map","map","caught","console","hostsReady","Boolean","SiteLink","props","href","name","siteName","displayName","InstalledOn","recipe","variant","color","hosts","filter","id","maybe","spacing","length","index","join","RecipeInstallForm","onClose","onInstalled","firestore","needsForm","needs","setHostId","pick","createHostId","formId","setFormId","busy","setBusy","data","formRead","formsStatus","idField","rows","formDocs","truncated","formsTruncated","formOptions","form","archivedAt","$id","String","trim","sort","a","b","localeCompare","formsLoading","canInstall","handleInstall","siteSuffix","standing","sx","width","p","title","description","select","size","label","value","onChange","event","target","disabled","helperText","fullWidth","direction","onClick","RecipesCard","org","enqueueSnackbar","refreshStatus","installing","setInstalling","lastInstall","setLastInstall","entitled","handleInstalled","result","persist","lastInstallHref","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","action","component","componentVariant","nativeButton","undefined","align","hover","maxWidth","open"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recipes-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 {\n type AglynOrgBilling,\n checkEntitlement,\n planLabelGrantingFeature,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { pluginRecordListHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AppLink, CardDisplay, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Drawer,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport type {\n CrmRecipeInstallResult,\n CrmRecipeSiteStatus,\n} from '../constants/api-routes'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport {\n CRM_ACTION_RECIPES,\n type CrmActionRecipe,\n type CrmActionRecipeId,\n} from '../model/crm-recipes'\nimport { useCrmApi } from './use-crm-api'\n\n/** How many of a site's forms the picker offers before it says it stopped. */\nconst FORM_OPTION_CEILING = 100\n\n/**\n * The site's list of actions — where an installed action lives and is\n * edited — at the address the plugin that keeps actions publishes, or `null`\n * for a site whose subdomain the mount could not answer or a workspace where\n * no plugin keeps actions, which is named and not linked.\n */\nfunction siteActionsHref(mount: CrmOrgMount, hostId: string): string | null {\n const subdomain = mount.siteSubdomain(hostId)\n return subdomain\n ? pluginRecordListHref('action', { orgSlug: mount.orgSlug, host: subdomain })\n : null\n}\n\n/**\n * Where a site stands with one recipe, read off the stamps the status route\n * answered. `maybe` is the third answer and the honest one: the site\n * carries actions saved before the stamp existed, any of which could have\n * started from the recipe, so the card neither lists the site as carrying\n * it nor offers the install as if to a blank site. `unread` is the answer\n * before the route has answered, or for a site it did not name.\n */\ntype SiteStanding = 'installed' | 'maybe' | 'clear' | 'unread'\n\nfunction standingOf(\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null,\n hostId: string,\n recipeId: CrmActionRecipeId,\n): SiteStanding {\n const site = status?.get(hostId)\n if (!site) return 'unread'\n if (site.installed.includes(recipeId)) return 'installed'\n return site.unstamped > 0 ? 'maybe' : 'clear'\n}\n\n/**\n * The stamps of every site of the org, read once the site list is known\n * and again after each install, so the card reports what the documents\n * say rather than what it last did.\n */\nfunction useRecipeStatus(mount: CrmOrgMount | null) {\n const api = useCrmApi(null)\n const [sites, setSites] = useState<Map<string, CrmRecipeSiteStatus> | null>(null)\n const [error, setError] = useState<string | null>(null)\n const refresh = useCallback(async () => {\n try {\n const { response, payload } = await api('recipe-status', {})\n if (!response.ok) {\n setError(payload?.error ?? 'Which sites carry a recipe could not be read.')\n return\n }\n const answered = (payload.sites ?? []) as CrmRecipeSiteStatus[]\n setSites(new Map(answered.map((site) => [site.hostId, site])))\n setError(null)\n } catch (caught) {\n console.error(caught)\n setError('Which sites carry a recipe could not be read.')\n }\n }, [api])\n const hostsReady = Boolean(mount?.hostsReady)\n useEffect(() => {\n if (hostsReady) void refresh()\n }, [hostsReady, refresh])\n return { sites, error, refresh }\n}\n\n/** A site's name, linked into its Actions page when the link is known. */\nfunction SiteLink(props: { mount: CrmOrgMount; hostId: string }) {\n const { mount, hostId } = props\n const href = siteActionsHref(mount, hostId)\n const name = mount.siteName(hostId)\n return href ? <AppLink href={href}>{name}</AppLink> : <>{name}</>\n}\nSiteLink.displayName = 'SiteLink'\n\n/** One recipe's \"Installed on\" cell: the sites that carry it, and the ones that may. */\nfunction InstalledOn(props: {\n mount: CrmOrgMount\n recipe: CrmActionRecipe\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n}) {\n const { mount, recipe, status } = props\n if (!status) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Reading…'}\n </Typography>\n )\n }\n const installed = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'installed',\n )\n const maybe = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'maybe',\n )\n return (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">\n {installed.length === 0\n ? 'Not installed on any site yet.'\n : installed.map((host, index) => (\n <Fragment key={host.id}>\n {index > 0 ? ', ' : null}\n <SiteLink mount={mount} hostId={host.id} />\n </Fragment>\n ))}\n </Typography>\n {maybe.length > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`May already have it: ${maybe.map((host) => host.name).join(', ')} — ` +\n 'actions there were saved before recipes were tracked.'}\n </Typography>\n ) : null}\n </Stack>\n )\n}\nInstalledOn.displayName = 'InstalledOn'\n\ninterface RecipeInstallFormProps {\n recipe: CrmActionRecipe\n mount: CrmOrgMount\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n onClose: () => void\n onInstalled: (result: CrmRecipeInstallResult, hostId: string) => void\n}\n\n/**\n * The install drawer's body: pick the site, pick the form a recipe that\n * needs one is keyed on, confirm. Mounted fresh on every opening — the\n * Drawer unmounts it when closed — so the picks start over each time from\n * the session's site, when that site does not already carry the recipe.\n *\n * The forms are read from the picked site only once a site is picked and\n * only for a recipe that needs one, so a reader installing the other three\n * pays no read. Live forms only: an archived form collects nothing, and a\n * recipe keyed on it would never fire.\n */\nfunction RecipeInstallForm(props: RecipeInstallFormProps) {\n const { recipe, mount, status, onClose, onInstalled } = props\n const firestore = useFirestore()\n const api = useCrmApi(null)\n const needsForm = recipe.picks?.kind === 'form'\n const [hostId, setHostId] = useState(() => {\n const pick = mount.createHostId\n return pick && standingOf(status, pick, recipe.id) !== 'installed' ? pick : ''\n })\n const [formId, setFormId] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const { data: formRead, status: formsStatus } = useFirestoreCollection<any>(\n () =>\n needsForm && hostId\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'forms'),\n FORM_OPTION_CEILING,\n )\n : null,\n [firestore, needsForm, hostId],\n { idField: '$id' },\n )\n const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow<any>(\n formRead,\n FORM_OPTION_CEILING,\n )\n const formOptions = useMemo(\n () =>\n (formDocs ?? [])\n .filter((form: any) => !form.archivedAt)\n .map((form: any) => ({\n id: form.$id as string,\n name: (String(form.displayName ?? '').trim() || form.$id) as string,\n }))\n .sort((a: { name: string }, b: { name: string }) =>\n a.name.localeCompare(b.name),\n ),\n [formDocs],\n )\n const formsLoading = needsForm && Boolean(hostId) && formsStatus === 'loading'\n\n const canInstall =\n Boolean(hostId) && (!needsForm || Boolean(formId)) && !busy && !formsLoading\n\n const handleInstall = async () => {\n if (!canInstall) return\n setBusy(true)\n setError(null)\n try {\n const { response, payload } = await api('recipe-install', {\n hostId,\n recipeId: recipe.id,\n ...(formId ? { formId } : {}),\n })\n if (!response.ok) {\n setError(payload?.error ?? 'The recipe could not be installed.')\n return\n }\n onInstalled(payload as CrmRecipeInstallResult, hostId)\n } catch (caught) {\n console.error(caught)\n setError('The recipe could not be installed.')\n } finally {\n setBusy(false)\n }\n }\n\n const siteSuffix = (id: string) => {\n const standing = standingOf(status, id, recipe.id)\n if (standing === 'installed') return ' — installed'\n if (standing === 'maybe') return ' — may already have it'\n return ''\n }\n\n return (\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{`Install “${recipe.title}”`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={hostId}\n onChange={(event) => {\n setHostId(event.target.value)\n setFormId('')\n setError(null)\n }}\n disabled={busy}\n helperText={\n mount.hosts.length === 0\n ? 'This organization has no sites yet.'\n : 'The action is written into this site’s Automation → Actions.'\n }\n fullWidth\n >\n {mount.hosts.map((host) => (\n <MenuItem\n key={host.id}\n value={host.id}\n disabled={standingOf(status, host.id, recipe.id) === 'installed'}\n >\n {`${host.name}${siteSuffix(host.id)}`}\n </MenuItem>\n ))}\n </TextField>\n {needsForm ? (\n <TextField\n select\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n disabled={!hostId || busy || formsLoading}\n helperText={\n !hostId\n ? 'Pick the site first; the recipe is keyed on one of its forms.'\n : formsLoading\n ? 'Loading the site’s forms…'\n : formOptions.length === 0\n ? 'This site has no live forms.'\n : formsTruncated\n ? `Showing the first ${FORM_OPTION_CEILING} forms.`\n : 'Contacts this form makes are tagged with its name.'\n }\n fullWidth\n >\n {formOptions.map((form) => (\n <MenuItem key={form.id} value={form.id}>\n {form.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The action is created enabled, exactly as the recipe defines it. ' +\n 'Change anything afterwards from the site’s Automation → Actions page.'}\n </Typography>\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!canInstall}\n onClick={() => void handleInstall()}\n >\n {busy ? 'Installing…' : 'Install'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nRecipeInstallForm.displayName = 'RecipeInstallForm'\n\nexport interface RecipesCardProps {\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Recipes\" — the CRM's ready-made automations, installed on a site from\n * the organization (AGL-2639).\n *\n * ## Why this card exists at the org level and nowhere else\n *\n * Automation is a site feature: a site's Actions page has a Recipes menu\n * that opens the editor prefilled, and the person saves what they see.\n * The org hub mounts over every site with no editor of its own, so it had\n * no recipes at all, and an org running five sites installed \"Welcome a\n * new lead\" five times by hand. This card is the org's door: every recipe,\n * the sites that already carry it, and Install, which writes the action\n * into the site the reader picks through the server — the server stamps\n * the recipe, refuses a site outside the org, and refuses a second copy.\n * Under a site the section does not mount it; the site's own menu is the\n * right door there.\n *\n * ## \"Installed on\" reads the stamp, and says when it cannot\n *\n * The status route answers, per site, the recipes a live action is stamped\n * with and how many live actions carry no stamp. A site with any of the\n * latter is named as one that MAY already have the recipe rather than\n * listed as clear: those actions were saved before the stamp existed and\n * the card cannot tell a hand-built welcome from an installed one.\n *\n * ## Creation opens a drawer\n *\n * Install is a button per row; the picks — the site, and for Tag by form\n * one of that site's forms — live in a drawer, never above the list.\n */\nexport function RecipesCard(props: RecipesCardProps) {\n const { org } = props\n const mount = useCrmOrgMount()\n const { enqueueSnackbar } = useSnackbar()\n const status = useRecipeStatus(mount)\n const refreshStatus = status.refresh\n const [installing, setInstalling] = useState<CrmActionRecipe | null>(null)\n const [lastInstall, setLastInstall] = useState<{\n hostId: string\n name: string\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const canInstall = Boolean(mount?.hostsReady) && entitled\n\n const handleInstalled = useCallback(\n (result: CrmRecipeInstallResult, hostId: string) => {\n setInstalling(null)\n setLastInstall({ hostId, name: result.name })\n enqueueSnackbar(\n `Installed “${result.name}” on ${mount?.siteName(hostId) ?? hostId}`,\n { variant: 'success', persist: false },\n )\n void refreshStatus()\n },\n [enqueueSnackbar, mount, refreshStatus],\n )\n\n // Mounted by the section only beneath the org hub's provider; the hooks\n // above run either way, so the render order never depends on the mount.\n if (!mount) return null\n\n const lastInstallHref = lastInstall ? siteActionsHref(mount, lastInstall.hostId) : null\n return (\n <CardDisplay\n header={'Recipes'}\n help={pluginDocsHelp('crmSettings', { anchor: '#recipes' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Ready-made automations for the CRM. Install one on a site and the ' +\n 'action is written into that site’s Automation → Actions as the ' +\n 'recipe defines it, where it can be edited like any other action.'}\n </Typography>\n {lastInstall ? (\n <Alert\n severity=\"success\"\n onClose={() => setLastInstall(null)}\n action={\n lastInstallHref ? (\n // A link dressed as a button: the anchor carries its own\n // href, so it keeps the link role a navigation door has.\n <Button\n component={AppLink as any}\n {...({\n componentVariant: 'naked',\n nativeButton: false,\n } as any)}\n href={lastInstallHref}\n size=\"small\"\n color=\"inherit\"\n >\n {'Open Automation → Actions'}\n </Button>\n ) : undefined\n }\n >\n {`Installed “${lastInstall.name}” on ${mount.siteName(lastInstall.hostId)}.`}\n </Alert>\n ) : null}\n {status.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {status.error}\n </Typography>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Recipe'}</TableCell>\n <TableCell>{'Installed on'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {CRM_ACTION_RECIPES.map((recipe) => (\n <TableRow key={recipe.id} hover>\n <TableCell sx={{ maxWidth: 360 }}>\n <Typography variant=\"body2\">{recipe.title}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n </TableCell>\n <TableCell>\n <InstalledOn mount={mount} recipe={recipe} status={status.sites} />\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 120 }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!canInstall}\n onClick={() => setInstalling(recipe)}\n >\n {'Install'}\n <SrOnly>{` ${recipe.title}`}</SrOnly>\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {!entitled ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`The actions builder requires the ${planLabelGrantingFeature('actions') ?? 'Pro'} ` +\n 'plan — see Billing to upgrade.'}\n </Typography>\n ) : null}\n {mount.hostsReady && mount.hosts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nowhere to install a recipe.'}\n </Typography>\n ) : null}\n </Stack>\n <Drawer anchor=\"right\" open={Boolean(installing)} onClose={() => setInstalling(null)}>\n {installing ? (\n <RecipeInstallForm\n recipe={installing}\n mount={mount}\n status={status.sites}\n onClose={() => setInstalling(null)}\n onInstalled={handleInstalled}\n />\n ) : null}\n </Drawer>\n </CardDisplay>\n )\n}\nRecipesCard.displayName = 'RecipesCard'\n\nexport default RecipesCard\n"],"names":["checkEntitlement","planLabelGrantingFeature","pluginDocsHelp","pluginRecordListHref","AppLink","CardDisplay","SrOnly","ScrollTable","useSnackbar","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","Alert","Button","Drawer","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","Fragment","useCallback","useEffect","useMemo","useState","useCrmOrgMount","CRM_ACTION_RECIPES","useCrmApi","FORM_OPTION_CEILING","siteActionsHref","mount","hostId","subdomain","siteSubdomain","orgSlug","host","standingOf","status","recipeId","site","get","installed","includes","unstamped","useRecipeStatus","api","sites","setSites","error","setError","refresh","payload","response","ok","answered","Map","map","caught","console","hostsReady","Boolean","SiteLink","props","href","name","siteName","displayName","InstalledOn","recipe","variant","color","hosts","filter","id","maybe","spacing","length","index","join","RecipeInstallForm","onClose","onInstalled","firestore","needsForm","picks","kind","setHostId","pick","createHostId","formId","setFormId","busy","setBusy","data","formRead","formsStatus","idField","rows","formDocs","truncated","formsTruncated","formOptions","form","archivedAt","$id","String","trim","sort","a","b","localeCompare","formsLoading","canInstall","handleInstall","siteSuffix","standing","sx","width","p","title","description","select","size","label","value","onChange","event","target","disabled","helperText","fullWidth","direction","onClick","RecipesCard","org","enqueueSnackbar","refreshStatus","installing","setInstalling","lastInstall","setLastInstall","entitled","handleInstalled","result","persist","lastInstallHref","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","action","component","componentVariant","nativeButton","undefined","align","hover","maxWidth","open"],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
-
import { CRM_COLLECTIONS, pluginDocsHelp } from "@aglyn/aglyn";
|
|
19
|
+
import { CRM_COLLECTIONS, crmTaskPicklistLabels, crmTaskStatusWrite, pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
20
|
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
21
|
import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
|
|
22
22
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
@@ -27,6 +27,7 @@ import { useCallback, useMemo, useState } from "react";
|
|
|
27
27
|
import { useCrmHubPath } from "../hooks/use-crm-hub-path.js";
|
|
28
28
|
import { useCrmRecordTasks, useNowMs } from "../hooks/use-crm-tasks.js";
|
|
29
29
|
import { useOrgMemberDirectory } from "../hooks/use-org-member-directory.js";
|
|
30
|
+
import { useCrmTaskPicklists } from "../hooks/use-crm-task-picklists.js";
|
|
30
31
|
import { crmRoutes } from "../model/crm-routes.js";
|
|
31
32
|
import { refreshCrmNextActivity } from "../model/next-activity-api.js";
|
|
32
33
|
import { completeCrmTask } from "../model/task-api.js";
|
|
@@ -74,6 +75,7 @@ import TaskSnoozeMenu from "./task-snooze-menu.js";
|
|
|
74
75
|
record
|
|
75
76
|
});
|
|
76
77
|
const directory = useOrgMemberDirectory(orgId);
|
|
78
|
+
const picklists = useCrmTaskPicklists(orgId);
|
|
77
79
|
const open = useMemo(()=>tasks.filter((task)=>task.status !== 'done'), [
|
|
78
80
|
tasks
|
|
79
81
|
]);
|
|
@@ -88,12 +90,11 @@ import TaskSnoozeMenu from "./task-snooze-menu.js";
|
|
|
88
90
|
setBusyId(task.$id);
|
|
89
91
|
try {
|
|
90
92
|
if (task.status === 'done') {
|
|
91
|
-
await updateDoc(doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id), {
|
|
92
|
-
status: 'open',
|
|
93
|
+
await updateDoc(doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id), _extends({}, crmTaskStatusWrite(picklists.status, false), {
|
|
93
94
|
completedAtMs: null,
|
|
94
95
|
completedByUid: deleteField(),
|
|
95
96
|
updatedAt: serverTimestamp()
|
|
96
|
-
});
|
|
97
|
+
}));
|
|
97
98
|
// A client-direct write: the records it names are told (AGL-2661).
|
|
98
99
|
await refreshCrmNextActivity(user, crmTaskCallScope(hostId, orgId), [
|
|
99
100
|
task
|
|
@@ -124,7 +125,8 @@ import TaskSnoozeMenu from "./task-snooze-menu.js";
|
|
|
124
125
|
user,
|
|
125
126
|
hostId,
|
|
126
127
|
orgId,
|
|
127
|
-
enqueueSnackbar
|
|
128
|
+
enqueueSnackbar,
|
|
129
|
+
picklists.status
|
|
128
130
|
]);
|
|
129
131
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
130
132
|
children: [
|
|
@@ -186,7 +188,9 @@ import TaskSnoozeMenu from "./task-snooze-menu.js";
|
|
|
186
188
|
})
|
|
187
189
|
}) : /*#__PURE__*/ _jsx(Stack, {
|
|
188
190
|
spacing: 1,
|
|
189
|
-
children: open.map((task)
|
|
191
|
+
children: open.map((task)=>{
|
|
192
|
+
const labels = crmTaskPicklistLabels(task, picklists);
|
|
193
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
190
194
|
direction: "row",
|
|
191
195
|
spacing: 1,
|
|
192
196
|
sx: {
|
|
@@ -226,6 +230,7 @@ import TaskSnoozeMenu from "./task-snooze-menu.js";
|
|
|
226
230
|
children: [
|
|
227
231
|
/*#__PURE__*/ _jsx(TaskKindCell, {
|
|
228
232
|
kind: task.kind,
|
|
233
|
+
label: labels.type,
|
|
229
234
|
iconOnly: true
|
|
230
235
|
}),
|
|
231
236
|
/*#__PURE__*/ _jsx(Typography, {
|
|
@@ -237,7 +242,8 @@ import TaskSnoozeMenu from "./task-snooze-menu.js";
|
|
|
237
242
|
children: task.title
|
|
238
243
|
}),
|
|
239
244
|
task.priority === 'high' ? /*#__PURE__*/ _jsx(TaskPriorityChip, {
|
|
240
|
-
priority: task.priority
|
|
245
|
+
priority: task.priority,
|
|
246
|
+
label: labels.priority
|
|
241
247
|
}) : null
|
|
242
248
|
]
|
|
243
249
|
}),
|
|
@@ -277,7 +283,8 @@ import TaskSnoozeMenu from "./task-snooze-menu.js";
|
|
|
277
283
|
]
|
|
278
284
|
})
|
|
279
285
|
]
|
|
280
|
-
}, task.$id)
|
|
286
|
+
}, task.$id);
|
|
287
|
+
})
|
|
281
288
|
})
|
|
282
289
|
}),
|
|
283
290
|
/*#__PURE__*/ _jsx(TaskEditDrawer, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/record-tasks-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 { CRM_COLLECTIONS, pluginDocsHelp } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, Checkbox, Stack, Typography } from '@mui/material'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmHubPath } from '../hooks/use-crm-hub-path'\nimport {\n type CrmRecordRef,\n type CrmTaskRow,\n useCrmRecordTasks,\n useNowMs,\n} from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { crmRoutes } from '../model/crm-routes'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { completeCrmTask } from '../model/task-api'\nimport { crmTaskCallScope } from '../model/task-routes'\nimport { TaskDueText, TaskKindCell, TaskPriorityChip } from './task-cells'\nimport TaskEditDrawer from './task-edit-drawer'\nimport TaskSnoozeMenu from './task-snooze-menu'\n\nexport interface RecordTasksCardProps extends CrmRecordRef {\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n /** The org document the shell passed the page; scopes the read when present. */\n org?: Record<string, unknown> | null\n /**\n * The hub's path, for the link to the full tasks list. A page the hub\n * rendered has one to pass; a card mounted anywhere else leaves it out and\n * the path is rebuilt from the URL.\n */\n basePath?: string\n}\n\n/**\n * The open tasks for ONE contact, company or deal, on that record's page\n * (AGL-2599).\n *\n * Exactly one of `contactId`, `companyId`, `dealId` names the record; the\n * first present one wins. A record page drops this in beside its other\n * cards: `<RecordTasksCard hostId={hostId} org={org} contactId={id} />`.\n * Tasks are completed inline and created with the record\n * already linked; everything else about a task is edited by opening it.\n *\n * Open tasks are listed and completed ones are counted, because a record's\n * page is where somebody decides what to do next about the person, and the\n * history of what was done lives in the Done view of the tasks section.\n */\nexport function RecordTasksCard(props: RecordTasksCardProps) {\n const { hostId, org, basePath, contactId, companyId, dealId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const nowMs = useNowMs()\n const hubPath = useCrmHubPath(basePath)\n const routes = useMemo(() => crmRoutes(hubPath), [hubPath])\n const record = useMemo<CrmRecordRef>(\n () =>\n contactId ? { contactId } : companyId ? { companyId } : { dealId },\n [contactId, companyId, dealId],\n )\n const { tasks, status, fromCache, scope, orgId, readTokens } = useCrmRecordTasks({\n hostId,\n org,\n record,\n })\n const directory = useOrgMemberDirectory(orgId)\n const open = useMemo(() => tasks.filter((task) => task.status !== 'done'), [tasks])\n const doneCount = tasks.length - open.length\n\n const [drawer, setDrawer] = useState<{ open: boolean; task: CrmTaskRow | null }>({\n open: false,\n task: null,\n })\n const [busyId, setBusyId] = useState<string | null>(null)\n\n const toggleDone = useCallback(\n async (task: CrmTaskRow) => {\n if (!scope || busyId) return\n setBusyId(task.$id)\n try {\n if (task.status === 'done') {\n await updateDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n {\n status: 'open',\n completedAtMs: null,\n completedByUid: deleteField(),\n updatedAt: serverTimestamp(),\n },\n )\n // A client-direct write: the records it names are told (AGL-2661).\n await refreshCrmNextActivity(user, crmTaskCallScope(hostId, orgId), [task])\n } else {\n // The mounted site, or at the organization level the org — whose\n // variant emits on the task's own site, or nowhere for an org task.\n const callScope = crmTaskCallScope(hostId, orgId)\n if (!callScope) return\n await completeCrmTask(user, { ...callScope, taskId: task.$id })\n enqueueSnackbar('Task completed', { variant: 'success' })\n }\n } catch (cause) {\n enqueueSnackbar(\n cause instanceof Error ? cause.message : 'The task could not be updated.',\n { variant: 'warning' },\n )\n } finally {\n setBusyId(null)\n }\n },\n [scope, busyId, firestore, user, hostId, orgId, enqueueSnackbar],\n )\n\n return (\n <>\n <CardDisplay\n header={'Tasks'}\n help={pluginDocsHelp('crmTasks', {\n anchor: '#tasks-on-a-contact-company-or-deal',\n excerpt:\n 'The open tasks about this record. Tick one to complete it, or ' +\n 'add a call, an email, a meeting or a to-do already linked here.',\n })}\n subheader={\n doneCount\n ? `${open.length} open · ${doneCount} done`\n : open.length\n ? `${open.length} open`\n : undefined\n }\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.section('tasks')}\n size=\"small\"\n color=\"primary\"\n >\n {'All tasks'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n </Stack>\n ),\n }}\n >\n {status === 'error' ? (\n <Typography variant=\"body2\" color=\"error\">\n {'The tasks could not be loaded.'}\n </Typography>\n ) : status === 'success' && !open.length ? (\n <EmptyStateComponent\n compact\n label={doneCount ? 'Everything here is done' : 'No tasks yet'}\n description={\n doneCount\n ? 'The completed ones are kept on the Tasks list.'\n : 'A call, an email, a meeting or a to-do owed to this record.'\n }\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n }\n />\n ) : (\n <Stack spacing={1}>\n {open.map((task) => (\n <Stack\n key={task.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Checkbox\n size=\"small\"\n checked={false}\n disabled={busyId === task.$id}\n onClick={() => void toggleDone(task)}\n slotProps={{\n input: { 'aria-label': `Complete \"${task.title}\"` },\n }}\n />\n <Stack\n sx={{ minWidth: 0, flex: 1, cursor: 'pointer' }}\n onClick={() => setDrawer({ open: true, task })}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', minWidth: 0 }}>\n <TaskKindCell kind={task.kind} iconOnly />\n <Typography variant=\"body2\" noWrap sx={{ minWidth: 0 }}>\n {task.title}\n </Typography>\n {task.priority === 'high' ? (\n <TaskPriorityChip priority={task.priority} />\n ) : null}\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TaskDueText task={task} nowMs={nowMs} variant=\"caption\" />\n {task.assigneeUid ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`· ${directory.nameOf(task.assigneeUid)}`}\n </Typography>\n ) : null}\n {scope ? (\n <TaskSnoozeMenu\n dueAtMs={task.dueAtMs}\n remindAtMs={task.remindAtMs}\n target={{\n write: {\n scope,\n taskId: task.$id,\n call: crmTaskCallScope(hostId, orgId),\n links: task,\n },\n }}\n disabled={busyId === task.$id}\n />\n ) : null}\n </Stack>\n </Stack>\n </Stack>\n ))}\n </Stack>\n )}\n </CardDisplay>\n <TaskEditDrawer\n open={drawer.open}\n onClose={() => setDrawer((prev) => ({ ...prev, open: false }))}\n hostId={hostId}\n org={org}\n orgId={orgId}\n scope={scope}\n readTokens={readTokens}\n task={drawer.task}\n prefill={record}\n seed={{ fromCache, unreadable: status === 'error' }}\n />\n </>\n )\n}\nRecordTasksCard.displayName = 'RecordTasksCard'\n\nexport default RecordTasksCard\n"],"names":["CRM_COLLECTIONS","pluginDocsHelp","AppLink","CardDisplay","EmptyStateComponent","useSnackbar","useFirestore","useUser","Button","Checkbox","Stack","Typography","deleteField","doc","serverTimestamp","updateDoc","useCallback","useMemo","useState","useCrmHubPath","useCrmRecordTasks","useNowMs","useOrgMemberDirectory","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","TaskDueText","TaskKindCell","TaskPriorityChip","TaskEditDrawer","TaskSnoozeMenu","RecordTasksCard","props","hostId","org","basePath","contactId","companyId","dealId","firestore","data","user","enqueueSnackbar","nowMs","hubPath","routes","record","tasks","status","fromCache","scope","orgId","readTokens","directory","open","filter","task","doneCount","length","drawer","setDrawer","busyId","setBusyId","toggleDone","$id","completedAtMs","completedByUid","updatedAt","callScope","taskId","variant","cause","Error","message","header","help","anchor","excerpt","subheader","undefined","contentGutterX","contentGutterY","HeaderProps","action","direction","spacing","component","componentVariant","nativeButton","href","section","size","color","onClick","compact","label","description","map","sx","alignItems","minWidth","checked","disabled","slotProps","input","title","flex","cursor","kind","iconOnly","noWrap","priority","assigneeUid","nameOf","dueAtMs","remindAtMs","target","write","call","links","onClose","prev","prefill","seed","unreadable","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/record-tasks-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 {\n CRM_COLLECTIONS,\n crmTaskPicklistLabels,\n crmTaskStatusWrite,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, Checkbox, Stack, Typography } from '@mui/material'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmHubPath } from '../hooks/use-crm-hub-path'\nimport {\n type CrmRecordRef,\n type CrmTaskRow,\n useCrmRecordTasks,\n useNowMs,\n} from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { useCrmTaskPicklists } from '../hooks/use-crm-task-picklists'\nimport { crmRoutes } from '../model/crm-routes'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { completeCrmTask } from '../model/task-api'\nimport { crmTaskCallScope } from '../model/task-routes'\nimport { TaskDueText, TaskKindCell, TaskPriorityChip } from './task-cells'\nimport TaskEditDrawer from './task-edit-drawer'\nimport TaskSnoozeMenu from './task-snooze-menu'\n\nexport interface RecordTasksCardProps extends CrmRecordRef {\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n /** The org document the shell passed the page; scopes the read when present. */\n org?: Record<string, unknown> | null\n /**\n * The hub's path, for the link to the full tasks list. A page the hub\n * rendered has one to pass; a card mounted anywhere else leaves it out and\n * the path is rebuilt from the URL.\n */\n basePath?: string\n}\n\n/**\n * The open tasks for ONE contact, company or deal, on that record's page\n * (AGL-2599).\n *\n * Exactly one of `contactId`, `companyId`, `dealId` names the record; the\n * first present one wins. A record page drops this in beside its other\n * cards: `<RecordTasksCard hostId={hostId} org={org} contactId={id} />`.\n * Tasks are completed inline and created with the record\n * already linked; everything else about a task is edited by opening it.\n *\n * Open tasks are listed and completed ones are counted, because a record's\n * page is where somebody decides what to do next about the person, and the\n * history of what was done lives in the Done view of the tasks section.\n */\nexport function RecordTasksCard(props: RecordTasksCardProps) {\n const { hostId, org, basePath, contactId, companyId, dealId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const nowMs = useNowMs()\n const hubPath = useCrmHubPath(basePath)\n const routes = useMemo(() => crmRoutes(hubPath), [hubPath])\n const record = useMemo<CrmRecordRef>(\n () =>\n contactId ? { contactId } : companyId ? { companyId } : { dealId },\n [contactId, companyId, dealId],\n )\n const { tasks, status, fromCache, scope, orgId, readTokens } = useCrmRecordTasks({\n hostId,\n org,\n record,\n })\n const directory = useOrgMemberDirectory(orgId)\n const picklists = useCrmTaskPicklists(orgId)\n const open = useMemo(() => tasks.filter((task) => task.status !== 'done'), [tasks])\n const doneCount = tasks.length - open.length\n\n const [drawer, setDrawer] = useState<{ open: boolean; task: CrmTaskRow | null }>({\n open: false,\n task: null,\n })\n const [busyId, setBusyId] = useState<string | null>(null)\n\n const toggleDone = useCallback(\n async (task: CrmTaskRow) => {\n if (!scope || busyId) return\n setBusyId(task.$id)\n try {\n if (task.status === 'done') {\n await updateDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n {\n // Open again, with the label a new task starts with (AGL-3517).\n ...crmTaskStatusWrite(picklists.status, false),\n completedAtMs: null,\n completedByUid: deleteField(),\n updatedAt: serverTimestamp(),\n },\n )\n // A client-direct write: the records it names are told (AGL-2661).\n await refreshCrmNextActivity(user, crmTaskCallScope(hostId, orgId), [task])\n } else {\n // The mounted site, or at the organization level the org — whose\n // variant emits on the task's own site, or nowhere for an org task.\n const callScope = crmTaskCallScope(hostId, orgId)\n if (!callScope) return\n await completeCrmTask(user, { ...callScope, taskId: task.$id })\n enqueueSnackbar('Task completed', { variant: 'success' })\n }\n } catch (cause) {\n enqueueSnackbar(\n cause instanceof Error ? cause.message : 'The task could not be updated.',\n { variant: 'warning' },\n )\n } finally {\n setBusyId(null)\n }\n },\n [scope, busyId, firestore, user, hostId, orgId, enqueueSnackbar, picklists.status],\n )\n\n return (\n <>\n <CardDisplay\n header={'Tasks'}\n help={pluginDocsHelp('crmTasks', {\n anchor: '#tasks-on-a-contact-company-or-deal',\n excerpt:\n 'The open tasks about this record. Tick one to complete it, or ' +\n 'add a call, an email, a meeting or a to-do already linked here.',\n })}\n subheader={\n doneCount\n ? `${open.length} open · ${doneCount} done`\n : open.length\n ? `${open.length} open`\n : undefined\n }\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.section('tasks')}\n size=\"small\"\n color=\"primary\"\n >\n {'All tasks'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n </Stack>\n ),\n }}\n >\n {status === 'error' ? (\n <Typography variant=\"body2\" color=\"error\">\n {'The tasks could not be loaded.'}\n </Typography>\n ) : status === 'success' && !open.length ? (\n <EmptyStateComponent\n compact\n label={doneCount ? 'Everything here is done' : 'No tasks yet'}\n description={\n doneCount\n ? 'The completed ones are kept on the Tasks list.'\n : 'A call, an email, a meeting or a to-do owed to this record.'\n }\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n }\n />\n ) : (\n <Stack spacing={1}>\n {open.map((task) => {\n const labels = crmTaskPicklistLabels(task, picklists)\n return (\n <Stack\n key={task.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Checkbox\n size=\"small\"\n checked={false}\n disabled={busyId === task.$id}\n onClick={() => void toggleDone(task)}\n slotProps={{\n input: { 'aria-label': `Complete \"${task.title}\"` },\n }}\n />\n <Stack\n sx={{ minWidth: 0, flex: 1, cursor: 'pointer' }}\n onClick={() => setDrawer({ open: true, task })}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', minWidth: 0 }}>\n <TaskKindCell kind={task.kind} label={labels.type} iconOnly />\n <Typography variant=\"body2\" noWrap sx={{ minWidth: 0 }}>\n {task.title}\n </Typography>\n {task.priority === 'high' ? (\n <TaskPriorityChip priority={task.priority} label={labels.priority} />\n ) : null}\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TaskDueText task={task} nowMs={nowMs} variant=\"caption\" />\n {task.assigneeUid ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`· ${directory.nameOf(task.assigneeUid)}`}\n </Typography>\n ) : null}\n {scope ? (\n <TaskSnoozeMenu\n dueAtMs={task.dueAtMs}\n remindAtMs={task.remindAtMs}\n target={{\n write: {\n scope,\n taskId: task.$id,\n call: crmTaskCallScope(hostId, orgId),\n links: task,\n },\n }}\n disabled={busyId === task.$id}\n />\n ) : null}\n </Stack>\n </Stack>\n </Stack>\n )\n })}\n </Stack>\n )}\n </CardDisplay>\n <TaskEditDrawer\n open={drawer.open}\n onClose={() => setDrawer((prev) => ({ ...prev, open: false }))}\n hostId={hostId}\n org={org}\n orgId={orgId}\n scope={scope}\n readTokens={readTokens}\n task={drawer.task}\n prefill={record}\n seed={{ fromCache, unreadable: status === 'error' }}\n />\n </>\n )\n}\nRecordTasksCard.displayName = 'RecordTasksCard'\n\nexport default RecordTasksCard\n"],"names":["CRM_COLLECTIONS","crmTaskPicklistLabels","crmTaskStatusWrite","pluginDocsHelp","AppLink","CardDisplay","EmptyStateComponent","useSnackbar","useFirestore","useUser","Button","Checkbox","Stack","Typography","deleteField","doc","serverTimestamp","updateDoc","useCallback","useMemo","useState","useCrmHubPath","useCrmRecordTasks","useNowMs","useOrgMemberDirectory","useCrmTaskPicklists","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","TaskDueText","TaskKindCell","TaskPriorityChip","TaskEditDrawer","TaskSnoozeMenu","RecordTasksCard","props","hostId","org","basePath","contactId","companyId","dealId","firestore","data","user","enqueueSnackbar","nowMs","hubPath","routes","record","tasks","status","fromCache","scope","orgId","readTokens","directory","picklists","open","filter","task","doneCount","length","drawer","setDrawer","busyId","setBusyId","toggleDone","$id","completedAtMs","completedByUid","updatedAt","callScope","taskId","variant","cause","Error","message","header","help","anchor","excerpt","subheader","undefined","contentGutterX","contentGutterY","HeaderProps","action","direction","spacing","component","componentVariant","nativeButton","href","section","size","color","onClick","compact","label","description","map","labels","sx","alignItems","minWidth","checked","disabled","slotProps","input","title","flex","cursor","kind","type","iconOnly","noWrap","priority","assigneeUid","nameOf","dueAtMs","remindAtMs","target","write","call","links","onClose","prev","prefill","seed","unreadable","displayName"],"mappings":"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"}
|
|
@@ -7,6 +7,12 @@ export interface ForecastCardProps {
|
|
|
7
7
|
* month each deal is expected to close, for the next six, per pipeline,
|
|
8
8
|
* at face value and weighted by the stage.
|
|
9
9
|
*
|
|
10
|
+
* And by forecast category (AGL-3516), Salesforce's other way to read it:
|
|
11
|
+
* the open deals by the category each stands in — Pipeline, Best Case,
|
|
12
|
+
* Commit — and the deals won in the period as Closed, each at face value
|
|
13
|
+
* and weighted. A deal the team put in Omitted is counted beside the table
|
|
14
|
+
* rather than in it.
|
|
15
|
+
*
|
|
10
16
|
* Not period-scoped, like the pipeline card and for the same reason: the
|
|
11
17
|
* forecast is what is open NOW, looked at forward. Deals with no expected
|
|
12
18
|
* close are their own row rather than a footnote — that row is the size
|
|
@@ -27,7 +27,7 @@ import { collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-col
|
|
|
27
27
|
import { plural } from "./report-format.js";
|
|
28
28
|
import { reportCacheKey, scopedCollection, visibleToClause } from "./report-scope.js";
|
|
29
29
|
import { useWindowRead } from "./use-aggregate-read.js";
|
|
30
|
-
import { currencyOfDeals, forecastByCloseMonth } from "../../model/crm-reports.js";
|
|
30
|
+
import { currencyOfDeals, FORECAST_CATEGORY_ROWS, forecastByCategory, forecastByCloseMonth } from "../../model/crm-reports.js";
|
|
31
31
|
/**
|
|
32
32
|
* The same window the pipeline card reads, under the SAME cache key and
|
|
33
33
|
* the same dependencies, so the two cards cost one read between them —
|
|
@@ -36,6 +36,11 @@ import { currencyOfDeals, forecastByCloseMonth } from "../../model/crm-reports.j
|
|
|
36
36
|
* name two different questions.
|
|
37
37
|
*/ const OPEN_DEAL_CEILING = 1000;
|
|
38
38
|
const PIPELINE_CEILING = 20;
|
|
39
|
+
/**
|
|
40
|
+
* The won deals of the period, read exactly as the closed-deals card reads
|
|
41
|
+
* them — the same query, ceiling and cache key — so the Closed row costs
|
|
42
|
+
* no read of its own.
|
|
43
|
+
*/ const CLOSED_DEAL_CEILING = 500;
|
|
39
44
|
/** `Sep 2026` — the month a column is. */ function monthLabel(startMs) {
|
|
40
45
|
return new Date(startMs).toLocaleDateString(undefined, {
|
|
41
46
|
month: 'short',
|
|
@@ -47,6 +52,12 @@ const PIPELINE_CEILING = 20;
|
|
|
47
52
|
* month each deal is expected to close, for the next six, per pipeline,
|
|
48
53
|
* at face value and weighted by the stage.
|
|
49
54
|
*
|
|
55
|
+
* And by forecast category (AGL-3516), Salesforce's other way to read it:
|
|
56
|
+
* the open deals by the category each stands in — Pipeline, Best Case,
|
|
57
|
+
* Commit — and the deals won in the period as Closed, each at face value
|
|
58
|
+
* and weighted. A deal the team put in Omitted is counted beside the table
|
|
59
|
+
* rather than in it.
|
|
60
|
+
*
|
|
50
61
|
* Not period-scoped, like the pipeline card and for the same reason: the
|
|
51
62
|
* forecast is what is open NOW, looked at forward. Deals with no expected
|
|
52
63
|
* close are their own row rather than a footnote — that row is the size
|
|
@@ -56,7 +67,7 @@ const PIPELINE_CEILING = 20;
|
|
|
56
67
|
* to the pipeline.
|
|
57
68
|
*/ export function ForecastCard(props) {
|
|
58
69
|
const { report } = props;
|
|
59
|
-
const { scope, tokens, nowMs } = report;
|
|
70
|
+
const { scope, tokens, nowMs, range } = report;
|
|
60
71
|
const firestore = useFirestore();
|
|
61
72
|
const dealWindow = useWindowRead(()=>query(scopedCollection(firestore, scope, 'deals'), ...visibleToClause(tokens), where('status', '==', 'open'), orderBy('updatedAt', 'desc'), limit(OPEN_DEAL_CEILING + 1)), OPEN_DEAL_CEILING, [
|
|
62
73
|
firestore,
|
|
@@ -74,11 +85,25 @@ const PIPELINE_CEILING = 20;
|
|
|
74
85
|
], {
|
|
75
86
|
cacheKey: reportCacheKey(report, 'pipeline:pipelines')
|
|
76
87
|
});
|
|
88
|
+
const wonWindow = useWindowRead(()=>query(scopedCollection(firestore, scope, 'deals'), ...visibleToClause(tokens), where('status', '==', 'won'), where('closedAtMs', '>=', range.from), where('closedAtMs', '<', range.to), orderBy('closedAtMs', 'desc'), limit(CLOSED_DEAL_CEILING + 1)), CLOSED_DEAL_CEILING, [
|
|
89
|
+
firestore,
|
|
90
|
+
scope,
|
|
91
|
+
tokens,
|
|
92
|
+
range
|
|
93
|
+
], {
|
|
94
|
+
cacheKey: reportCacheKey(report, 'closed:won')
|
|
95
|
+
});
|
|
77
96
|
const forecast = useMemo(()=>forecastByCloseMonth(dealWindow.rows, pipelineWindow.rows, nowMs), [
|
|
78
97
|
dealWindow.rows,
|
|
79
98
|
pipelineWindow.rows,
|
|
80
99
|
nowMs
|
|
81
100
|
]);
|
|
101
|
+
const byCategory = useMemo(()=>forecastByCategory(dealWindow.rows, wonWindow.rows, pipelineWindow.rows), [
|
|
102
|
+
dealWindow.rows,
|
|
103
|
+
wonWindow.rows,
|
|
104
|
+
pipelineWindow.rows
|
|
105
|
+
]);
|
|
106
|
+
const categoriesRead = wonWindow.status === 'success';
|
|
82
107
|
const currency = useMemo(()=>currencyOfDeals(dealWindow.rows), [
|
|
83
108
|
dealWindow.rows
|
|
84
109
|
]);
|
|
@@ -141,7 +166,7 @@ const PIPELINE_CEILING = 20;
|
|
|
141
166
|
header: 'Forecast by close month',
|
|
142
167
|
help: Aglyn.pluginDocsHelp('crmReports', {
|
|
143
168
|
anchor: '#forecast-by-close-month',
|
|
144
|
-
excerpt: 'Every open deal by the month it is expected to close, for the ' + 'next six months and per pipeline, at face value and weighted by ' + 'its stage. Deals with no expected close are their own row.'
|
|
169
|
+
excerpt: 'Every open deal by the month it is expected to close, for the ' + 'next six months and per pipeline, at face value and weighted by ' + 'its stage. Deals with no expected close are their own row. Below, ' + 'the same deals by forecast category, with the deals won in the ' + 'period as Closed.'
|
|
145
170
|
}),
|
|
146
171
|
contentGutterX: true,
|
|
147
172
|
contentGutterY: true,
|
|
@@ -227,6 +252,79 @@ const PIPELINE_CEILING = 20;
|
|
|
227
252
|
]
|
|
228
253
|
})
|
|
229
254
|
}),
|
|
255
|
+
read && categoriesRead && byCategory.total.count + byCategory.omitted.count > 0 ? /*#__PURE__*/ _jsxs(Stack, {
|
|
256
|
+
spacing: 1,
|
|
257
|
+
children: [
|
|
258
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
259
|
+
variant: "subtitle2",
|
|
260
|
+
children: 'By forecast category'
|
|
261
|
+
}),
|
|
262
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
263
|
+
children: /*#__PURE__*/ _jsxs(ScrollTable, {
|
|
264
|
+
size: "small",
|
|
265
|
+
"aria-label": "Forecast by category",
|
|
266
|
+
children: [
|
|
267
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
268
|
+
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
269
|
+
children: [
|
|
270
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
271
|
+
children: 'Forecast category'
|
|
272
|
+
}),
|
|
273
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
274
|
+
align: "right",
|
|
275
|
+
children: 'Deals'
|
|
276
|
+
})
|
|
277
|
+
]
|
|
278
|
+
})
|
|
279
|
+
}),
|
|
280
|
+
/*#__PURE__*/ _jsxs(TableBody, {
|
|
281
|
+
children: [
|
|
282
|
+
FORECAST_CATEGORY_ROWS.map((category)=>/*#__PURE__*/ _jsxs(TableRow, {
|
|
283
|
+
"data-forecast-category": category,
|
|
284
|
+
children: [
|
|
285
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
286
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
287
|
+
variant: "body2",
|
|
288
|
+
children: category === 'closed' ? 'Closed (won in the period)' : Aglyn.CRM_FORECAST_CATEGORY_LABELS[category]
|
|
289
|
+
})
|
|
290
|
+
}),
|
|
291
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
292
|
+
align: "right",
|
|
293
|
+
children: cell(byCategory.rows[category], currency.currency)
|
|
294
|
+
})
|
|
295
|
+
]
|
|
296
|
+
}, category)),
|
|
297
|
+
/*#__PURE__*/ _jsxs(TableRow, {
|
|
298
|
+
children: [
|
|
299
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
300
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
301
|
+
variant: "subtitle2",
|
|
302
|
+
children: 'Forecast'
|
|
303
|
+
})
|
|
304
|
+
}),
|
|
305
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
306
|
+
align: "right",
|
|
307
|
+
children: cell(byCategory.total, currency.currency)
|
|
308
|
+
})
|
|
309
|
+
]
|
|
310
|
+
})
|
|
311
|
+
]
|
|
312
|
+
})
|
|
313
|
+
]
|
|
314
|
+
})
|
|
315
|
+
}),
|
|
316
|
+
byCategory.omitted.count > 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
317
|
+
variant: "caption",
|
|
318
|
+
color: "text.secondary",
|
|
319
|
+
children: `${plural(byCategory.omitted.count, 'open deal')} worth ` + `${money(byCategory.omitted.amountCents, currency.currency)} ` + `${byCategory.omitted.count === 1 ? 'is' : 'are'} in Omitted and left out of the forecast.`
|
|
320
|
+
}) : null,
|
|
321
|
+
wonWindow.truncated ? /*#__PURE__*/ _jsx(Typography, {
|
|
322
|
+
variant: "caption",
|
|
323
|
+
color: "text.secondary",
|
|
324
|
+
children: `Closed counts the ${CLOSED_DEAL_CEILING.toLocaleString()} most recently won deals of the period.`
|
|
325
|
+
}) : null
|
|
326
|
+
]
|
|
327
|
+
}) : null,
|
|
230
328
|
read && currency.mixed ? /*#__PURE__*/ _jsx(Typography, {
|
|
231
329
|
variant: "caption",
|
|
232
330
|
color: "text.secondary",
|