@aglyn/plugins-crm 1.0.0-beta.220 → 1.0.0-beta.222
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-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/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 +92 -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 +15 -1
- 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-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +244 -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/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/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 +75 -33
- 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 +162 -0
- package/src/lib/server/assign-contact-owner.js +340 -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 +139 -0
- package/src/lib/server/automation-steps.js +472 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.js +69 -22
- 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-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/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 +22 -4
- package/src/lib/server/merge-contacts.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 +15 -7
- 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 +15 -2
- 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-routes.js +30 -16
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.d.ts +5 -1
- package/src/lib/server-deal-stage.js +10 -5
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +8 -3
- package/src/lib/server.js +47 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +55 -0
- package/src/lib/testing/memory-firestore.js +335 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
- package/src/lib/testing/stand-in-email-send-index.js +49 -0
- package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
- package/src/lib/transfer/activities.d.ts +30 -0
- package/src/lib/transfer/activities.js +132 -0
- package/src/lib/transfer/activities.js.map +1 -0
- package/src/lib/transfer/aliases.d.ts +22 -0
- package/src/lib/transfer/aliases.js +1342 -0
- package/src/lib/transfer/aliases.js.map +1 -0
- package/src/lib/transfer/common.d.ts +222 -0
- package/src/lib/transfer/common.js +832 -0
- package/src/lib/transfer/common.js.map +1 -0
- package/src/lib/transfer/companies.d.ts +43 -0
- package/src/lib/transfer/companies.js +549 -0
- package/src/lib/transfer/companies.js.map +1 -0
- package/src/lib/transfer/contacts.d.ts +31 -0
- package/src/lib/transfer/contacts.js +727 -0
- package/src/lib/transfer/contacts.js.map +1 -0
- package/src/lib/transfer/deals.d.ts +66 -0
- package/src/lib/transfer/deals.js +698 -0
- package/src/lib/transfer/deals.js.map +1 -0
- package/src/lib/transfer/email-templates-package.d.ts +40 -0
- package/src/lib/transfer/email-templates-package.js +76 -0
- package/src/lib/transfer/email-templates-package.js.map +1 -0
- package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
- package/src/lib/transfer/email-templates-package.server.js +176 -0
- package/src/lib/transfer/email-templates-package.server.js.map +1 -0
- package/src/lib/transfer/fields.d.ts +102 -0
- package/src/lib/transfer/fields.js +1755 -0
- package/src/lib/transfer/fields.js.map +1 -0
- package/src/lib/transfer/leads.d.ts +30 -0
- package/src/lib/transfer/leads.js +582 -0
- package/src/lib/transfer/leads.js.map +1 -0
- package/src/lib/transfer/pipelines.d.ts +21 -0
- package/src/lib/transfer/pipelines.js +172 -0
- package/src/lib/transfer/pipelines.js.map +1 -0
- package/src/lib/transfer/register.d.ts +47 -0
- package/src/lib/transfer/register.js +277 -0
- package/src/lib/transfer/register.js.map +1 -0
- package/src/lib/transfer/tasks.d.ts +30 -0
- package/src/lib/transfer/tasks.js +418 -0
- package/src/lib/transfer/tasks.js.map +1 -0
- package/src/lib/transfer/ui.d.ts +20 -0
- package/src/lib/transfer/ui.js +42 -0
- package/src/lib/transfer/ui.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +0 -42
- package/src/lib/components/company-import-drawer.js +0 -99
- package/src/lib/components/company-import-drawer.js.map +0 -1
- package/src/lib/components/contact-import-drawer.d.ts +0 -46
- package/src/lib/components/contact-import-drawer.js +0 -149
- package/src/lib/components/contact-import-drawer.js.map +0 -1
- package/src/lib/components/crm-export-all-button.d.ts +0 -39
- package/src/lib/components/crm-export-all-button.js +0 -98
- package/src/lib/components/crm-export-all-button.js.map +0 -1
- package/src/lib/components/csv-import-drawer.d.ts +0 -139
- package/src/lib/components/csv-import-drawer.js +0 -757
- package/src/lib/components/csv-import-drawer.js.map +0 -1
- package/src/lib/components/deal-import-drawer.d.ts +0 -24
- package/src/lib/components/deal-import-drawer.js +0 -83
- package/src/lib/components/deal-import-drawer.js.map +0 -1
- package/src/lib/components/lead-import-drawer.d.ts +0 -27
- package/src/lib/components/lead-import-drawer.js +0 -118
- package/src/lib/components/lead-import-drawer.js.map +0 -1
- package/src/lib/components/lead-source-values-card.d.ts +0 -27
- package/src/lib/components/lead-source-values-card.js.map +0 -1
- package/src/lib/components/task-import-drawer.d.ts +0 -24
- package/src/lib/components/task-import-drawer.js +0 -82
- package/src/lib/components/task-import-drawer.js.map +0 -1
- package/src/lib/model/companies-csv.d.ts +0 -36
- package/src/lib/model/companies-csv.js +0 -38
- package/src/lib/model/companies-csv.js.map +0 -1
- package/src/lib/model/contacts-csv.d.ts +0 -54
- package/src/lib/model/contacts-csv.js +0 -67
- package/src/lib/model/contacts-csv.js.map +0 -1
- package/src/lib/model/crm-company-import.d.ts +0 -154
- package/src/lib/model/crm-company-import.js +0 -331
- package/src/lib/model/crm-company-import.js.map +0 -1
- package/src/lib/model/crm-csv.d.ts +0 -239
- package/src/lib/model/crm-csv.js +0 -467
- package/src/lib/model/crm-csv.js.map +0 -1
- package/src/lib/model/crm-deal-import.d.ts +0 -112
- package/src/lib/model/crm-deal-import.js +0 -263
- package/src/lib/model/crm-deal-import.js.map +0 -1
- package/src/lib/model/crm-import.d.ts +0 -250
- package/src/lib/model/crm-import.js +0 -467
- package/src/lib/model/crm-import.js.map +0 -1
- package/src/lib/model/crm-lead-import.d.ts +0 -138
- package/src/lib/model/crm-lead-import.js +0 -446
- package/src/lib/model/crm-lead-import.js.map +0 -1
- package/src/lib/model/crm-task-import.d.ts +0 -94
- package/src/lib/model/crm-task-import.js +0 -275
- package/src/lib/model/crm-task-import.js.map +0 -1
- package/src/lib/model/deals-csv.d.ts +0 -32
- package/src/lib/model/deals-csv.js +0 -36
- package/src/lib/model/deals-csv.js.map +0 -1
- package/src/lib/model/import-column-shapes.d.ts +0 -36
- package/src/lib/model/import-column-shapes.js +0 -76
- package/src/lib/model/import-column-shapes.js.map +0 -1
- package/src/lib/model/lead-source-values.d.ts +0 -82
- package/src/lib/model/lead-source-values.js +0 -149
- package/src/lib/model/lead-source-values.js.map +0 -1
- package/src/lib/model/leads-csv.d.ts +0 -40
- package/src/lib/model/leads-csv.js +0 -40
- package/src/lib/model/leads-csv.js.map +0 -1
- package/src/lib/model/tasks-csv.d.ts +0 -28
- package/src/lib/model/tasks-csv.js +0 -28
- package/src/lib/model/tasks-csv.js.map +0 -1
- package/src/lib/server/companies-import.d.ts +0 -57
- package/src/lib/server/companies-import.js +0 -242
- package/src/lib/server/companies-import.js.map +0 -1
- package/src/lib/server/contacts-import.d.ts +0 -109
- package/src/lib/server/contacts-import.js +0 -393
- package/src/lib/server/contacts-import.js.map +0 -1
- package/src/lib/server/deals-import.d.ts +0 -76
- package/src/lib/server/deals-import.js +0 -248
- package/src/lib/server/deals-import.js.map +0 -1
- package/src/lib/server/export-route.d.ts +0 -60
- package/src/lib/server/export-route.js +0 -447
- package/src/lib/server/export-route.js.map +0 -1
- package/src/lib/server/import-context.d.ts +0 -87
- package/src/lib/server/import-context.js +0 -182
- package/src/lib/server/import-context.js.map +0 -1
- package/src/lib/server/lead-source-values.d.ts +0 -50
- package/src/lib/server/lead-source-values.js +0 -191
- package/src/lib/server/lead-source-values.js.map +0 -1
- package/src/lib/server/leads-import.d.ts +0 -84
- package/src/lib/server/leads-import.js +0 -367
- package/src/lib/server/leads-import.js.map +0 -1
- package/src/lib/server/tasks-import.d.ts +0 -54
- package/src/lib/server/tasks-import.js +0 -160
- package/src/lib/server/tasks-import.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/picklist-values-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 CRM_LEAD_TEXT_MAX,\n type CrmPicklist,\n type CrmPicklistDefinition,\n type CrmPicklistId,\n type CrmPicklistObject,\n type CrmPicklistValue,\n isStandardPicklistValueId,\n newResourceScopeFields,\n ORG_SCOPE_TOKEN,\n} from '@aglyn/aglyn'\nimport {\n mdiArrowDown,\n mdiArrowUp,\n mdiDeleteOutline,\n mdiDragVertical,\n mdiEyeOffOutline,\n mdiEyeOutline,\n mdiPencilOutline,\n mdiStarOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n IconButton,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, serverTimestamp, setDoc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport {\n addPicklistValue,\n crmPicklistValuesRouteUrl,\n movePicklistValue,\n picklistAddableMeanings,\n picklistMeaningLabel,\n picklistReplacements,\n type PicklistValuesAction,\n type PicklistValuesRequest,\n type PicklistValuesResponse,\n setPicklistDefault,\n setPicklistValueActive,\n setPicklistValueGroup,\n sortPicklistValues,\n} from '../model/picklist-values'\nimport { crmTaskCallScope } from '../model/task-routes'\n\nexport interface PicklistValuesCardProps {\n /** Which of the CRM's standard picklists this card manages. */\n picklistId: CrmPicklistId\n /** The mounted site, or `null` at the organization level. */\n hostId: string | null\n /** The org whose list this is; `null` while it settles. */\n orgId: string | null\n /** The site a newly written list records as its definer — the Fields page's own. */\n createHostId: string | null\n}\n\n/** Each object's noun, singular and plural, for the card's sentences. */\nconst OBJECT_NOUNS: Record<CrmPicklistObject, [string, string]> = {\n contact: ['contact', 'contacts'],\n company: ['company', 'companies'],\n deal: ['deal', 'deals'],\n lead: ['lead', 'leads'],\n task: ['task', 'tasks'],\n}\n\n/** \"a\", \"a and b\", \"a, b and c\". */\nfunction sentenceList(words: readonly string[]): string {\n return words.length < 2\n ? (words[0] ?? '')\n : `${words.slice(0, -1).join(', ')} and ${words[words.length - 1]}`\n}\n\n/** The objects whose records hold the field's label, in the definition's order. */\nfunction targetObjects(definition: CrmPicklistDefinition): CrmPicklistObject[] {\n return [...new Set(definition.targets.map((target) => target.object))]\n}\n\n/** The add and rename dialog's state: which value, if any, is being renamed. */\ntype NameDialog = { mode: 'add' } | { mode: 'rename'; value: CrmPicklistValue } | null\n\n/**\n * ONE STANDARD PICKLIST'S VALUES (AGL-3298, AGL-3510) — Salesforce's\n * picklist value set for a standard field, managed on the Fields tab of the\n * object the field belongs to.\n *\n * Standard values are marked, and cannot be deleted — every org has them —\n * but rename, reorder, regroup, deactivate and default are theirs as much\n * as an added value's. Every move that touches only the LIST — add,\n * reorder by drag or arrows, sort A–Z, activate, deactivate, group,\n * default — is one client write of the whole document, guarded against a\n * cached read like every seeded save on the Fields page. The two that\n * change RECORDS — rename, and delete with a replacement — go to\n * `crm/picklist-values`, which rewrites the list and every record the\n * definition's targets name together; see that route.\n *\n * An org that has never edited the list reads the standard values, and the\n * first move here writes them down as the org's own.\n */\nexport function PicklistValuesCard(props: PicklistValuesCardProps) {\n const { picklistId, hostId, orgId, createHostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { definition, picklist, stored, ready, fromCache } = useCrmPicklist(picklistId, orgId)\n const [busy, setBusy] = useState(false)\n const [nameDialog, setNameDialog] = useState<NameDialog>(null)\n const [draftName, setDraftName] = useState('')\n const [draftGroup, setDraftGroup] = useState('')\n const [draftMeaning, setDraftMeaning] = useState('')\n const [nameError, setNameError] = useState('')\n const [deleting, setDeleting] = useState<CrmPicklistValue | null>(null)\n const [replaceWith, setReplaceWith] = useState('')\n const [dragFrom, setDragFrom] = useState<number | null>(null)\n\n const groups = definition.groups ?? []\n const meanings = definition.meanings ?? []\n // An added value may not take a meaning only the platform sets (AGL-3512).\n const addableMeanings = picklistAddableMeanings(definition)\n const [noun, nouns] = OBJECT_NOUNS[definition.object]\n const holders = targetObjects(definition).map((object) => OBJECT_NOUNS[object])\n // A list of suggestions only — a task's subjects (AGL-3517) — that no record holds.\n const suggestions = holders.length === 0\n const field = definition.label.toLowerCase()\n const failed = `The ${definition.plural} could not be saved.`\n const isStandard = (value: CrmPicklistValue) => isStandardPicklistValueId(definition, value.id)\n\n /** Write the whole list — the list-only moves above. */\n const save = useCallback(\n async (next: CrmPicklist, done: string) => {\n if (!orgId || busy) return\n setBusy(true)\n try {\n const verdict = await writeGuardedBySeed({ subject: `${field} list`, fromCache }, () =>\n setDoc(\n doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.picklists, picklistId),\n {\n values: next.values,\n defaultValueId: next.defaultValueId,\n updatedAt: serverTimestamp(),\n // The org-wide stamp the rules require of a new CRM document,\n // the same one a field definition carries.\n ...(stored\n ? {}\n : {\n hostId: createHostId,\n createdAt: serverTimestamp(),\n ...newResourceScopeFields([ORG_SCOPE_TOKEN]),\n }),\n },\n { merge: true },\n ),\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar(failed, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(false)\n }\n },\n [orgId, busy, field, fromCache, firestore, picklistId, stored, createHostId, enqueueSnackbar, failed],\n )\n\n /** A rename or a delete — the moves that change records too. */\n const post = useCallback(\n async (request: PicklistValuesAction): Promise<PicklistValuesResponse | null> => {\n const scope = crmTaskCallScope(hostId, orgId)\n if (!scope || busy) return null\n setBusy(true)\n try {\n const response = await authorizedFetch(user, crmPicklistValuesRouteUrl(), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...request, picklistId, ...scope } satisfies PicklistValuesRequest),\n })\n const payload = (await response.json().catch(() => ({}))) as { error?: string }\n if (!response.ok) {\n enqueueSnackbar(payload.error || failed, { variant: 'warning', persist: false })\n return null\n }\n return payload as PicklistValuesResponse\n } catch (error) {\n console.error(error)\n enqueueSnackbar(failed, { variant: 'error', allowDuplicate: true })\n return null\n } finally {\n setBusy(false)\n }\n },\n [hostId, orgId, busy, user, picklistId, enqueueSnackbar, failed],\n )\n\n /** \" — 2 leads and 1 contact updated\", or nothing when no record held the value. */\n const moved = (result: PicklistValuesResponse) => {\n const counts = targetObjects(definition).map((object) => {\n const count = result.updated?.[object] ?? 0\n const [one, many] = OBJECT_NOUNS[object]\n return { count, text: `${count.toLocaleString()} ${count === 1 ? one : many}` }\n })\n return counts.some((entry) => entry.count)\n ? ` — ${sentenceList(counts.map((entry) => entry.text))} updated`\n : ''\n }\n\n const openAdd = () => {\n setDraftName('')\n setDraftGroup('')\n setDraftMeaning('')\n setNameError('')\n setNameDialog({ mode: 'add' })\n }\n const openRename = (value: CrmPicklistValue) => {\n setDraftName(value.label)\n setNameError('')\n setNameDialog({ mode: 'rename', value })\n }\n\n const submitName = async () => {\n if (!nameDialog) return\n if (nameDialog.mode === 'add') {\n const move = addPicklistValue(definition, picklist, draftName, {\n group: draftGroup || null,\n meaning: draftMeaning || null,\n })\n if (move.ok === false) return void setNameError(move.error)\n setNameDialog(null)\n await save(move.picklist, `“${draftName.trim()}” added`)\n return\n }\n const result = await post({ action: 'rename', valueId: nameDialog.value.id, label: draftName })\n if (!result) return\n setNameDialog(null)\n enqueueSnackbar(`Renamed${moved(result)}`, { variant: 'success', persist: false })\n }\n\n const openDelete = (value: CrmPicklistValue) => {\n setReplaceWith('')\n setDeleting(value)\n }\n const submitDelete = async () => {\n if (!deleting) return\n const result = await post({\n action: 'delete',\n valueId: deleting.id,\n replaceWith: replaceWith || null,\n })\n if (!result) return\n setDeleting(null)\n enqueueSnackbar(`“${deleting.label}” deleted${moved(result)}`, {\n variant: 'success',\n persist: false,\n })\n }\n\n const move = (from: number, to: number) =>\n void save(movePicklistValue(picklist, from, to), 'Order saved')\n\n const rowActions = (value: CrmPicklistValue): RowActionsMenuItem[] => {\n const isDefault = picklist.defaultValueId === value.id\n const items: RowActionsMenuItem[] = [\n {\n key: 'rename',\n label: 'Rename',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n disabled: busy,\n onClick: () => openRename(value),\n },\n {\n key: 'default',\n label: isDefault ? 'Clear default' : 'Make default',\n icon: <MdiIcon path={mdiStarOutline.path} size={0.8} />,\n disabled: busy || (!isDefault && !value.active),\n disabledReason: !value.active ? 'An inactive value cannot be the default' : undefined,\n onClick: () =>\n void save(\n setPicklistDefault(picklist, isDefault ? null : value.id),\n isDefault ? 'Default cleared' : `New ${nouns} start as “${value.label}”`,\n ),\n },\n {\n key: 'active',\n label: value.active ? 'Deactivate' : 'Activate',\n icon: (\n <MdiIcon path={value.active ? mdiEyeOffOutline.path : mdiEyeOutline.path} size={0.8} />\n ),\n disabled: busy,\n onClick: () =>\n void save(\n setPicklistValueActive(picklist, value.id, !value.active),\n value.active ? `“${value.label}” deactivated` : `“${value.label}” activated`,\n ),\n },\n ]\n // A standard value is every organization's: it is deactivated, never deleted.\n if (!isStandard(value)) {\n items.push({\n key: 'delete',\n label: 'Delete…',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: busy,\n onClick: () => openDelete(value),\n })\n }\n return items\n }\n\n const replacements = deleting ? picklistReplacements(definition, picklist, deleting.id) : []\n const columns = 4 + (groups.length ? 1 : 0) + (meanings.length ? 1 : 0)\n const heldBy = sentenceList(holders.map(([, many]) => many)).replace(/^./, (letter) =>\n letter.toUpperCase(),\n )\n\n return (\n <Stack spacing={1.5}>\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ justifyContent: 'space-between', alignItems: { sm: 'center' } }}\n >\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle1\">{`${definition.label} values`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {(suggestions\n ? `The suggestions every ${noun}’s ${definition.label} offers, in this order. ` +\n 'Any text may still be typed, and renaming or deleting a suggestion ' +\n `changes no ${noun}. `\n : `The choices in every ${noun}’s ${definition.label} select, in this order. ` +\n (definition.restricted\n ? 'An import or an API write naming anything else is refused. '\n : '') +\n 'A deactivated value stays on the records that hold it. ') +\n 'Standard values come with every organization and cannot be deleted; ' +\n 'add your own beside them.'}\n </Typography>\n </Stack>\n {orgId ? (\n <Stack direction=\"row\" spacing={1} sx={{ flexShrink: 0 }}>\n <Button\n size=\"small\"\n disabled={busy || picklist.values.length < 2}\n onClick={() => void save(sortPicklistValues(picklist), 'Sorted A–Z')}\n >\n {'Sort A–Z'}\n </Button>\n <Button size=\"small\" variant=\"outlined\" disabled={busy} onClick={openAdd}>\n {'Add value'}\n </Button>\n </Stack>\n ) : null}\n </Stack>\n {!ready ? null : (\n <ScrollTable size=\"small\" aria-label={`${definition.label} values`}>\n <TableHead>\n <TableRow>\n <TableCell sx={{ width: 120 }}>{'Order'}</TableCell>\n <TableCell>{'Value'}</TableCell>\n {groups.length ? <TableCell>{'Group'}</TableCell> : null}\n {meanings.length ? <TableCell>{'Means'}</TableCell> : null}\n <TableCell>{'Status'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {picklist.values.length === 0 ? (\n <TableRow>\n <TableCell colSpan={columns}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`No values yet. Add one, and every ${noun} can be given it.`}\n </Typography>\n </TableCell>\n </TableRow>\n ) : null}\n {picklist.values.map((value, index) => (\n <TableRow\n key={value.id}\n hover\n // Native drag and drop for a pointer; the arrows beside the\n // handle are the same move for a keyboard or a touch screen.\n draggable={!busy}\n onDragStart={(event) => {\n setDragFrom(index)\n event.dataTransfer.effectAllowed = 'move'\n }}\n onDragOver={(event) => {\n if (dragFrom === null) return\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n }}\n onDrop={(event) => {\n event.preventDefault()\n if (dragFrom !== null && dragFrom !== index) move(dragFrom, index)\n setDragFrom(null)\n }}\n onDragEnd={() => setDragFrom(null)}\n sx={{\n ...(value.active ? {} : { opacity: 0.6 }),\n ...(dragFrom === index ? { outline: 1, outlineColor: 'primary.main' } : {}),\n }}\n >\n <TableCell>\n <Stack direction=\"row\" spacing={0} sx={{ alignItems: 'center' }}>\n <MdiIcon\n path={mdiDragVertical.path}\n size={0.8}\n style={{ cursor: busy ? 'default' : 'grab' }}\n aria-hidden\n />\n <IconButton\n size=\"small\"\n disabled={index === 0 || busy}\n onClick={() => move(index, index - 1)}\n >\n <MdiIcon path={mdiArrowUp.path} size={0.7} />\n <SrOnly>{`Move ${value.label} up`}</SrOnly>\n </IconButton>\n <IconButton\n size=\"small\"\n disabled={index === picklist.values.length - 1 || busy}\n onClick={() => move(index, index + 1)}\n >\n <MdiIcon path={mdiArrowDown.path} size={0.7} />\n <SrOnly>{`Move ${value.label} down`}</SrOnly>\n </IconButton>\n </Stack>\n </TableCell>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{value.label}</Typography>\n {isStandard(value) ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Standard\" />\n ) : null}\n </Stack>\n </TableCell>\n {groups.length ? (\n <TableCell sx={{ minWidth: 150 }}>\n <TextField\n select\n size=\"small\"\n variant=\"standard\"\n value={value.group ?? ''}\n disabled={busy || !orgId}\n onChange={(event) => {\n const group = String(event.target.value) || null\n const name = groups.find((entry) => entry.id === group)?.label\n void save(\n setPicklistValueGroup(definition, picklist, value.id, group),\n name ? `“${value.label}” is ${name}` : `“${value.label}” has no group`,\n )\n }}\n fullWidth\n slotProps={{\n select: { displayEmpty: true, 'aria-label': `Group of ${value.label}` },\n }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {groups.map((group) => (\n <MenuItem key={group.id} value={group.id}>\n {group.label}\n </MenuItem>\n ))}\n </TextField>\n </TableCell>\n ) : null}\n {meanings.length ? (\n // What the value means to the CRM: fixed for a standard value, and\n // chosen when an added one is made.\n <TableCell>\n <Typography variant=\"body2\" color={value.meaning ? 'text.primary' : 'text.secondary'}>\n {value.meaning ? picklistMeaningLabel(definition, value.meaning) : '—'}\n </Typography>\n </TableCell>\n ) : null}\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', alignItems: 'center' }}>\n {value.active ? null : (\n <Chip size=\"small\" variant=\"outlined\" color=\"warning\" label=\"Inactive\" />\n )}\n {picklist.defaultValueId === value.id ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"primary\" label=\"Default\" />\n ) : null}\n {value.active && picklist.defaultValueId !== value.id ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Active'}\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 56 }}>\n <RowActionsMenu label={value.label} items={rowActions(value)} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n {ready && !stored ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'These are the standard values. Your first change here makes the list your ' +\n 'organization’s own.'}\n </Typography>\n ) : null}\n\n <Dialog open={nameDialog !== null} onClose={() => setNameDialog(null)} fullWidth maxWidth=\"xs\">\n <DialogTitle>\n {nameDialog?.mode === 'rename' ? `Rename “${nameDialog.value.label}”` : `Add a ${field}`}\n </DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n {nameDialog?.mode === 'rename' && !suggestions ? (\n <DialogContentText variant=\"body2\">\n {`Every ${sentenceList(holders.map(([one]) => one))} holding this value ` +\n 'is updated to the new name.'}\n </DialogContentText>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Value\"\n value={draftName}\n onChange={(event) => {\n setDraftName(event.target.value)\n setNameError('')\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter') void submitName()\n }}\n error={Boolean(nameError)}\n helperText={nameError || ' '}\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n autoFocus\n fullWidth\n />\n {nameDialog?.mode === 'add' && groups.length ? (\n <TextField\n select\n size=\"small\"\n label=\"Group\"\n value={draftGroup}\n onChange={(event) => setDraftGroup(String(event.target.value))}\n fullWidth\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {groups.map((group) => (\n <MenuItem key={group.id} value={group.id}>\n {group.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {nameDialog?.mode === 'add' && meanings.length ? (\n <TextField\n select\n size=\"small\"\n label=\"Means\"\n value={draftMeaning}\n onChange={(event) => {\n setDraftMeaning(String(event.target.value))\n setNameError('')\n }}\n fullWidth\n >\n {addableMeanings.map((meaning) => (\n <MenuItem key={meaning} value={meaning}>\n {picklistMeaningLabel(definition, meaning)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setNameDialog(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n disabled={busy || !draftName.trim()}\n onClick={() => void submitName()}\n >\n {nameDialog?.mode === 'rename' ? 'Rename' : 'Add'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog open={deleting !== null} onClose={() => setDeleting(null)} fullWidth maxWidth=\"xs\">\n <DialogTitle>{`Delete “${deleting?.label ?? ''}”?`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <DialogContentText variant=\"body2\">\n {suggestions\n ? `The suggestion leaves the list for good. No ${noun} changes.`\n : `The value leaves the list for good. ${heldBy} that hold it ` +\n (meanings.length\n ? 'are moved to the value you pick here, which means the same. '\n : 'are moved to the value you pick here, or cleared. ') +\n 'To keep it on those records instead, deactivate it.'}\n </DialogContentText>\n {suggestions ? null : (\n <TextField\n select\n size=\"small\"\n label=\"Replace it on existing records with\"\n value={replaceWith}\n onChange={(event) => setReplaceWith(String(event.target.value))}\n fullWidth\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n {/* A value with a meaning moves its records to another of that meaning, never off. */}\n {meanings.length ? null : <MenuItem value=\"\">{'None — clear it'}</MenuItem>}\n {replacements.map((value) => (\n <MenuItem key={value.id} value={value.label}>\n {value.label}\n </MenuItem>\n ))}\n </TextField>\n )}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDeleting(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"error\"\n disabled={busy || (meanings.length > 0 && !replaceWith)}\n onClick={() => void submitDelete()}\n >\n {'Delete value'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nPicklistValuesCard.displayName = 'PicklistValuesCard'\n\nexport default PicklistValuesCard\n"],"names":["CRM_COLLECTIONS","CRM_LEAD_TEXT_MAX","isStandardPicklistValueId","newResourceScopeFields","ORG_SCOPE_TOKEN","mdiArrowDown","mdiArrowUp","mdiDeleteOutline","mdiDragVertical","mdiEyeOffOutline","mdiEyeOutline","mdiPencilOutline","mdiStarOutline","MdiIcon","SrOnly","RowActionsMenu","ScrollTable","useSnackbar","authorizedFetch","useFirestore","useUser","writeGuardedBySeed","Button","Chip","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","IconButton","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","doc","serverTimestamp","setDoc","useCallback","useState","useCrmPicklist","addPicklistValue","crmPicklistValuesRouteUrl","movePicklistValue","picklistAddableMeanings","picklistMeaningLabel","picklistReplacements","setPicklistDefault","setPicklistValueActive","setPicklistValueGroup","sortPicklistValues","crmTaskCallScope","OBJECT_NOUNS","contact","company","deal","lead","task","sentenceList","words","length","slice","join","targetObjects","definition","Set","targets","map","target","object","PicklistValuesCard","props","picklistId","hostId","orgId","createHostId","firestore","data","user","enqueueSnackbar","picklist","stored","ready","fromCache","busy","setBusy","nameDialog","setNameDialog","draftName","setDraftName","draftGroup","setDraftGroup","draftMeaning","setDraftMeaning","nameError","setNameError","deleting","setDeleting","replaceWith","setReplaceWith","dragFrom","setDragFrom","groups","meanings","addableMeanings","noun","nouns","holders","suggestions","field","label","toLowerCase","failed","plural","isStandard","value","id","save","next","done","verdict","subject","picklists","values","defaultValueId","updatedAt","createdAt","merge","ok","message","variant","persist","error","console","allowDuplicate","post","request","scope","response","method","headers","body","JSON","stringify","payload","json","catch","moved","result","counts","count","updated","one","many","text","toLocaleString","some","entry","openAdd","mode","openRename","submitName","move","group","meaning","trim","action","valueId","openDelete","submitDelete","from","to","rowActions","isDefault","items","key","icon","path","size","disabled","onClick","active","disabledReason","undefined","push","destructive","replacements","columns","heldBy","replace","letter","toUpperCase","spacing","direction","xs","sm","sx","justifyContent","alignItems","color","restricted","flexShrink","aria-label","width","align","colSpan","index","hover","draggable","onDragStart","event","dataTransfer","effectAllowed","onDragOver","preventDefault","dropEffect","onDrop","onDragEnd","opacity","outline","outlineColor","style","cursor","aria-hidden","minWidth","select","onChange","String","name","find","fullWidth","slotProps","displayEmpty","flexWrap","open","onClose","maxWidth","pt","onKeyDown","Boolean","helperText","htmlInput","maxLength","autoFocus","inputLabel","shrink","displayName"],"mappings":"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"}
|
|
@@ -35,6 +35,13 @@ export interface PipelineStagesDialogProps {
|
|
|
35
35
|
*
|
|
36
36
|
* Won and Lost cannot be removed or reordered; their probabilities are
|
|
37
37
|
* fixed by what they mean.
|
|
38
|
+
*
|
|
39
|
+
* ## The forecast category (AGL-3516)
|
|
40
|
+
*
|
|
41
|
+
* Each open stage names the forecast category a deal takes on landing in
|
|
42
|
+
* it — Salesforce's fixed set, Pipeline unless chosen. Won is Closed and
|
|
43
|
+
* Lost is Omitted by definition. A change here applies to the deals that
|
|
44
|
+
* move into the stage from now on; a deal already in it keeps its own.
|
|
38
45
|
*/
|
|
39
46
|
export declare function PipelineStagesDialog(props: PipelineStagesDialogProps): import("react").JSX.Element;
|
|
40
47
|
export declare namespace PipelineStagesDialog {
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
-
import { CRM_COLLECTIONS } from "@aglyn/aglyn";
|
|
18
|
+
import { CRM_COLLECTIONS, CRM_FORECAST_CATEGORIES, CRM_FORECAST_CATEGORY_LABELS, dealStageForecastCategory } from "@aglyn/aglyn";
|
|
19
19
|
import { mdiArrowDown, mdiArrowUp, mdiDeleteOutline } from "@aglyn/shared-data-mdi";
|
|
20
20
|
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
21
21
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
22
22
|
import { useFirestore, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
23
|
-
import { Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Stack, TextField, Tooltip, Typography } from "@mui/material";
|
|
23
|
+
import { Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, MenuItem, Stack, TextField, Tooltip, Typography } from "@mui/material";
|
|
24
24
|
import { collection, doc, getCountFromServer, query, updateDoc, where } from "firebase/firestore";
|
|
25
25
|
import { useCallback, useEffect, useState } from "react";
|
|
26
26
|
import { crmVisibleToClause } from "../hooks/use-crm-scope.js";
|
|
27
|
-
import { addStage, moveStage, removeStage, renameStage, setStageProbability, sortedStages, stageRemovalRefusal, stagesProblem } from "../model/deal-board-model.js";
|
|
27
|
+
import { addStage, moveStage, removeStage, renameStage, setStageForecastCategory, setStageProbability, sortedStages, stageRemovalRefusal, stagesProblem } from "../model/deal-board-model.js";
|
|
28
28
|
/**
|
|
29
29
|
* The pipeline's stages, edited as one draft and saved as one write.
|
|
30
30
|
*
|
|
@@ -49,6 +49,13 @@ import { addStage, moveStage, removeStage, renameStage, setStageProbability, sor
|
|
|
49
49
|
*
|
|
50
50
|
* Won and Lost cannot be removed or reordered; their probabilities are
|
|
51
51
|
* fixed by what they mean.
|
|
52
|
+
*
|
|
53
|
+
* ## The forecast category (AGL-3516)
|
|
54
|
+
*
|
|
55
|
+
* Each open stage names the forecast category a deal takes on landing in
|
|
56
|
+
* it — Salesforce's fixed set, Pipeline unless chosen. Won is Closed and
|
|
57
|
+
* Lost is Omitted by definition. A change here applies to the deals that
|
|
58
|
+
* move into the stage from now on; a deal already in it keeps its own.
|
|
52
59
|
*/ export function PipelineStagesDialog(props) {
|
|
53
60
|
const { open, onClose, orgId, pipeline, fromCache, unreadable, visibleToTokens } = props;
|
|
54
61
|
const firestore = useFirestore();
|
|
@@ -165,7 +172,7 @@ import { addStage, moveStage, removeStage, renameStage, setStageProbability, sor
|
|
|
165
172
|
/*#__PURE__*/ _jsx(Typography, {
|
|
166
173
|
variant: "body2",
|
|
167
174
|
color: "text.secondary",
|
|
168
|
-
children: 'The probability is the chance a deal in that stage closes, which ' + 'is what the weighted value multiplies by. Won is always 100% and ' + 'Lost always 0%.'
|
|
175
|
+
children: 'The probability is the chance a deal in that stage closes, which ' + 'is what the weighted value multiplies by. Won is always 100% and ' + 'Lost always 0%. The forecast category is where a deal moved into ' + 'the stage is forecast; Won is always Closed and Lost always Omitted.'
|
|
169
176
|
}),
|
|
170
177
|
stages.map((stage, index)=>{
|
|
171
178
|
const isOpen = stage.kind === 'open';
|
|
@@ -213,6 +220,21 @@ import { addStage, moveStage, removeStage, renameStage, setStageProbability, sor
|
|
|
213
220
|
}
|
|
214
221
|
}
|
|
215
222
|
}),
|
|
223
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
224
|
+
select: true,
|
|
225
|
+
size: "small",
|
|
226
|
+
label: "Forecast",
|
|
227
|
+
value: dealStageForecastCategory(stage),
|
|
228
|
+
disabled: !isOpen,
|
|
229
|
+
onChange: (event)=>setStages((current)=>setStageForecastCategory(current, stage.id, event.target.value)),
|
|
230
|
+
sx: {
|
|
231
|
+
width: 140
|
|
232
|
+
},
|
|
233
|
+
children: CRM_FORECAST_CATEGORIES.map((category)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
234
|
+
value: category,
|
|
235
|
+
children: CRM_FORECAST_CATEGORY_LABELS[category]
|
|
236
|
+
}, category))
|
|
237
|
+
}),
|
|
216
238
|
isOpen ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
217
239
|
children: [
|
|
218
240
|
/*#__PURE__*/ _jsx(Tooltip, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/pipeline-stages-dialog.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, type CrmDealStage } from '@aglyn/aglyn'\nimport {\n mdiArrowDown,\n mdiArrowUp,\n mdiDeleteOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, writeGuardedBySeed } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n doc,\n getCountFromServer,\n query,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { crmVisibleToClause } from '../hooks/use-crm-scope'\nimport {\n addStage,\n moveStage,\n type PipelineDoc,\n removeStage,\n renameStage,\n setStageProbability,\n sortedStages,\n stageRemovalRefusal,\n stagesProblem,\n} from '../model/deal-board-model'\n\nexport interface PipelineStagesDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n pipeline: PipelineDoc | null\n /** The listener's verdict on the pipeline row the draft is seeded from. */\n fromCache: boolean\n unreadable: boolean\n /** The scope tokens the deal count is filtered by — the viewer's own. */\n /** The reader's tokens, or `null` at the organization level — no clause (AGL-2630). */\n visibleToTokens: readonly string[] | null\n}\n\n/**\n * The pipeline's stages, edited as one draft and saved as one write.\n *\n * ## One document, one save\n *\n * The stages live as an array on the pipeline document, so every edit here\n * is a local change to a copy and the Save button writes the whole array\n * back. A per-row write would be a pipeline that is half renamed when the\n * network drops, and — because the board draws columns from this array —\n * a board that redraws between every keystroke for everybody looking at it.\n *\n * ## A stage with deals in it stays\n *\n * Removing a stage is the one edit that can strand data: every deal in it\n * would point at a stage id the pipeline no longer has, sit in no column,\n * and be worth nothing to the forecast. So the remove button first asks the\n * server how many deals are in the stage — an aggregate count over the same\n * scoped query the board reads — and refuses with that number when it is\n * not zero. The count is taken at the moment of the click rather than held\n * from when the dialog opened, because a deal can be dragged in from the\n * board in another tab meanwhile.\n *\n * Won and Lost cannot be removed or reordered; their probabilities are\n * fixed by what they mean.\n */\nexport function PipelineStagesDialog(props: PipelineStagesDialogProps) {\n const { open, onClose, orgId, pipeline, fromCache, unreadable, visibleToTokens } =\n props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const [stages, setStages] = useState<CrmDealStage[]>([])\n const [newName, setNewName] = useState('')\n const [busy, setBusy] = useState(false)\n const [counting, setCounting] = useState<string | null>(null)\n\n // Re-seeded on every open, so the draft starts from the stored stages\n // rather than from whatever the last session left unsaved.\n useEffect(() => {\n if (!open) return\n setStages(sortedStages(pipeline))\n setNewName('')\n }, [open, pipeline])\n\n const handleRemove = useCallback(\n async (stageId: string) => {\n if (!pipeline) return\n setCounting(stageId)\n try {\n const snapshot = await getCountFromServer(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(visibleToTokens),\n where('pipelineId', '==', pipeline.$id),\n where('stageId', '==', stageId),\n ),\n )\n const refusal = stageRemovalRefusal(stages, stageId, snapshot.data().count)\n if (refusal) {\n enqueueSnackbar(refusal, { variant: 'warning', persist: false })\n return\n }\n setStages((current) => removeStage(current, stageId))\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The deals in this stage could not be counted, so it stays.', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setCounting(null)\n }\n },\n [firestore, orgId, pipeline, visibleToTokens, stages, enqueueSnackbar],\n )\n\n const problem = stagesProblem(stages)\n\n const handleSave = useCallback(async () => {\n if (!pipeline || problem) return\n setBusy(true)\n try {\n const verdict = await writeGuardedBySeed(\n { subject: 'pipeline', unreadable, fromCache },\n async () => {\n await updateDoc(\n doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.pipelines, pipeline.$id),\n { stages, updatedAt: new Date() },\n )\n },\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return\n }\n enqueueSnackbar('Stages saved', { variant: 'success', persist: false })\n onClose()\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [firestore, orgId, pipeline, problem, stages, unreadable, fromCache, enqueueSnackbar, onClose])\n\n const openCount = stages.filter((stage) => stage.kind === 'open').length\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{pipeline ? `${pipeline.name} stages` : 'Stages'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The probability is the chance a deal in that stage closes, which ' +\n 'is what the weighted value multiplies by. Won is always 100% and ' +\n 'Lost always 0%.'}\n </Typography>\n {stages.map((stage, index) => {\n const isOpen = stage.kind === 'open'\n const first = index === 0\n const lastOpen = index === openCount - 1\n return (\n <Stack\n key={stage.id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <TextField\n size=\"small\"\n label=\"Stage\"\n value={stage.name}\n onChange={(event) =>\n setStages((current) =>\n renameStage(current, stage.id, event.target.value),\n )\n }\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: 60 } }}\n />\n <TextField\n size=\"small\"\n label=\"Probability\"\n type=\"number\"\n value={stage.probability}\n disabled={!isOpen}\n onChange={(event) =>\n setStages((current) =>\n setStageProbability(current, stage.id, event.target.value),\n )\n }\n sx={{ width: 120 }}\n slotProps={{\n htmlInput: { min: 0, max: 100, step: 5 },\n input: { endAdornment: '%' },\n }}\n />\n {isOpen ? (\n <>\n <Tooltip title=\"Move up\">\n <span>\n <IconButton\n size=\"small\"\n disabled={first}\n onClick={() =>\n setStages((current) => moveStage(current, stage.id, 'up'))\n }\n >\n <MdiIcon path={mdiArrowUp.path} size={0.8} />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Move down\">\n <span>\n <IconButton\n size=\"small\"\n disabled={lastOpen}\n onClick={() =>\n setStages((current) => moveStage(current, stage.id, 'down'))\n }\n >\n <MdiIcon path={mdiArrowDown.path} size={0.8} />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Remove stage\">\n <span>\n <IconButton\n size=\"small\"\n color=\"error\"\n disabled={counting === stage.id || busy}\n onClick={() => void handleRemove(stage.id)}\n >\n <MdiIcon path={mdiDeleteOutline.path} size={0.8} />\n </IconButton>\n </span>\n </Tooltip>\n </>\n ) : (\n <Chip\n size=\"small\"\n label={stage.kind === 'won' ? 'Won' : 'Lost'}\n color={stage.kind === 'won' ? 'success' : 'default'}\n sx={{ minWidth: 120 }}\n />\n )}\n </Stack>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TextField\n size=\"small\"\n label=\"New stage\"\n value={newName}\n onChange={(event) => setNewName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' || !newName.trim()) return\n event.preventDefault()\n setStages((current) => addStage(current, newName))\n setNewName('')\n }}\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: 60 } }}\n />\n <Button\n disabled={!newName.trim()}\n onClick={() => {\n setStages((current) => addStage(current, newName))\n setNewName('')\n }}\n >\n {'Add stage'}\n </Button>\n </Stack>\n {problem ? (\n <Typography variant=\"caption\" color=\"error\">\n {problem}\n </Typography>\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={busy || !pipeline || Boolean(problem)}\n onClick={() => void handleSave()}\n >\n {'Save stages'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPipelineStagesDialog.displayName = 'PipelineStagesDialog'\n\nexport default PipelineStagesDialog\n"],"names":["CRM_COLLECTIONS","mdiArrowDown","mdiArrowUp","mdiDeleteOutline","MdiIcon","useSnackbar","useFirestore","writeGuardedBySeed","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","Stack","TextField","Tooltip","Typography","collection","doc","getCountFromServer","query","updateDoc","where","useCallback","useEffect","useState","crmVisibleToClause","addStage","moveStage","removeStage","renameStage","setStageProbability","sortedStages","stageRemovalRefusal","stagesProblem","PipelineStagesDialog","props","open","onClose","orgId","pipeline","fromCache","unreadable","visibleToTokens","firestore","enqueueSnackbar","stages","setStages","newName","setNewName","busy","setBusy","counting","setCounting","handleRemove","stageId","snapshot","deals","$id","refusal","data","count","variant","persist","current","error","console","allowDuplicate","problem","handleSave","verdict","subject","pipelines","updatedAt","Date","ok","message","openCount","filter","stage","kind","length","undefined","fullWidth","maxWidth","name","spacing","sx","pt","color","map","index","isOpen","first","lastOpen","direction","alignItems","size","label","value","onChange","event","id","target","flex","slotProps","htmlInput","maxLength","type","probability","disabled","width","min","max","step","input","endAdornment","title","span","onClick","path","minWidth","onKeyDown","key","trim","preventDefault","Boolean","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/pipeline-stages-dialog.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 CRM_FORECAST_CATEGORIES,\n CRM_FORECAST_CATEGORY_LABELS,\n type CrmDealStage,\n dealStageForecastCategory,\n} from '@aglyn/aglyn'\nimport {\n mdiArrowDown,\n mdiArrowUp,\n mdiDeleteOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, writeGuardedBySeed } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n doc,\n getCountFromServer,\n query,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { crmVisibleToClause } from '../hooks/use-crm-scope'\nimport {\n addStage,\n moveStage,\n type PipelineDoc,\n removeStage,\n renameStage,\n setStageForecastCategory,\n setStageProbability,\n sortedStages,\n stageRemovalRefusal,\n stagesProblem,\n} from '../model/deal-board-model'\n\nexport interface PipelineStagesDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n pipeline: PipelineDoc | null\n /** The listener's verdict on the pipeline row the draft is seeded from. */\n fromCache: boolean\n unreadable: boolean\n /** The scope tokens the deal count is filtered by — the viewer's own. */\n /** The reader's tokens, or `null` at the organization level — no clause (AGL-2630). */\n visibleToTokens: readonly string[] | null\n}\n\n/**\n * The pipeline's stages, edited as one draft and saved as one write.\n *\n * ## One document, one save\n *\n * The stages live as an array on the pipeline document, so every edit here\n * is a local change to a copy and the Save button writes the whole array\n * back. A per-row write would be a pipeline that is half renamed when the\n * network drops, and — because the board draws columns from this array —\n * a board that redraws between every keystroke for everybody looking at it.\n *\n * ## A stage with deals in it stays\n *\n * Removing a stage is the one edit that can strand data: every deal in it\n * would point at a stage id the pipeline no longer has, sit in no column,\n * and be worth nothing to the forecast. So the remove button first asks the\n * server how many deals are in the stage — an aggregate count over the same\n * scoped query the board reads — and refuses with that number when it is\n * not zero. The count is taken at the moment of the click rather than held\n * from when the dialog opened, because a deal can be dragged in from the\n * board in another tab meanwhile.\n *\n * Won and Lost cannot be removed or reordered; their probabilities are\n * fixed by what they mean.\n *\n * ## The forecast category (AGL-3516)\n *\n * Each open stage names the forecast category a deal takes on landing in\n * it — Salesforce's fixed set, Pipeline unless chosen. Won is Closed and\n * Lost is Omitted by definition. A change here applies to the deals that\n * move into the stage from now on; a deal already in it keeps its own.\n */\nexport function PipelineStagesDialog(props: PipelineStagesDialogProps) {\n const { open, onClose, orgId, pipeline, fromCache, unreadable, visibleToTokens } =\n props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const [stages, setStages] = useState<CrmDealStage[]>([])\n const [newName, setNewName] = useState('')\n const [busy, setBusy] = useState(false)\n const [counting, setCounting] = useState<string | null>(null)\n\n // Re-seeded on every open, so the draft starts from the stored stages\n // rather than from whatever the last session left unsaved.\n useEffect(() => {\n if (!open) return\n setStages(sortedStages(pipeline))\n setNewName('')\n }, [open, pipeline])\n\n const handleRemove = useCallback(\n async (stageId: string) => {\n if (!pipeline) return\n setCounting(stageId)\n try {\n const snapshot = await getCountFromServer(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(visibleToTokens),\n where('pipelineId', '==', pipeline.$id),\n where('stageId', '==', stageId),\n ),\n )\n const refusal = stageRemovalRefusal(stages, stageId, snapshot.data().count)\n if (refusal) {\n enqueueSnackbar(refusal, { variant: 'warning', persist: false })\n return\n }\n setStages((current) => removeStage(current, stageId))\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The deals in this stage could not be counted, so it stays.', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setCounting(null)\n }\n },\n [firestore, orgId, pipeline, visibleToTokens, stages, enqueueSnackbar],\n )\n\n const problem = stagesProblem(stages)\n\n const handleSave = useCallback(async () => {\n if (!pipeline || problem) return\n setBusy(true)\n try {\n const verdict = await writeGuardedBySeed(\n { subject: 'pipeline', unreadable, fromCache },\n async () => {\n await updateDoc(\n doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.pipelines, pipeline.$id),\n { stages, updatedAt: new Date() },\n )\n },\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return\n }\n enqueueSnackbar('Stages saved', { variant: 'success', persist: false })\n onClose()\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [firestore, orgId, pipeline, problem, stages, unreadable, fromCache, enqueueSnackbar, onClose])\n\n const openCount = stages.filter((stage) => stage.kind === 'open').length\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{pipeline ? `${pipeline.name} stages` : 'Stages'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The probability is the chance a deal in that stage closes, which ' +\n 'is what the weighted value multiplies by. Won is always 100% and ' +\n 'Lost always 0%. The forecast category is where a deal moved into ' +\n 'the stage is forecast; Won is always Closed and Lost always Omitted.'}\n </Typography>\n {stages.map((stage, index) => {\n const isOpen = stage.kind === 'open'\n const first = index === 0\n const lastOpen = index === openCount - 1\n return (\n <Stack\n key={stage.id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <TextField\n size=\"small\"\n label=\"Stage\"\n value={stage.name}\n onChange={(event) =>\n setStages((current) =>\n renameStage(current, stage.id, event.target.value),\n )\n }\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: 60 } }}\n />\n <TextField\n size=\"small\"\n label=\"Probability\"\n type=\"number\"\n value={stage.probability}\n disabled={!isOpen}\n onChange={(event) =>\n setStages((current) =>\n setStageProbability(current, stage.id, event.target.value),\n )\n }\n sx={{ width: 120 }}\n slotProps={{\n htmlInput: { min: 0, max: 100, step: 5 },\n input: { endAdornment: '%' },\n }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Forecast\"\n value={dealStageForecastCategory(stage)}\n disabled={!isOpen}\n onChange={(event) =>\n setStages((current) =>\n setStageForecastCategory(current, stage.id, event.target.value),\n )\n }\n sx={{ width: 140 }}\n >\n {CRM_FORECAST_CATEGORIES.map((category) => (\n <MenuItem key={category} value={category}>\n {CRM_FORECAST_CATEGORY_LABELS[category]}\n </MenuItem>\n ))}\n </TextField>\n {isOpen ? (\n <>\n <Tooltip title=\"Move up\">\n <span>\n <IconButton\n size=\"small\"\n disabled={first}\n onClick={() =>\n setStages((current) => moveStage(current, stage.id, 'up'))\n }\n >\n <MdiIcon path={mdiArrowUp.path} size={0.8} />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Move down\">\n <span>\n <IconButton\n size=\"small\"\n disabled={lastOpen}\n onClick={() =>\n setStages((current) => moveStage(current, stage.id, 'down'))\n }\n >\n <MdiIcon path={mdiArrowDown.path} size={0.8} />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Remove stage\">\n <span>\n <IconButton\n size=\"small\"\n color=\"error\"\n disabled={counting === stage.id || busy}\n onClick={() => void handleRemove(stage.id)}\n >\n <MdiIcon path={mdiDeleteOutline.path} size={0.8} />\n </IconButton>\n </span>\n </Tooltip>\n </>\n ) : (\n <Chip\n size=\"small\"\n label={stage.kind === 'won' ? 'Won' : 'Lost'}\n color={stage.kind === 'won' ? 'success' : 'default'}\n sx={{ minWidth: 120 }}\n />\n )}\n </Stack>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TextField\n size=\"small\"\n label=\"New stage\"\n value={newName}\n onChange={(event) => setNewName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' || !newName.trim()) return\n event.preventDefault()\n setStages((current) => addStage(current, newName))\n setNewName('')\n }}\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: 60 } }}\n />\n <Button\n disabled={!newName.trim()}\n onClick={() => {\n setStages((current) => addStage(current, newName))\n setNewName('')\n }}\n >\n {'Add stage'}\n </Button>\n </Stack>\n {problem ? (\n <Typography variant=\"caption\" color=\"error\">\n {problem}\n </Typography>\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={busy || !pipeline || Boolean(problem)}\n onClick={() => void handleSave()}\n >\n {'Save stages'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPipelineStagesDialog.displayName = 'PipelineStagesDialog'\n\nexport default PipelineStagesDialog\n"],"names":["CRM_COLLECTIONS","CRM_FORECAST_CATEGORIES","CRM_FORECAST_CATEGORY_LABELS","dealStageForecastCategory","mdiArrowDown","mdiArrowUp","mdiDeleteOutline","MdiIcon","useSnackbar","useFirestore","writeGuardedBySeed","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","MenuItem","Stack","TextField","Tooltip","Typography","collection","doc","getCountFromServer","query","updateDoc","where","useCallback","useEffect","useState","crmVisibleToClause","addStage","moveStage","removeStage","renameStage","setStageForecastCategory","setStageProbability","sortedStages","stageRemovalRefusal","stagesProblem","PipelineStagesDialog","props","open","onClose","orgId","pipeline","fromCache","unreadable","visibleToTokens","firestore","enqueueSnackbar","stages","setStages","newName","setNewName","busy","setBusy","counting","setCounting","handleRemove","stageId","snapshot","deals","$id","refusal","data","count","variant","persist","current","error","console","allowDuplicate","problem","handleSave","verdict","subject","pipelines","updatedAt","Date","ok","message","openCount","filter","stage","kind","length","undefined","fullWidth","maxWidth","name","spacing","sx","pt","color","map","index","isOpen","first","lastOpen","direction","alignItems","size","label","value","onChange","event","id","target","flex","slotProps","htmlInput","maxLength","type","probability","disabled","width","min","max","step","input","endAdornment","select","category","title","span","onClick","path","minWidth","onKeyDown","key","trim","preventDefault","Boolean","displayName"],"mappings":"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"}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
import { CrmExportButton } from "./crm-transfer-buttons.js";
|
|
19
|
+
import { CRM_PIPELINES_RESOURCE } from "../transfer/fields.js";
|
|
18
20
|
import { CRM_COLLECTIONS, isPipelineArchived } from "@aglyn/aglyn";
|
|
19
21
|
import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline, mdiFormatListBulleted, mdiPencilOutline, mdiStarOutline } from "@aglyn/shared-data-mdi";
|
|
20
22
|
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
@@ -490,12 +492,19 @@ import { PipelineStagesDialog } from "./pipeline-stages-dialog.js";
|
|
|
490
492
|
]
|
|
491
493
|
})
|
|
492
494
|
}),
|
|
493
|
-
/*#__PURE__*/
|
|
494
|
-
children:
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
495
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
496
|
+
children: [
|
|
497
|
+
/*#__PURE__*/ _jsx(CrmExportButton, {
|
|
498
|
+
resource: CRM_PIPELINES_RESOURCE,
|
|
499
|
+
hostId: hostId,
|
|
500
|
+
label: "Export pipelines…"
|
|
501
|
+
}),
|
|
502
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
503
|
+
onClick: onClose,
|
|
504
|
+
disabled: busy !== null,
|
|
505
|
+
children: 'Done'
|
|
506
|
+
})
|
|
507
|
+
]
|
|
499
508
|
})
|
|
500
509
|
]
|
|
501
510
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/pipelines-dialog.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, isPipelineArchived } from '@aglyn/aglyn'\nimport {\n mdiArchiveArrowDownOutline,\n mdiArchiveArrowUpOutline,\n mdiFormatListBulleted,\n mdiPencilOutline,\n mdiStarOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n addDoc,\n collection,\n doc,\n getCountFromServer,\n query,\n updateDoc,\n where,\n writeBatch,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n activePipelines,\n newPipelineDocument,\n PIPELINE_NAME_MAX,\n type PipelineDoc,\n pipelineArchiveRefusal,\n pipelineNameProblem,\n sortedStages,\n} from '../model/deal-board-model'\nimport { PipelineStagesDialog } from './pipeline-stages-dialog'\n\nexport interface PipelinesDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n /**\n * The site this console is viewed from — provenance on a pipeline created\n * here — or at the organization level the site the reader picked, `null`\n * until they have (AGL-2630): a pipeline made there is the org's.\n */\n hostId: string | null\n /** Every pipeline the viewer may read, archived ones included. */\n pipelines: PipelineDoc[]\n /** The listener's verdict on the rows, for the stale-seed guard. */\n fromCache: boolean\n unreadable: boolean\n /** The scope tokens the open-deal count is filtered by — the viewer's own. */\n visibleToTokens: readonly string[]\n /** What a pipeline created here is stamped with. */\n createTokens: readonly string[]\n}\n\n/**\n * The org's pipelines (AGL-2620): list, create, rename, set as default,\n * archive, and a door into each one's stages.\n *\n * ## Org documents, scoped like every CRM row\n *\n * A pipeline lives at `orgs/{orgId}/pipelines` and is stamped with the same\n * scope tokens a contact captured on this site would carry, so an agency's\n * client sees its own pipelines and the org-wide ones — the same rule the\n * board reads by. Nothing here assumes one site: the tokens come in as\n * props from `useCrmScope`, and an org-level mount passes its own.\n *\n * ## What each verb writes\n *\n * - **New pipeline** — one `addDoc` of `newPipelineDocument`: a COPY of the\n * default stages, not the default, active. The field sits UNDER the list.\n * - **Rename** — `name` on the one document, refused when an active\n * pipeline already has the name (`pipelineNameProblem`).\n * - **Set as default** — one batch: the flag on, and off on every other\n * document that had it. A batch rather than two updates because two\n * defaults for the length of a network round trip is a board that opens\n * on either.\n * - **Archive** — `archivedAt: now`, after the server has counted the\n * pipeline's OPEN deals and `pipelineArchiveRefusal` has agreed; the\n * count is taken at the click because a deal can be created into the\n * pipeline from another tab meanwhile. The default pipeline and the last\n * active one refuse. **Restore** clears the stamp.\n * - **Edit stages** — opens the stages dialog for that pipeline, unchanged.\n */\nexport function PipelinesDialog(props: PipelinesDialogProps) {\n const {\n open,\n onClose,\n orgId,\n hostId,\n pipelines,\n fromCache,\n unreadable,\n visibleToTokens,\n createTokens,\n } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const [busy, setBusy] = useState<string | null>(null)\n const [newName, setNewName] = useState('')\n const [renaming, setRenaming] = useState<{ id: string; name: string } | null>(null)\n const [editingStages, setEditingStages] = useState<string | null>(null)\n\n useEffect(() => {\n if (!open) return\n setNewName('')\n setRenaming(null)\n setEditingStages(null)\n }, [open])\n\n const pipelineRef = useCallback(\n (id: string) => doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.pipelines, id),\n [firestore, orgId],\n )\n\n /** One guarded write, reported as a snackbar either way. */\n const run = useCallback(\n async (key: string, write: () => Promise<void>, done: string) => {\n setBusy(key)\n try {\n const verdict = await writeGuardedBySeed(\n { subject: 'pipeline', unreadable, fromCache },\n write,\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return false\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error', allowDuplicate: true })\n return false\n } finally {\n setBusy(null)\n }\n },\n [unreadable, fromCache, enqueueSnackbar],\n )\n\n const newProblem = newName.trim() ? pipelineNameProblem(newName, pipelines) : null\n const handleCreate = useCallback(async () => {\n if (!user?.uid || newProblem || !newName.trim()) return\n const ok = await run(\n 'create',\n async () => {\n await addDoc(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.pipelines),\n newPipelineDocument(newName, {\n visibleTo: createTokens,\n hostId,\n uid: user.uid,\n nowMs: Date.now(),\n }),\n )\n },\n 'Pipeline created',\n )\n if (ok) setNewName('')\n }, [user, newProblem, newName, run, firestore, orgId, createTokens, hostId])\n\n const handleRename = useCallback(async () => {\n if (!renaming) return\n const problem = pipelineNameProblem(renaming.name, pipelines, renaming.id)\n if (problem) {\n enqueueSnackbar(problem, { variant: 'warning', persist: false })\n return\n }\n const ok = await run(\n `rename:${renaming.id}`,\n async () => {\n await updateDoc(pipelineRef(renaming.id), {\n name: renaming.name.trim().slice(0, PIPELINE_NAME_MAX),\n updatedAt: new Date(),\n })\n },\n 'Pipeline renamed',\n )\n if (ok) setRenaming(null)\n }, [renaming, pipelines, enqueueSnackbar, run, pipelineRef])\n\n const handleSetDefault = useCallback(\n (pipeline: PipelineDoc) =>\n void run(\n `default:${pipeline.$id}`,\n async () => {\n const batch = writeBatch(firestore)\n const now = new Date()\n for (const entry of pipelines) {\n if (entry.$id === pipeline.$id) {\n batch.update(pipelineRef(entry.$id), { isDefault: true, updatedAt: now })\n } else if (entry.isDefault) {\n batch.update(pipelineRef(entry.$id), { isDefault: false, updatedAt: now })\n }\n }\n await batch.commit()\n },\n `${pipeline.name} is now the default pipeline`,\n ),\n [run, firestore, pipelines, pipelineRef],\n )\n\n const handleArchive = useCallback(\n async (pipeline: PipelineDoc) => {\n setBusy(`archive:${pipeline.$id}`)\n let openDeals: number\n try {\n const snapshot = await getCountFromServer(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n where('visibleTo', 'array-contains-any', [...visibleToTokens]),\n where('pipelineId', '==', pipeline.$id),\n where('status', '==', 'open'),\n ),\n )\n openDeals = snapshot.data().count\n } catch (error) {\n console.error(error)\n setBusy(null)\n enqueueSnackbar('The open deals in this pipeline could not be counted, so it stays.', {\n variant: 'error',\n allowDuplicate: true,\n })\n return\n }\n const refusal = pipelineArchiveRefusal(pipeline, pipelines, openDeals)\n if (refusal) {\n setBusy(null)\n enqueueSnackbar(refusal, { variant: 'warning', persist: false })\n return\n }\n await run(\n `archive:${pipeline.$id}`,\n async () => {\n await updateDoc(pipelineRef(pipeline.$id), {\n archivedAt: Date.now(),\n isDefault: false,\n updatedAt: new Date(),\n })\n },\n `${pipeline.name} archived`,\n )\n },\n [firestore, orgId, visibleToTokens, pipelines, run, pipelineRef, enqueueSnackbar],\n )\n\n const handleRestore = useCallback(\n (pipeline: PipelineDoc) => {\n const problem = pipelineNameProblem(pipeline.name, pipelines, pipeline.$id)\n if (problem) {\n enqueueSnackbar(`${problem} Rename it after restoring, or rename the other one first.`, {\n variant: 'warning',\n persist: false,\n })\n return\n }\n void run(\n `restore:${pipeline.$id}`,\n async () => {\n await updateDoc(pipelineRef(pipeline.$id), { archivedAt: null, updatedAt: new Date() })\n },\n `${pipeline.name} restored`,\n )\n },\n [pipelines, enqueueSnackbar, run, pipelineRef],\n )\n\n const actionsFor = (pipeline: PipelineDoc): RowActionsMenuItem[] => {\n const archived = isPipelineArchived(pipeline)\n const working = busy !== null\n return archived\n ? [\n {\n key: 'restore',\n label: 'Restore',\n icon: <MdiIcon path={mdiArchiveArrowUpOutline.path} size={0.8} />,\n disabled: working,\n onClick: () => handleRestore(pipeline),\n },\n ]\n : [\n {\n key: 'stages',\n label: 'Edit stages',\n icon: <MdiIcon path={mdiFormatListBulleted.path} size={0.8} />,\n disabled: working,\n onClick: () => setEditingStages(pipeline.$id),\n },\n {\n key: 'rename',\n label: 'Rename',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n disabled: working,\n onClick: () => setRenaming({ id: pipeline.$id, name: pipeline.name }),\n },\n {\n key: 'default',\n label: 'Set as default',\n icon: <MdiIcon path={mdiStarOutline.path} size={0.8} />,\n disabled: working || Boolean(pipeline.isDefault),\n disabledReason: pipeline.isDefault ? 'Already the default' : undefined,\n onClick: () => handleSetDefault(pipeline),\n },\n {\n key: 'archive',\n label: 'Archive',\n icon: <MdiIcon path={mdiArchiveArrowDownOutline.path} size={0.8} />,\n disabled: working,\n destructive: true,\n onClick: () => void handleArchive(pipeline),\n },\n ]\n }\n\n const active = activePipelines(pipelines)\n const archived = pipelines.filter((pipeline) => isPipelineArchived(pipeline))\n const stagesPipeline = editingStages\n ? (pipelines.find((pipeline) => pipeline.$id === editingStages) ?? null)\n : null\n\n const row = (pipeline: PipelineDoc) => {\n const stageCount = sortedStages(pipeline).filter((stage) => stage.kind === 'open').length\n const isRenaming = renaming?.id === pipeline.$id\n return (\n <Stack\n key={pipeline.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', py: 0.5 }}\n data-pipeline-id={pipeline.$id}\n >\n {isRenaming ? (\n <>\n <TextField\n size=\"small\"\n label=\"Name\"\n value={renaming.name}\n autoFocus\n onChange={(event) => setRenaming({ id: pipeline.$id, name: event.target.value })}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n void handleRename()\n }\n if (event.key === 'Escape') setRenaming(null)\n }}\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: PIPELINE_NAME_MAX } }}\n />\n <Button size=\"small\" onClick={() => setRenaming(null)} disabled={busy !== null}>\n {'Cancel'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={busy !== null || !renaming.name.trim()}\n onClick={() => void handleRename()}\n >\n {'Save'}\n </Button>\n </>\n ) : (\n <>\n <Stack sx={{ flex: 1, minWidth: 0, lineHeight: 1.25 }}>\n <Typography variant=\"body2\" noWrap>\n {pipeline.name || 'Untitled pipeline'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${stageCount} open ${stageCount === 1 ? 'stage' : 'stages'}`}\n </Typography>\n </Stack>\n {pipeline.isDefault && !isPipelineArchived(pipeline) ? (\n <Chip size=\"small\" color=\"primary\" variant=\"outlined\" label=\"Default\" />\n ) : null}\n {isPipelineArchived(pipeline) ? <Chip size=\"small\" label=\"Archived\" /> : null}\n <RowActionsMenu items={actionsFor(pipeline)} label={pipeline.name} />\n </>\n )}\n </Stack>\n )\n }\n\n return (\n <>\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{'Pipelines'}</DialogTitle>\n <DialogContent>\n <Stack spacing={1} sx={{ pt: 1 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'A pipeline is one way your business sells — its own stages, its own board. ' +\n 'New deals land in the default pipeline unless the drawer picks another.'}\n </Typography>\n {active.length ? (\n active.map(row)\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No active pipeline. Create one below.'}\n </Typography>\n )}\n {archived.length ? (\n <>\n <Divider sx={{ my: 1 }} />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Archived — kept so closed deals still show their stages'}\n </Typography>\n {archived.map(row)}\n </>\n ) : null}\n <Divider sx={{ my: 1 }} />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n size=\"small\"\n label=\"New pipeline\"\n placeholder=\"Renewals\"\n value={newName}\n onChange={(event) => setNewName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' || !newName.trim() || newProblem) return\n event.preventDefault()\n void handleCreate()\n }}\n error={Boolean(newProblem)}\n helperText={newProblem ?? 'Starts with the default stages, which you can then edit.'}\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: PIPELINE_NAME_MAX } }}\n />\n <Button\n variant=\"contained\"\n disabled={busy !== null || !newName.trim() || Boolean(newProblem) || !user?.uid}\n onClick={() => void handleCreate()}\n sx={{ mt: 0.25 }}\n >\n {'Create'}\n </Button>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy !== null}>\n {'Done'}\n </Button>\n </DialogActions>\n </Dialog>\n <PipelineStagesDialog\n open={Boolean(stagesPipeline)}\n onClose={() => setEditingStages(null)}\n orgId={orgId}\n pipeline={stagesPipeline}\n fromCache={fromCache}\n unreadable={unreadable}\n visibleToTokens={visibleToTokens}\n />\n </>\n )\n}\nPipelinesDialog.displayName = 'PipelinesDialog'\n\nexport default PipelinesDialog\n"],"names":["CRM_COLLECTIONS","isPipelineArchived","mdiArchiveArrowDownOutline","mdiArchiveArrowUpOutline","mdiFormatListBulleted","mdiPencilOutline","mdiStarOutline","MdiIcon","RowActionsMenu","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Divider","Stack","TextField","Typography","addDoc","collection","doc","getCountFromServer","query","updateDoc","where","writeBatch","useCallback","useEffect","useState","activePipelines","newPipelineDocument","PIPELINE_NAME_MAX","pipelineArchiveRefusal","pipelineNameProblem","sortedStages","PipelineStagesDialog","PipelinesDialog","props","pipelines","open","onClose","orgId","hostId","fromCache","unreadable","visibleToTokens","createTokens","firestore","enqueueSnackbar","data","user","busy","setBusy","newName","setNewName","renaming","setRenaming","editingStages","setEditingStages","pipelineRef","id","run","key","write","done","verdict","subject","ok","message","variant","persist","error","console","allowDuplicate","newProblem","trim","handleCreate","uid","visibleTo","nowMs","Date","now","handleRename","problem","name","slice","updatedAt","handleSetDefault","pipeline","$id","batch","entry","update","isDefault","commit","handleArchive","openDeals","snapshot","deals","count","refusal","archivedAt","handleRestore","actionsFor","archived","working","label","icon","path","size","disabled","onClick","Boolean","disabledReason","undefined","destructive","active","filter","stagesPipeline","find","row","stageCount","stage","kind","length","isRenaming","direction","spacing","sx","alignItems","py","data-pipeline-id","value","autoFocus","onChange","event","target","onKeyDown","preventDefault","flex","slotProps","htmlInput","maxLength","minWidth","lineHeight","noWrap","color","items","fullWidth","maxWidth","pt","map","my","placeholder","helperText","mt","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/pipelines-dialog.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 { CrmExportButton } from './crm-transfer-buttons'\nimport { CRM_PIPELINES_RESOURCE } from '../transfer/fields'\nimport { CRM_COLLECTIONS, isPipelineArchived } from '@aglyn/aglyn'\nimport {\n mdiArchiveArrowDownOutline,\n mdiArchiveArrowUpOutline,\n mdiFormatListBulleted,\n mdiPencilOutline,\n mdiStarOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n addDoc,\n collection,\n doc,\n getCountFromServer,\n query,\n updateDoc,\n where,\n writeBatch,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n activePipelines,\n newPipelineDocument,\n PIPELINE_NAME_MAX,\n type PipelineDoc,\n pipelineArchiveRefusal,\n pipelineNameProblem,\n sortedStages,\n} from '../model/deal-board-model'\nimport { PipelineStagesDialog } from './pipeline-stages-dialog'\n\nexport interface PipelinesDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n /**\n * The site this console is viewed from — provenance on a pipeline created\n * here — or at the organization level the site the reader picked, `null`\n * until they have (AGL-2630): a pipeline made there is the org's.\n */\n hostId: string | null\n /** Every pipeline the viewer may read, archived ones included. */\n pipelines: PipelineDoc[]\n /** The listener's verdict on the rows, for the stale-seed guard. */\n fromCache: boolean\n unreadable: boolean\n /** The scope tokens the open-deal count is filtered by — the viewer's own. */\n visibleToTokens: readonly string[]\n /** What a pipeline created here is stamped with. */\n createTokens: readonly string[]\n}\n\n/**\n * The org's pipelines (AGL-2620): list, create, rename, set as default,\n * archive, and a door into each one's stages.\n *\n * ## Org documents, scoped like every CRM row\n *\n * A pipeline lives at `orgs/{orgId}/pipelines` and is stamped with the same\n * scope tokens a contact captured on this site would carry, so an agency's\n * client sees its own pipelines and the org-wide ones — the same rule the\n * board reads by. Nothing here assumes one site: the tokens come in as\n * props from `useCrmScope`, and an org-level mount passes its own.\n *\n * ## What each verb writes\n *\n * - **New pipeline** — one `addDoc` of `newPipelineDocument`: a COPY of the\n * default stages, not the default, active. The field sits UNDER the list.\n * - **Rename** — `name` on the one document, refused when an active\n * pipeline already has the name (`pipelineNameProblem`).\n * - **Set as default** — one batch: the flag on, and off on every other\n * document that had it. A batch rather than two updates because two\n * defaults for the length of a network round trip is a board that opens\n * on either.\n * - **Archive** — `archivedAt: now`, after the server has counted the\n * pipeline's OPEN deals and `pipelineArchiveRefusal` has agreed; the\n * count is taken at the click because a deal can be created into the\n * pipeline from another tab meanwhile. The default pipeline and the last\n * active one refuse. **Restore** clears the stamp.\n * - **Edit stages** — opens the stages dialog for that pipeline, unchanged.\n */\nexport function PipelinesDialog(props: PipelinesDialogProps) {\n const {\n open,\n onClose,\n orgId,\n hostId,\n pipelines,\n fromCache,\n unreadable,\n visibleToTokens,\n createTokens,\n } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const [busy, setBusy] = useState<string | null>(null)\n const [newName, setNewName] = useState('')\n const [renaming, setRenaming] = useState<{ id: string; name: string } | null>(null)\n const [editingStages, setEditingStages] = useState<string | null>(null)\n\n useEffect(() => {\n if (!open) return\n setNewName('')\n setRenaming(null)\n setEditingStages(null)\n }, [open])\n\n const pipelineRef = useCallback(\n (id: string) => doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.pipelines, id),\n [firestore, orgId],\n )\n\n /** One guarded write, reported as a snackbar either way. */\n const run = useCallback(\n async (key: string, write: () => Promise<void>, done: string) => {\n setBusy(key)\n try {\n const verdict = await writeGuardedBySeed(\n { subject: 'pipeline', unreadable, fromCache },\n write,\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return false\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error', allowDuplicate: true })\n return false\n } finally {\n setBusy(null)\n }\n },\n [unreadable, fromCache, enqueueSnackbar],\n )\n\n const newProblem = newName.trim() ? pipelineNameProblem(newName, pipelines) : null\n const handleCreate = useCallback(async () => {\n if (!user?.uid || newProblem || !newName.trim()) return\n const ok = await run(\n 'create',\n async () => {\n await addDoc(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.pipelines),\n newPipelineDocument(newName, {\n visibleTo: createTokens,\n hostId,\n uid: user.uid,\n nowMs: Date.now(),\n }),\n )\n },\n 'Pipeline created',\n )\n if (ok) setNewName('')\n }, [user, newProblem, newName, run, firestore, orgId, createTokens, hostId])\n\n const handleRename = useCallback(async () => {\n if (!renaming) return\n const problem = pipelineNameProblem(renaming.name, pipelines, renaming.id)\n if (problem) {\n enqueueSnackbar(problem, { variant: 'warning', persist: false })\n return\n }\n const ok = await run(\n `rename:${renaming.id}`,\n async () => {\n await updateDoc(pipelineRef(renaming.id), {\n name: renaming.name.trim().slice(0, PIPELINE_NAME_MAX),\n updatedAt: new Date(),\n })\n },\n 'Pipeline renamed',\n )\n if (ok) setRenaming(null)\n }, [renaming, pipelines, enqueueSnackbar, run, pipelineRef])\n\n const handleSetDefault = useCallback(\n (pipeline: PipelineDoc) =>\n void run(\n `default:${pipeline.$id}`,\n async () => {\n const batch = writeBatch(firestore)\n const now = new Date()\n for (const entry of pipelines) {\n if (entry.$id === pipeline.$id) {\n batch.update(pipelineRef(entry.$id), { isDefault: true, updatedAt: now })\n } else if (entry.isDefault) {\n batch.update(pipelineRef(entry.$id), { isDefault: false, updatedAt: now })\n }\n }\n await batch.commit()\n },\n `${pipeline.name} is now the default pipeline`,\n ),\n [run, firestore, pipelines, pipelineRef],\n )\n\n const handleArchive = useCallback(\n async (pipeline: PipelineDoc) => {\n setBusy(`archive:${pipeline.$id}`)\n let openDeals: number\n try {\n const snapshot = await getCountFromServer(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n where('visibleTo', 'array-contains-any', [...visibleToTokens]),\n where('pipelineId', '==', pipeline.$id),\n where('status', '==', 'open'),\n ),\n )\n openDeals = snapshot.data().count\n } catch (error) {\n console.error(error)\n setBusy(null)\n enqueueSnackbar('The open deals in this pipeline could not be counted, so it stays.', {\n variant: 'error',\n allowDuplicate: true,\n })\n return\n }\n const refusal = pipelineArchiveRefusal(pipeline, pipelines, openDeals)\n if (refusal) {\n setBusy(null)\n enqueueSnackbar(refusal, { variant: 'warning', persist: false })\n return\n }\n await run(\n `archive:${pipeline.$id}`,\n async () => {\n await updateDoc(pipelineRef(pipeline.$id), {\n archivedAt: Date.now(),\n isDefault: false,\n updatedAt: new Date(),\n })\n },\n `${pipeline.name} archived`,\n )\n },\n [firestore, orgId, visibleToTokens, pipelines, run, pipelineRef, enqueueSnackbar],\n )\n\n const handleRestore = useCallback(\n (pipeline: PipelineDoc) => {\n const problem = pipelineNameProblem(pipeline.name, pipelines, pipeline.$id)\n if (problem) {\n enqueueSnackbar(`${problem} Rename it after restoring, or rename the other one first.`, {\n variant: 'warning',\n persist: false,\n })\n return\n }\n void run(\n `restore:${pipeline.$id}`,\n async () => {\n await updateDoc(pipelineRef(pipeline.$id), { archivedAt: null, updatedAt: new Date() })\n },\n `${pipeline.name} restored`,\n )\n },\n [pipelines, enqueueSnackbar, run, pipelineRef],\n )\n\n const actionsFor = (pipeline: PipelineDoc): RowActionsMenuItem[] => {\n const archived = isPipelineArchived(pipeline)\n const working = busy !== null\n return archived\n ? [\n {\n key: 'restore',\n label: 'Restore',\n icon: <MdiIcon path={mdiArchiveArrowUpOutline.path} size={0.8} />,\n disabled: working,\n onClick: () => handleRestore(pipeline),\n },\n ]\n : [\n {\n key: 'stages',\n label: 'Edit stages',\n icon: <MdiIcon path={mdiFormatListBulleted.path} size={0.8} />,\n disabled: working,\n onClick: () => setEditingStages(pipeline.$id),\n },\n {\n key: 'rename',\n label: 'Rename',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n disabled: working,\n onClick: () => setRenaming({ id: pipeline.$id, name: pipeline.name }),\n },\n {\n key: 'default',\n label: 'Set as default',\n icon: <MdiIcon path={mdiStarOutline.path} size={0.8} />,\n disabled: working || Boolean(pipeline.isDefault),\n disabledReason: pipeline.isDefault ? 'Already the default' : undefined,\n onClick: () => handleSetDefault(pipeline),\n },\n {\n key: 'archive',\n label: 'Archive',\n icon: <MdiIcon path={mdiArchiveArrowDownOutline.path} size={0.8} />,\n disabled: working,\n destructive: true,\n onClick: () => void handleArchive(pipeline),\n },\n ]\n }\n\n const active = activePipelines(pipelines)\n const archived = pipelines.filter((pipeline) => isPipelineArchived(pipeline))\n const stagesPipeline = editingStages\n ? (pipelines.find((pipeline) => pipeline.$id === editingStages) ?? null)\n : null\n\n const row = (pipeline: PipelineDoc) => {\n const stageCount = sortedStages(pipeline).filter((stage) => stage.kind === 'open').length\n const isRenaming = renaming?.id === pipeline.$id\n return (\n <Stack\n key={pipeline.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', py: 0.5 }}\n data-pipeline-id={pipeline.$id}\n >\n {isRenaming ? (\n <>\n <TextField\n size=\"small\"\n label=\"Name\"\n value={renaming.name}\n autoFocus\n onChange={(event) => setRenaming({ id: pipeline.$id, name: event.target.value })}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n void handleRename()\n }\n if (event.key === 'Escape') setRenaming(null)\n }}\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: PIPELINE_NAME_MAX } }}\n />\n <Button size=\"small\" onClick={() => setRenaming(null)} disabled={busy !== null}>\n {'Cancel'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={busy !== null || !renaming.name.trim()}\n onClick={() => void handleRename()}\n >\n {'Save'}\n </Button>\n </>\n ) : (\n <>\n <Stack sx={{ flex: 1, minWidth: 0, lineHeight: 1.25 }}>\n <Typography variant=\"body2\" noWrap>\n {pipeline.name || 'Untitled pipeline'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${stageCount} open ${stageCount === 1 ? 'stage' : 'stages'}`}\n </Typography>\n </Stack>\n {pipeline.isDefault && !isPipelineArchived(pipeline) ? (\n <Chip size=\"small\" color=\"primary\" variant=\"outlined\" label=\"Default\" />\n ) : null}\n {isPipelineArchived(pipeline) ? <Chip size=\"small\" label=\"Archived\" /> : null}\n <RowActionsMenu items={actionsFor(pipeline)} label={pipeline.name} />\n </>\n )}\n </Stack>\n )\n }\n\n return (\n <>\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{'Pipelines'}</DialogTitle>\n <DialogContent>\n <Stack spacing={1} sx={{ pt: 1 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'A pipeline is one way your business sells — its own stages, its own board. ' +\n 'New deals land in the default pipeline unless the drawer picks another.'}\n </Typography>\n {active.length ? (\n active.map(row)\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No active pipeline. Create one below.'}\n </Typography>\n )}\n {archived.length ? (\n <>\n <Divider sx={{ my: 1 }} />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Archived — kept so closed deals still show their stages'}\n </Typography>\n {archived.map(row)}\n </>\n ) : null}\n <Divider sx={{ my: 1 }} />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n size=\"small\"\n label=\"New pipeline\"\n placeholder=\"Renewals\"\n value={newName}\n onChange={(event) => setNewName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' || !newName.trim() || newProblem) return\n event.preventDefault()\n void handleCreate()\n }}\n error={Boolean(newProblem)}\n helperText={newProblem ?? 'Starts with the default stages, which you can then edit.'}\n sx={{ flex: 1 }}\n slotProps={{ htmlInput: { maxLength: PIPELINE_NAME_MAX } }}\n />\n <Button\n variant=\"contained\"\n disabled={busy !== null || !newName.trim() || Boolean(newProblem) || !user?.uid}\n onClick={() => void handleCreate()}\n sx={{ mt: 0.25 }}\n >\n {'Create'}\n </Button>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions>\n {/* Every pipeline with its stages, as a file (AGL-3528). */}\n <CrmExportButton resource={CRM_PIPELINES_RESOURCE} hostId={hostId} label=\"Export pipelines…\" />\n <Button onClick={onClose} disabled={busy !== null}>\n {'Done'}\n </Button>\n </DialogActions>\n </Dialog>\n <PipelineStagesDialog\n open={Boolean(stagesPipeline)}\n onClose={() => setEditingStages(null)}\n orgId={orgId}\n pipeline={stagesPipeline}\n fromCache={fromCache}\n unreadable={unreadable}\n visibleToTokens={visibleToTokens}\n />\n </>\n )\n}\nPipelinesDialog.displayName = 'PipelinesDialog'\n\nexport default PipelinesDialog\n"],"names":["CrmExportButton","CRM_PIPELINES_RESOURCE","CRM_COLLECTIONS","isPipelineArchived","mdiArchiveArrowDownOutline","mdiArchiveArrowUpOutline","mdiFormatListBulleted","mdiPencilOutline","mdiStarOutline","MdiIcon","RowActionsMenu","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Divider","Stack","TextField","Typography","addDoc","collection","doc","getCountFromServer","query","updateDoc","where","writeBatch","useCallback","useEffect","useState","activePipelines","newPipelineDocument","PIPELINE_NAME_MAX","pipelineArchiveRefusal","pipelineNameProblem","sortedStages","PipelineStagesDialog","PipelinesDialog","props","pipelines","open","onClose","orgId","hostId","fromCache","unreadable","visibleToTokens","createTokens","firestore","enqueueSnackbar","data","user","busy","setBusy","newName","setNewName","renaming","setRenaming","editingStages","setEditingStages","pipelineRef","id","run","key","write","done","verdict","subject","ok","message","variant","persist","error","console","allowDuplicate","newProblem","trim","handleCreate","uid","visibleTo","nowMs","Date","now","handleRename","problem","name","slice","updatedAt","handleSetDefault","pipeline","$id","batch","entry","update","isDefault","commit","handleArchive","openDeals","snapshot","deals","count","refusal","archivedAt","handleRestore","actionsFor","archived","working","label","icon","path","size","disabled","onClick","Boolean","disabledReason","undefined","destructive","active","filter","stagesPipeline","find","row","stageCount","stage","kind","length","isRenaming","direction","spacing","sx","alignItems","py","data-pipeline-id","value","autoFocus","onChange","event","target","onKeyDown","preventDefault","flex","slotProps","htmlInput","maxLength","minWidth","lineHeight","noWrap","color","items","fullWidth","maxWidth","pt","map","my","placeholder","helperText","mt","resource","displayName"],"mappings":"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"}
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { CrmExportButton } from "./crm-transfer-buttons.js";
|
|
19
|
+
import { CRM_ACTIVITIES_RESOURCE } from "../transfer/fields.js";
|
|
18
20
|
import * as Aglyn from "@aglyn/aglyn";
|
|
19
21
|
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
20
|
-
import { Typography } from "@mui/material";
|
|
22
|
+
import { Stack, Typography } from "@mui/material";
|
|
21
23
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
22
24
|
import { crmRoutes } from "../model/crm-routes.js";
|
|
23
25
|
import { ActivityList } from "./activity-list.js";
|
|
@@ -101,9 +103,22 @@ import { TimelineExpandAllButton, useTimelineExpansion } from "./activity-timeli
|
|
|
101
103
|
contentGutterX: true,
|
|
102
104
|
contentGutterY: true,
|
|
103
105
|
HeaderProps: {
|
|
104
|
-
action: /*#__PURE__*/
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
action: /*#__PURE__*/ _jsxs(Stack, {
|
|
107
|
+
direction: "row",
|
|
108
|
+
spacing: 1,
|
|
109
|
+
sx: {
|
|
110
|
+
alignItems: 'center'
|
|
111
|
+
},
|
|
112
|
+
children: [
|
|
113
|
+
/*#__PURE__*/ _jsx(CrmExportButton, {
|
|
114
|
+
resource: CRM_ACTIVITIES_RESOURCE,
|
|
115
|
+
hostId: hostId
|
|
116
|
+
}),
|
|
117
|
+
/*#__PURE__*/ _jsx(TimelineExpandAllButton, {
|
|
118
|
+
expansion: expansion,
|
|
119
|
+
disabled: !activities.rows.length
|
|
120
|
+
})
|
|
121
|
+
]
|
|
107
122
|
})
|
|
108
123
|
},
|
|
109
124
|
children: activities.status === 'error' ? /*#__PURE__*/ _jsx(Typography, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recent-activity-feed.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport type { CrmActivityRow } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Typography } from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { crmRoutes } from '../model/crm-routes'\nimport { ActivityList } from './activity-list'\nimport {\n type CrmOrg,\n useActivityScope,\n useActivityWindow,\n} from './activity-queries'\nimport { TimelineExpandAllButton, useTimelineExpansion } from './activity-timeline'\n\n/** How the record an activity is about reads in the feed. */\nconst RECORD_LABELS = {\n contact: 'Contact',\n deal: 'Deal',\n company: 'Company',\n lead: 'Lead',\n} as const\n\n/** One page of the feed, and the step its listener widens by — a glance, not a log. */\nexport const RECENT_ACTIVITY_LIMIT = 10\n\n/** The empty link: nothing to render, the query returns nothing. */\nconst NO_RECORD = Object.freeze({})\n\nexport interface RecentActivityFeedProps {\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n org: CrmOrg\n /** A page of the feed, and how far its listener widens when a page is turned past it. */\n limit?: number\n /**\n * The hub's mount path, for the link each row carries to its record. The\n * shell supplies it on every hub page; a mount without one draws the\n * record's label with no link rather than a link into nowhere.\n */\n basePath?: string\n}\n\n/**\n * The newest logged activity across the CRM (AGL-2600): the last few calls,\n * emails, meetings and notes anyone on the team filed against any record,\n * each linking to the record it is about.\n *\n * The contacts landing shows it as a card of its own under the list card,\n * so that opening the CRM answers \"what has the team been doing\" before\n * anybody opens a record, without the list running on into it. One bounded\n * listener on `(visibleTo, atMs DESC)` — the index that exists for exactly\n * this query — filtered to what this reader may see, the same predicate the\n * rules evaluate. It reads one page, `limit`, and widens by a page only\n * when the reader turns to the next one.\n *\n * Every row is collapsed to its kind, its heading, who and when, and opens\n * in place to the body; **Expand all** in the card header opens them all.\n *\n * A row links to its CONTACT when it has one, else its deal, else its\n * company — `crmActivityRecordLink`'s precedence — through `crmRoutes`, so\n * the address it builds is the one the hub resolves. Nothing is looked up\n * to name the record: a name per row would be a document read per row of a\n * feed drawn on every visit to the list, and the record's page names itself.\n */\nexport function RecentActivityFeed(props: RecentActivityFeedProps) {\n const { hostId, org, limit = RECENT_ACTIVITY_LIMIT, basePath } = props\n const scope = useActivityScope(hostId, org)\n const activities = useActivityWindow(scope, NO_RECORD, limit)\n const expansion = useTimelineExpansion()\n /*\n * Whether the feed has ever had a row. A page turned past the window\n * re-reads it, and the listener holds nothing while it does; the card has\n * to stay through that rather than vanish with its page.\n */\n const [seen, setSeen] = useState(false)\n useEffect(() => {\n if (activities.rows.length) setSeen(true)\n }, [activities.rows.length])\n const routes = useMemo(\n () => (basePath ? crmRoutes(basePath) : null),\n [basePath],\n )\n const subjectFor = useCallback(\n (activity: CrmActivityRow) => {\n const target = Aglyn.crmActivityRecordLink(activity)\n if (!target) return null\n const label = RECORD_LABELS[target.record]\n const href = routes ? routes[target.record](target.id) : null\n return href ? (\n <Typography variant=\"caption\">\n <AppLink href={href}>{label}</AppLink>\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n )\n },\n [routes],\n )\n\n // Nothing to show and nothing to say: a landing with no activity yet\n // should not carry an empty heading for a feature the reader has not\n // used, and the record pages are where the \"log one\" affordance lives.\n if (activities.status !== 'error' && !activities.rows.length && !seen) return null\n\n return (\n <CardDisplay\n header={'Recent activity'}\n help={Aglyn.pluginDocsHelp('contactActivities', { anchor: '#the-recent-activity-feed' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <TimelineExpandAllButton\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recent-activity-feed.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 { CrmExportButton } from './crm-transfer-buttons'\nimport { CRM_ACTIVITIES_RESOURCE } from '../transfer/fields'\nimport * as Aglyn from '@aglyn/aglyn'\nimport type { CrmActivityRow } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { crmRoutes } from '../model/crm-routes'\nimport { ActivityList } from './activity-list'\nimport {\n type CrmOrg,\n useActivityScope,\n useActivityWindow,\n} from './activity-queries'\nimport { TimelineExpandAllButton, useTimelineExpansion } from './activity-timeline'\n\n/** How the record an activity is about reads in the feed. */\nconst RECORD_LABELS = {\n contact: 'Contact',\n deal: 'Deal',\n company: 'Company',\n lead: 'Lead',\n} as const\n\n/** One page of the feed, and the step its listener widens by — a glance, not a log. */\nexport const RECENT_ACTIVITY_LIMIT = 10\n\n/** The empty link: nothing to render, the query returns nothing. */\nconst NO_RECORD = Object.freeze({})\n\nexport interface RecentActivityFeedProps {\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n org: CrmOrg\n /** A page of the feed, and how far its listener widens when a page is turned past it. */\n limit?: number\n /**\n * The hub's mount path, for the link each row carries to its record. The\n * shell supplies it on every hub page; a mount without one draws the\n * record's label with no link rather than a link into nowhere.\n */\n basePath?: string\n}\n\n/**\n * The newest logged activity across the CRM (AGL-2600): the last few calls,\n * emails, meetings and notes anyone on the team filed against any record,\n * each linking to the record it is about.\n *\n * The contacts landing shows it as a card of its own under the list card,\n * so that opening the CRM answers \"what has the team been doing\" before\n * anybody opens a record, without the list running on into it. One bounded\n * listener on `(visibleTo, atMs DESC)` — the index that exists for exactly\n * this query — filtered to what this reader may see, the same predicate the\n * rules evaluate. It reads one page, `limit`, and widens by a page only\n * when the reader turns to the next one.\n *\n * Every row is collapsed to its kind, its heading, who and when, and opens\n * in place to the body; **Expand all** in the card header opens them all.\n *\n * A row links to its CONTACT when it has one, else its deal, else its\n * company — `crmActivityRecordLink`'s precedence — through `crmRoutes`, so\n * the address it builds is the one the hub resolves. Nothing is looked up\n * to name the record: a name per row would be a document read per row of a\n * feed drawn on every visit to the list, and the record's page names itself.\n */\nexport function RecentActivityFeed(props: RecentActivityFeedProps) {\n const { hostId, org, limit = RECENT_ACTIVITY_LIMIT, basePath } = props\n const scope = useActivityScope(hostId, org)\n const activities = useActivityWindow(scope, NO_RECORD, limit)\n const expansion = useTimelineExpansion()\n /*\n * Whether the feed has ever had a row. A page turned past the window\n * re-reads it, and the listener holds nothing while it does; the card has\n * to stay through that rather than vanish with its page.\n */\n const [seen, setSeen] = useState(false)\n useEffect(() => {\n if (activities.rows.length) setSeen(true)\n }, [activities.rows.length])\n const routes = useMemo(\n () => (basePath ? crmRoutes(basePath) : null),\n [basePath],\n )\n const subjectFor = useCallback(\n (activity: CrmActivityRow) => {\n const target = Aglyn.crmActivityRecordLink(activity)\n if (!target) return null\n const label = RECORD_LABELS[target.record]\n const href = routes ? routes[target.record](target.id) : null\n return href ? (\n <Typography variant=\"caption\">\n <AppLink href={href}>{label}</AppLink>\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n )\n },\n [routes],\n )\n\n // Nothing to show and nothing to say: a landing with no activity yet\n // should not carry an empty heading for a feature the reader has not\n // used, and the record pages are where the \"log one\" affordance lives.\n if (activities.status !== 'error' && !activities.rows.length && !seen) return null\n\n return (\n <CardDisplay\n header={'Recent activity'}\n help={Aglyn.pluginDocsHelp('contactActivities', { anchor: '#the-recent-activity-feed' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {/* Every logged activity, as a file (AGL-3528). */}\n <CrmExportButton resource={CRM_ACTIVITIES_RESOURCE} hostId={hostId} />\n <TimelineExpandAllButton\n expansion={expansion}\n disabled={!activities.rows.length}\n />\n </Stack>\n ),\n }}\n >\n {activities.status === 'error' ? (\n <Typography variant=\"body2\" color=\"error\">\n {'Recent activity could not be loaded.'}\n </Typography>\n ) : (\n <ActivityList\n rows={activities.rows}\n scope={scope}\n subjectFor={subjectFor}\n readOnly\n hasMore={activities.hasMore}\n onShowMore={activities.showMore}\n loading={activities.status === 'loading'}\n expansion={expansion}\n />\n )}\n </CardDisplay>\n )\n}\nRecentActivityFeed.displayName = 'RecentActivityFeed'\n\nexport default RecentActivityFeed\n"],"names":["CrmExportButton","CRM_ACTIVITIES_RESOURCE","Aglyn","AppLink","CardDisplay","Stack","Typography","useCallback","useEffect","useMemo","useState","crmRoutes","ActivityList","useActivityScope","useActivityWindow","TimelineExpandAllButton","useTimelineExpansion","RECORD_LABELS","contact","deal","company","lead","RECENT_ACTIVITY_LIMIT","NO_RECORD","Object","freeze","RecentActivityFeed","props","hostId","org","limit","basePath","scope","activities","expansion","seen","setSeen","rows","length","routes","subjectFor","activity","target","crmActivityRecordLink","label","record","href","id","variant","color","status","header","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","direction","spacing","sx","alignItems","resource","disabled","readOnly","hasMore","onShowMore","showMore","loading","displayName"],"mappings":"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"}
|