@aglyn/plugins-crm 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +57 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +29 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/activity-list.d.ts +98 -0
- package/src/lib/components/activity-list.js +321 -0
- package/src/lib/components/activity-list.js.map +1 -0
- package/src/lib/components/activity-queries.d.ts +117 -0
- package/src/lib/components/activity-queries.js +198 -0
- package/src/lib/components/activity-queries.js.map +1 -0
- package/src/lib/components/add-to-list-button.d.ts +25 -0
- package/src/lib/components/add-to-list-button.js +64 -0
- package/src/lib/components/add-to-list-button.js.map +1 -0
- package/src/lib/components/add-to-list-dialog.d.ts +24 -0
- package/src/lib/components/add-to-list-dialog.js +327 -0
- package/src/lib/components/add-to-list-dialog.js.map +1 -0
- package/src/lib/components/assignment-rule-drawer.d.ts +43 -0
- package/src/lib/components/assignment-rule-drawer.js +293 -0
- package/src/lib/components/assignment-rule-drawer.js.map +1 -0
- package/src/lib/components/companies-bulk-bar.d.ts +29 -0
- package/src/lib/components/companies-bulk-bar.js +274 -0
- package/src/lib/components/companies-bulk-bar.js.map +1 -0
- package/src/lib/components/companies-section.d.ts +54 -0
- package/src/lib/components/companies-section.js +522 -0
- package/src/lib/components/companies-section.js.map +1 -0
- package/src/lib/components/company-contacts-card.d.ts +49 -0
- package/src/lib/components/company-contacts-card.js +435 -0
- package/src/lib/components/company-contacts-card.js.map +1 -0
- package/src/lib/components/company-deals-card.d.ts +20 -0
- package/src/lib/components/company-deals-card.js +38 -0
- package/src/lib/components/company-deals-card.js.map +1 -0
- package/src/lib/components/company-detail-page.d.ts +20 -0
- package/src/lib/components/company-detail-page.js +165 -0
- package/src/lib/components/company-detail-page.js.map +1 -0
- package/src/lib/components/company-edit-drawer.d.ts +65 -0
- package/src/lib/components/company-edit-drawer.js +433 -0
- package/src/lib/components/company-edit-drawer.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +42 -0
- package/src/lib/components/company-import-drawer.js +99 -0
- package/src/lib/components/company-import-drawer.js.map +1 -0
- package/src/lib/components/company-picker.d.ts +127 -0
- package/src/lib/components/company-picker.js +323 -0
- package/src/lib/components/company-picker.js.map +1 -0
- package/src/lib/components/company-properties-card.d.ts +47 -0
- package/src/lib/components/company-properties-card.js +289 -0
- package/src/lib/components/company-properties-card.js.map +1 -0
- package/src/lib/components/contact-address-fields.d.ts +29 -0
- package/src/lib/components/contact-address-fields.js +149 -0
- package/src/lib/components/contact-address-fields.js.map +1 -0
- package/src/lib/components/contact-associations-card.d.ts +72 -0
- package/src/lib/components/contact-associations-card.js +296 -0
- package/src/lib/components/contact-associations-card.js.map +1 -0
- package/src/lib/components/contact-custom-columns.d.ts +38 -0
- package/src/lib/components/contact-custom-columns.js +102 -0
- package/src/lib/components/contact-custom-columns.js.map +1 -0
- package/src/lib/components/contact-custom-fields-card.d.ts +39 -0
- package/src/lib/components/contact-custom-fields-card.js +204 -0
- package/src/lib/components/contact-custom-fields-card.js.map +1 -0
- package/src/lib/components/contact-deals-card.d.ts +20 -0
- package/src/lib/components/contact-deals-card.js +38 -0
- package/src/lib/components/contact-deals-card.js.map +1 -0
- package/src/lib/components/contact-detail-page.d.ts +46 -0
- package/src/lib/components/contact-detail-page.js +535 -0
- package/src/lib/components/contact-detail-page.js.map +1 -0
- package/src/lib/components/contact-duplicates-card.d.ts +42 -0
- package/src/lib/components/contact-duplicates-card.js +167 -0
- package/src/lib/components/contact-duplicates-card.js.map +1 -0
- package/src/lib/components/contact-field-drawer.d.ts +64 -0
- package/src/lib/components/contact-field-drawer.js +251 -0
- package/src/lib/components/contact-field-drawer.js.map +1 -0
- package/src/lib/components/contact-import-drawer.d.ts +46 -0
- package/src/lib/components/contact-import-drawer.js +149 -0
- package/src/lib/components/contact-import-drawer.js.map +1 -0
- package/src/lib/components/contact-known-by-card.d.ts +39 -0
- package/src/lib/components/contact-known-by-card.js +142 -0
- package/src/lib/components/contact-known-by-card.js.map +1 -0
- package/src/lib/components/contact-list-columns.d.ts +53 -0
- package/src/lib/components/contact-list-columns.js +288 -0
- package/src/lib/components/contact-list-columns.js.map +1 -0
- package/src/lib/components/contact-merge-dialog.d.ts +79 -0
- package/src/lib/components/contact-merge-dialog.js +405 -0
- package/src/lib/components/contact-merge-dialog.js.map +1 -0
- package/src/lib/components/contact-properties-card.d.ts +89 -0
- package/src/lib/components/contact-properties-card.js +492 -0
- package/src/lib/components/contact-properties-card.js.map +1 -0
- package/src/lib/components/contact-timeline-card.d.ts +68 -0
- package/src/lib/components/contact-timeline-card.js +435 -0
- package/src/lib/components/contact-timeline-card.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +91 -0
- package/src/lib/components/contacts-bulk-bar.js +493 -0
- package/src/lib/components/contacts-bulk-bar.js.map +1 -0
- package/src/lib/components/contacts-section.d.ts +26 -0
- package/src/lib/components/contacts-section.js +1025 -0
- package/src/lib/components/contacts-section.js.map +1 -0
- package/src/lib/components/copy-capture-address-button.d.ts +33 -0
- package/src/lib/components/copy-capture-address-button.js +60 -0
- package/src/lib/components/copy-capture-address-button.js.map +1 -0
- package/src/lib/components/crm-attribution-zone.d.ts +38 -0
- package/src/lib/components/crm-attribution-zone.js +36 -0
- package/src/lib/components/crm-attribution-zone.js.map +1 -0
- package/src/lib/components/crm-booking-zone.d.ts +61 -0
- package/src/lib/components/crm-booking-zone.js +50 -0
- package/src/lib/components/crm-booking-zone.js.map +1 -0
- package/src/lib/components/crm-bulk-bar-frame.d.ts +47 -0
- package/src/lib/components/crm-bulk-bar-frame.js +137 -0
- package/src/lib/components/crm-bulk-bar-frame.js.map +1 -0
- package/src/lib/components/crm-call-actions.d.ts +65 -0
- package/src/lib/components/crm-call-actions.js +110 -0
- package/src/lib/components/crm-call-actions.js.map +1 -0
- package/src/lib/components/crm-column-menu.d.ts +21 -0
- package/src/lib/components/crm-column-menu.js +103 -0
- package/src/lib/components/crm-column-menu.js.map +1 -0
- package/src/lib/components/crm-console-page.d.ts +19 -0
- package/src/lib/components/crm-console-page.js +151 -0
- package/src/lib/components/crm-console-page.js.map +1 -0
- package/src/lib/components/crm-console-sections.d.ts +52 -0
- package/src/lib/components/crm-console-sections.js +83 -0
- package/src/lib/components/crm-console-sections.js.map +1 -0
- package/src/lib/components/crm-custom-field-control.d.ts +26 -0
- package/src/lib/components/crm-custom-field-control.js +131 -0
- package/src/lib/components/crm-custom-field-control.js.map +1 -0
- package/src/lib/components/crm-email-template-tools.d.ts +69 -0
- package/src/lib/components/crm-email-template-tools.js +240 -0
- package/src/lib/components/crm-email-template-tools.js.map +1 -0
- package/src/lib/components/crm-export-all-button.d.ts +39 -0
- package/src/lib/components/crm-export-all-button.js +98 -0
- package/src/lib/components/crm-export-all-button.js.map +1 -0
- package/src/lib/components/crm-filter-bar.d.ts +53 -0
- package/src/lib/components/crm-filter-bar.js +294 -0
- package/src/lib/components/crm-filter-bar.js.map +1 -0
- package/src/lib/components/crm-glance-card.d.ts +54 -0
- package/src/lib/components/crm-glance-card.js +219 -0
- package/src/lib/components/crm-glance-card.js.map +1 -0
- package/src/lib/components/crm-next-activity-column.d.ts +35 -0
- package/src/lib/components/crm-next-activity-column.js +100 -0
- package/src/lib/components/crm-next-activity-column.js.map +1 -0
- package/src/lib/components/crm-record-header.d.ts +97 -0
- package/src/lib/components/crm-record-header.js +160 -0
- package/src/lib/components/crm-record-header.js.map +1 -0
- package/src/lib/components/crm-record-insights-zone.d.ts +50 -0
- package/src/lib/components/crm-record-insights-zone.js +169 -0
- package/src/lib/components/crm-record-insights-zone.js.map +1 -0
- package/src/lib/components/crm-record-picker.d.ts +42 -0
- package/src/lib/components/crm-record-picker.js +104 -0
- package/src/lib/components/crm-record-picker.js.map +1 -0
- package/src/lib/components/crm-send-email-button.d.ts +39 -0
- package/src/lib/components/crm-send-email-button.js +81 -0
- package/src/lib/components/crm-send-email-button.js.map +1 -0
- package/src/lib/components/crm-send-email-dialog.d.ts +61 -0
- package/src/lib/components/crm-send-email-dialog.js +851 -0
- package/src/lib/components/crm-send-email-dialog.js.map +1 -0
- package/src/lib/components/crm-site-picker.d.ts +61 -0
- package/src/lib/components/crm-site-picker.js +99 -0
- package/src/lib/components/crm-site-picker.js.map +1 -0
- package/src/lib/components/crm-suite-lock.d.ts +29 -0
- package/src/lib/components/crm-suite-lock.js +114 -0
- package/src/lib/components/crm-suite-lock.js.map +1 -0
- package/src/lib/components/crm-tasks-due-card.d.ts +42 -0
- package/src/lib/components/crm-tasks-due-card.js +225 -0
- package/src/lib/components/crm-tasks-due-card.js.map +1 -0
- package/src/lib/components/crm-views-control.d.ts +65 -0
- package/src/lib/components/crm-views-control.js +518 -0
- package/src/lib/components/crm-views-control.js.map +1 -0
- package/src/lib/components/csv-import-drawer.d.ts +132 -0
- package/src/lib/components/csv-import-drawer.js +746 -0
- package/src/lib/components/csv-import-drawer.js.map +1 -0
- package/src/lib/components/deal-board.d.ts +70 -0
- package/src/lib/components/deal-board.js +478 -0
- package/src/lib/components/deal-board.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +18 -0
- package/src/lib/components/deal-detail-page.js +323 -0
- package/src/lib/components/deal-detail-page.js.map +1 -0
- package/src/lib/components/deal-edit-drawer.d.ts +59 -0
- package/src/lib/components/deal-edit-drawer.js +640 -0
- package/src/lib/components/deal-edit-drawer.js.map +1 -0
- package/src/lib/components/deal-import-drawer.d.ts +24 -0
- package/src/lib/components/deal-import-drawer.js +83 -0
- package/src/lib/components/deal-import-drawer.js.map +1 -0
- package/src/lib/components/deal-products-card.d.ts +48 -0
- package/src/lib/components/deal-products-card.js +614 -0
- package/src/lib/components/deal-products-card.js.map +1 -0
- package/src/lib/components/deal-properties-card.d.ts +29 -0
- package/src/lib/components/deal-properties-card.js +144 -0
- package/src/lib/components/deal-properties-card.js.map +1 -0
- package/src/lib/components/deal-stage-card.d.ts +24 -0
- package/src/lib/components/deal-stage-card.js +199 -0
- package/src/lib/components/deal-stage-card.js.map +1 -0
- package/src/lib/components/deals-bulk-bar.d.ts +31 -0
- package/src/lib/components/deals-bulk-bar.js +340 -0
- package/src/lib/components/deals-bulk-bar.js.map +1 -0
- package/src/lib/components/deals-section.d.ts +49 -0
- package/src/lib/components/deals-section.js +767 -0
- package/src/lib/components/deals-section.js.map +1 -0
- package/src/lib/components/email-capture-card.d.ts +42 -0
- package/src/lib/components/email-capture-card.js +134 -0
- package/src/lib/components/email-capture-card.js.map +1 -0
- package/src/lib/components/email-template-drawer.d.ts +33 -0
- package/src/lib/components/email-template-drawer.js +209 -0
- package/src/lib/components/email-template-drawer.js.map +1 -0
- package/src/lib/components/email-templates-card.d.ts +33 -0
- package/src/lib/components/email-templates-card.js +370 -0
- package/src/lib/components/email-templates-card.js.map +1 -0
- package/src/lib/components/erase-person-action.d.ts +92 -0
- package/src/lib/components/erase-person-action.js +261 -0
- package/src/lib/components/erase-person-action.js.map +1 -0
- package/src/lib/components/fields-section.d.ts +37 -0
- package/src/lib/components/fields-section.js +641 -0
- package/src/lib/components/fields-section.js.map +1 -0
- package/src/lib/components/form-contact-fields-card.d.ts +46 -0
- package/src/lib/components/form-contact-fields-card.js +253 -0
- package/src/lib/components/form-contact-fields-card.js.map +1 -0
- package/src/lib/components/insert-link-at-caret.d.ts +28 -0
- package/src/lib/components/insert-link-at-caret.js +37 -0
- package/src/lib/components/insert-link-at-caret.js.map +1 -0
- package/src/lib/components/lead-convert-dialog.d.ts +32 -0
- package/src/lib/components/lead-convert-dialog.js +493 -0
- package/src/lib/components/lead-convert-dialog.js.map +1 -0
- package/src/lib/components/lead-detail-page.d.ts +16 -0
- package/src/lib/components/lead-detail-page.js +163 -0
- package/src/lib/components/lead-detail-page.js.map +1 -0
- package/src/lib/components/lead-history-card.d.ts +32 -0
- package/src/lib/components/lead-history-card.js +165 -0
- package/src/lib/components/lead-history-card.js.map +1 -0
- package/src/lib/components/lead-import-drawer.d.ts +24 -0
- package/src/lib/components/lead-import-drawer.js +95 -0
- package/src/lib/components/lead-import-drawer.js.map +1 -0
- package/src/lib/components/lead-owner-select.d.ts +35 -0
- package/src/lib/components/lead-owner-select.js +91 -0
- package/src/lib/components/lead-owner-select.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +61 -0
- package/src/lib/components/lead-properties-card.js +395 -0
- package/src/lib/components/lead-properties-card.js.map +1 -0
- package/src/lib/components/lead-status-chip.d.ts +12 -0
- package/src/lib/components/lead-status-chip.js +43 -0
- package/src/lib/components/lead-status-chip.js.map +1 -0
- package/src/lib/components/lead-surfaces-note.d.ts +126 -0
- package/src/lib/components/lead-surfaces-note.js +262 -0
- package/src/lib/components/lead-surfaces-note.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +24 -0
- package/src/lib/components/lead-unqualify-dialog.js +125 -0
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -0
- package/src/lib/components/leads-bulk-bar.d.ts +58 -0
- package/src/lib/components/leads-bulk-bar.js +307 -0
- package/src/lib/components/leads-bulk-bar.js.map +1 -0
- package/src/lib/components/leads-section.d.ts +23 -0
- package/src/lib/components/leads-section.js +664 -0
- package/src/lib/components/leads-section.js.map +1 -0
- package/src/lib/components/linked-deals-card.d.ts +32 -0
- package/src/lib/components/linked-deals-card.js +163 -0
- package/src/lib/components/linked-deals-card.js.map +1 -0
- package/src/lib/components/log-activity-dialog.d.ts +60 -0
- package/src/lib/components/log-activity-dialog.js +368 -0
- package/src/lib/components/log-activity-dialog.js.map +1 -0
- package/src/lib/components/lost-reason-dialog.d.ts +26 -0
- package/src/lib/components/lost-reason-dialog.js +95 -0
- package/src/lib/components/lost-reason-dialog.js.map +1 -0
- package/src/lib/components/new-contact-drawer.d.ts +78 -0
- package/src/lib/components/new-contact-drawer.js +343 -0
- package/src/lib/components/new-contact-drawer.js.map +1 -0
- package/src/lib/components/org-lead-surfaces-note.d.ts +57 -0
- package/src/lib/components/org-lead-surfaces-note.js +172 -0
- package/src/lib/components/org-lead-surfaces-note.js.map +1 -0
- package/src/lib/components/owner-avatar.d.ts +18 -0
- package/src/lib/components/owner-avatar.js +61 -0
- package/src/lib/components/owner-avatar.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +43 -0
- package/src/lib/components/pipeline-stages-dialog.js +339 -0
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -0
- package/src/lib/components/pipelines-dialog.d.ts +55 -0
- package/src/lib/components/pipelines-dialog.js +517 -0
- package/src/lib/components/pipelines-dialog.js.map +1 -0
- package/src/lib/components/recent-activity-feed.d.ts +39 -0
- package/src/lib/components/recent-activity-feed.js +103 -0
- package/src/lib/components/recent-activity-feed.js.map +1 -0
- package/src/lib/components/recipes-card.d.ts +39 -0
- package/src/lib/components/recipes-card.js +485 -0
- package/src/lib/components/recipes-card.js.map +1 -0
- package/src/lib/components/record-activity-card.d.ts +26 -0
- package/src/lib/components/record-activity-card.js +121 -0
- package/src/lib/components/record-activity-card.js.map +1 -0
- package/src/lib/components/record-files-card.d.ts +67 -0
- package/src/lib/components/record-files-card.js +290 -0
- package/src/lib/components/record-files-card.js.map +1 -0
- package/src/lib/components/record-tasks-card.d.ts +32 -0
- package/src/lib/components/record-tasks-card.js +306 -0
- package/src/lib/components/record-tasks-card.js.map +1 -0
- package/src/lib/components/reports/activity-card.d.ts +57 -0
- package/src/lib/components/reports/activity-card.js +281 -0
- package/src/lib/components/reports/activity-card.js.map +1 -0
- package/src/lib/components/reports/closed-deals-card.d.ts +20 -0
- package/src/lib/components/reports/closed-deals-card.js +194 -0
- package/src/lib/components/reports/closed-deals-card.js.map +1 -0
- package/src/lib/components/reports/contacts-mix-card.d.ts +27 -0
- package/src/lib/components/reports/contacts-mix-card.js +161 -0
- package/src/lib/components/reports/contacts-mix-card.js.map +1 -0
- package/src/lib/components/reports/contacts-trend-card.d.ts +31 -0
- package/src/lib/components/reports/contacts-trend-card.js +144 -0
- package/src/lib/components/reports/contacts-trend-card.js.map +1 -0
- package/src/lib/components/reports/forecast-card.d.ts +22 -0
- package/src/lib/components/reports/forecast-card.js +251 -0
- package/src/lib/components/reports/forecast-card.js.map +1 -0
- package/src/lib/components/reports/lead-counts.d.ts +32 -0
- package/src/lib/components/reports/lead-counts.js +44 -0
- package/src/lib/components/reports/lead-counts.js.map +1 -0
- package/src/lib/components/reports/lead-funnel-card.d.ts +38 -0
- package/src/lib/components/reports/lead-funnel-card.js +375 -0
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -0
- package/src/lib/components/reports/pipeline-card.d.ts +19 -0
- package/src/lib/components/reports/pipeline-card.js +338 -0
- package/src/lib/components/reports/pipeline-card.js.map +1 -0
- package/src/lib/components/reports/report-bar-chart.d.ts +39 -0
- package/src/lib/components/reports/report-bar-chart.js +133 -0
- package/src/lib/components/reports/report-bar-chart.js.map +1 -0
- package/src/lib/components/reports/report-breakdown.d.ts +32 -0
- package/src/lib/components/reports/report-breakdown.js +105 -0
- package/src/lib/components/reports/report-breakdown.js.map +1 -0
- package/src/lib/components/reports/report-export.d.ts +37 -0
- package/src/lib/components/reports/report-export.js +66 -0
- package/src/lib/components/reports/report-export.js.map +1 -0
- package/src/lib/components/reports/report-format.d.ts +36 -0
- package/src/lib/components/reports/report-format.js +43 -0
- package/src/lib/components/reports/report-format.js.map +1 -0
- package/src/lib/components/reports/report-scope.d.ts +81 -0
- package/src/lib/components/reports/report-scope.js +58 -0
- package/src/lib/components/reports/report-scope.js.map +1 -0
- package/src/lib/components/reports/report-stat-tile.d.ts +47 -0
- package/src/lib/components/reports/report-stat-tile.js +96 -0
- package/src/lib/components/reports/report-stat-tile.js.map +1 -0
- package/src/lib/components/reports/source-conversion-card.d.ts +28 -0
- package/src/lib/components/reports/source-conversion-card.js +205 -0
- package/src/lib/components/reports/source-conversion-card.js.map +1 -0
- package/src/lib/components/reports/tasks-card.d.ts +26 -0
- package/src/lib/components/reports/tasks-card.js +279 -0
- package/src/lib/components/reports/tasks-card.js.map +1 -0
- package/src/lib/components/reports/use-aggregate-read.d.ts +99 -0
- package/src/lib/components/reports/use-aggregate-read.js +195 -0
- package/src/lib/components/reports/use-aggregate-read.js.map +1 -0
- package/src/lib/components/reports/won-lost-by-owner-card.d.ts +30 -0
- package/src/lib/components/reports/won-lost-by-owner-card.js +168 -0
- package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -0
- package/src/lib/components/reports-section.d.ts +42 -0
- package/src/lib/components/reports-section.js +308 -0
- package/src/lib/components/reports-section.js.map +1 -0
- package/src/lib/components/sending-addresses-card.d.ts +49 -0
- package/src/lib/components/sending-addresses-card.js +262 -0
- package/src/lib/components/sending-addresses-card.js.map +1 -0
- package/src/lib/components/settings-section.d.ts +151 -0
- package/src/lib/components/settings-section.js +783 -0
- package/src/lib/components/settings-section.js.map +1 -0
- package/src/lib/components/task-cells.d.ts +51 -0
- package/src/lib/components/task-cells.js +139 -0
- package/src/lib/components/task-cells.js.map +1 -0
- package/src/lib/components/task-edit-drawer.d.ts +53 -0
- package/src/lib/components/task-edit-drawer.js +483 -0
- package/src/lib/components/task-edit-drawer.js.map +1 -0
- package/src/lib/components/task-import-drawer.d.ts +24 -0
- package/src/lib/components/task-import-drawer.js +82 -0
- package/src/lib/components/task-import-drawer.js.map +1 -0
- package/src/lib/components/task-snooze-menu.d.ts +65 -0
- package/src/lib/components/task-snooze-menu.js +207 -0
- package/src/lib/components/task-snooze-menu.js.map +1 -0
- package/src/lib/components/tasks-bulk-bar.d.ts +25 -0
- package/src/lib/components/tasks-bulk-bar.js +348 -0
- package/src/lib/components/tasks-bulk-bar.js.map +1 -0
- package/src/lib/components/tasks-calendar.d.ts +38 -0
- package/src/lib/components/tasks-calendar.js +228 -0
- package/src/lib/components/tasks-calendar.js.map +1 -0
- package/src/lib/components/tasks-section.d.ts +27 -0
- package/src/lib/components/tasks-section.js +579 -0
- package/src/lib/components/tasks-section.js.map +1 -0
- package/src/lib/components/use-contact-campaign-emails.d.ts +32 -0
- package/src/lib/components/use-contact-campaign-emails.js +83 -0
- package/src/lib/components/use-contact-campaign-emails.js.map +1 -0
- package/src/lib/components/use-crm-api.d.ts +44 -0
- package/src/lib/components/use-crm-api.js +84 -0
- package/src/lib/components/use-crm-api.js.map +1 -0
- package/src/lib/components/use-crm-inbound-address.d.ts +60 -0
- package/src/lib/components/use-crm-inbound-address.js +124 -0
- package/src/lib/components/use-crm-inbound-address.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +24 -0
- package/src/lib/components/use-emails-hub-path.js +59 -0
- package/src/lib/components/use-emails-hub-path.js.map +1 -0
- package/src/lib/components/use-member-email-aliases.d.ts +54 -0
- package/src/lib/components/use-member-email-aliases.js +186 -0
- package/src/lib/components/use-member-email-aliases.js.map +1 -0
- package/src/lib/components/use-org-members.d.ts +40 -0
- package/src/lib/components/use-org-members.js +100 -0
- package/src/lib/components/use-org-members.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +109 -0
- package/src/lib/constants/api-routes.js +74 -0
- package/src/lib/constants/api-routes.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +14 -0
- package/src/lib/constants/bundle-common.js +14 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/company-filters.d.ts +18 -0
- package/src/lib/constants/company-filters.js +51 -0
- package/src/lib/constants/company-filters.js.map +1 -0
- package/src/lib/constants/contact-filters.d.ts +38 -0
- package/src/lib/constants/contact-filters.js +294 -0
- package/src/lib/constants/contact-filters.js.map +1 -0
- package/src/lib/hooks/use-contact-field-definitions.d.ts +48 -0
- package/src/lib/hooks/use-contact-field-definitions.js +72 -0
- package/src/lib/hooks/use-contact-field-definitions.js.map +1 -0
- package/src/lib/hooks/use-contact-update.d.ts +25 -0
- package/src/lib/hooks/use-contact-update.js +80 -0
- package/src/lib/hooks/use-contact-update.js.map +1 -0
- package/src/lib/hooks/use-crm-activity-logger.d.ts +73 -0
- package/src/lib/hooks/use-crm-activity-logger.js +101 -0
- package/src/lib/hooks/use-crm-activity-logger.js.map +1 -0
- package/src/lib/hooks/use-crm-bulk-apply.d.ts +52 -0
- package/src/lib/hooks/use-crm-bulk-apply.js +112 -0
- package/src/lib/hooks/use-crm-bulk-apply.js.map +1 -0
- package/src/lib/hooks/use-crm-default-view.d.ts +24 -0
- package/src/lib/hooks/use-crm-default-view.js +64 -0
- package/src/lib/hooks/use-crm-default-view.js.map +1 -0
- package/src/lib/hooks/use-crm-email-templates.d.ts +59 -0
- package/src/lib/hooks/use-crm-email-templates.js +103 -0
- package/src/lib/hooks/use-crm-email-templates.js.map +1 -0
- package/src/lib/hooks/use-crm-hub-path.d.ts +29 -0
- package/src/lib/hooks/use-crm-hub-path.js +41 -0
- package/src/lib/hooks/use-crm-hub-path.js.map +1 -0
- package/src/lib/hooks/use-crm-org-mount.d.ts +97 -0
- package/src/lib/hooks/use-crm-org-mount.js +215 -0
- package/src/lib/hooks/use-crm-org-mount.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +52 -0
- package/src/lib/hooks/use-crm-record-names.js +126 -0
- package/src/lib/hooks/use-crm-record-names.js.map +1 -0
- package/src/lib/hooks/use-crm-records-quota.d.ts +68 -0
- package/src/lib/hooks/use-crm-records-quota.js +118 -0
- package/src/lib/hooks/use-crm-records-quota.js.map +1 -0
- package/src/lib/hooks/use-crm-saved-view.d.ts +96 -0
- package/src/lib/hooks/use-crm-saved-view.js +329 -0
- package/src/lib/hooks/use-crm-saved-view.js.map +1 -0
- package/src/lib/hooks/use-crm-scope.d.ts +117 -0
- package/src/lib/hooks/use-crm-scope.js +147 -0
- package/src/lib/hooks/use-crm-scope.js.map +1 -0
- package/src/lib/hooks/use-crm-tasks.d.ts +85 -0
- package/src/lib/hooks/use-crm-tasks.js +153 -0
- package/src/lib/hooks/use-crm-tasks.js.map +1 -0
- package/src/lib/hooks/use-crm-view-grid.d.ts +55 -0
- package/src/lib/hooks/use-crm-view-grid.js +170 -0
- package/src/lib/hooks/use-crm-view-grid.js.map +1 -0
- package/src/lib/hooks/use-crm-views.d.ts +42 -0
- package/src/lib/hooks/use-crm-views.js +84 -0
- package/src/lib/hooks/use-crm-views.js.map +1 -0
- package/src/lib/hooks/use-deal-stage-api.d.ts +69 -0
- package/src/lib/hooks/use-deal-stage-api.js +78 -0
- package/src/lib/hooks/use-deal-stage-api.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +47 -0
- package/src/lib/hooks/use-deals.js +105 -0
- package/src/lib/hooks/use-deals.js.map +1 -0
- package/src/lib/hooks/use-org-leads.d.ts +53 -0
- package/src/lib/hooks/use-org-leads.js +114 -0
- package/src/lib/hooks/use-org-leads.js.map +1 -0
- package/src/lib/hooks/use-org-member-directory.d.ts +36 -0
- package/src/lib/hooks/use-org-member-directory.js +116 -0
- package/src/lib/hooks/use-org-member-directory.js.map +1 -0
- package/src/lib/hooks/use-org-member-options.d.ts +38 -0
- package/src/lib/hooks/use-org-member-options.js +131 -0
- package/src/lib/hooks/use-org-member-options.js.map +1 -0
- package/src/lib/hooks/use-pipeline.d.ts +71 -0
- package/src/lib/hooks/use-pipeline.js +159 -0
- package/src/lib/hooks/use-pipeline.js.map +1 -0
- package/src/lib/model/companies-bulk-writes.d.ts +40 -0
- package/src/lib/model/companies-bulk-writes.js +103 -0
- package/src/lib/model/companies-bulk-writes.js.map +1 -0
- package/src/lib/model/companies-csv.d.ts +36 -0
- package/src/lib/model/companies-csv.js +38 -0
- package/src/lib/model/companies-csv.js.map +1 -0
- package/src/lib/model/companies.d.ts +99 -0
- package/src/lib/model/companies.js +168 -0
- package/src/lib/model/companies.js.map +1 -0
- package/src/lib/model/company-delete-route.d.ts +43 -0
- package/src/lib/model/company-delete-route.js +31 -0
- package/src/lib/model/company-delete-route.js.map +1 -0
- package/src/lib/model/company-delete.d.ts +42 -0
- package/src/lib/model/company-delete.js +52 -0
- package/src/lib/model/company-delete.js.map +1 -0
- package/src/lib/model/contact-duplicates.d.ts +58 -0
- package/src/lib/model/contact-duplicates.js +67 -0
- package/src/lib/model/contact-duplicates.js.map +1 -0
- package/src/lib/model/contact-record.d.ts +113 -0
- package/src/lib/model/contact-record.js +70 -0
- package/src/lib/model/contact-record.js.map +1 -0
- package/src/lib/model/contact-update.d.ts +93 -0
- package/src/lib/model/contact-update.js +50 -0
- package/src/lib/model/contact-update.js.map +1 -0
- package/src/lib/model/contacts-bulk-writes.d.ts +173 -0
- package/src/lib/model/contacts-bulk-writes.js +196 -0
- package/src/lib/model/contacts-bulk-writes.js.map +1 -0
- package/src/lib/model/contacts-csv.d.ts +54 -0
- package/src/lib/model/contacts-csv.js +67 -0
- package/src/lib/model/contacts-csv.js.map +1 -0
- package/src/lib/model/contacts-list-seed.d.ts +51 -0
- package/src/lib/model/contacts-list-seed.js +63 -0
- package/src/lib/model/contacts-list-seed.js.map +1 -0
- package/src/lib/model/crm-activity-report.d.ts +27 -0
- package/src/lib/model/crm-activity-report.js +56 -0
- package/src/lib/model/crm-activity-report.js.map +1 -0
- package/src/lib/model/crm-api.d.ts +49 -0
- package/src/lib/model/crm-api.js +47 -0
- package/src/lib/model/crm-api.js.map +1 -0
- package/src/lib/model/crm-bulk-writes.d.ts +147 -0
- package/src/lib/model/crm-bulk-writes.js +207 -0
- package/src/lib/model/crm-bulk-writes.js.map +1 -0
- package/src/lib/model/crm-company-import.d.ts +154 -0
- package/src/lib/model/crm-company-import.js +331 -0
- package/src/lib/model/crm-company-import.js.map +1 -0
- package/src/lib/model/crm-custom-draft.d.ts +62 -0
- package/src/lib/model/crm-custom-draft.js +67 -0
- package/src/lib/model/crm-custom-draft.js.map +1 -0
- package/src/lib/model/crm-deal-import.d.ts +112 -0
- package/src/lib/model/crm-deal-import.js +263 -0
- package/src/lib/model/crm-deal-import.js.map +1 -0
- package/src/lib/model/crm-import.d.ts +244 -0
- package/src/lib/model/crm-import.js +466 -0
- package/src/lib/model/crm-import.js.map +1 -0
- package/src/lib/model/crm-lead-import.d.ts +105 -0
- package/src/lib/model/crm-lead-import.js +255 -0
- package/src/lib/model/crm-lead-import.js.map +1 -0
- package/src/lib/model/crm-record-routes.d.ts +21 -0
- package/src/lib/model/crm-record-routes.js +70 -0
- package/src/lib/model/crm-record-routes.js.map +1 -0
- package/src/lib/model/crm-routes.d.ts +101 -0
- package/src/lib/model/crm-routes.js +92 -0
- package/src/lib/model/crm-routes.js.map +1 -0
- package/src/lib/model/crm-task-import.d.ts +94 -0
- package/src/lib/model/crm-task-import.js +275 -0
- package/src/lib/model/crm-task-import.js.map +1 -0
- package/src/lib/model/crm-view-param.d.ts +43 -0
- package/src/lib/model/crm-view-param.js +49 -0
- package/src/lib/model/crm-view-param.js.map +1 -0
- package/src/lib/model/deal-board-model.d.ts +241 -0
- package/src/lib/model/deal-board-model.js +439 -0
- package/src/lib/model/deal-board-model.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +114 -0
- package/src/lib/model/deal-form-model.js +218 -0
- package/src/lib/model/deal-form-model.js.map +1 -0
- package/src/lib/model/deals-csv.d.ts +32 -0
- package/src/lib/model/deals-csv.js +37 -0
- package/src/lib/model/deals-csv.js.map +1 -0
- package/src/lib/model/import-column-shapes.d.ts +36 -0
- package/src/lib/model/import-column-shapes.js +76 -0
- package/src/lib/model/import-column-shapes.js.map +1 -0
- package/src/lib/model/lead-company-suggestion.d.ts +61 -0
- package/src/lib/model/lead-company-suggestion.js +65 -0
- package/src/lib/model/lead-company-suggestion.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +29 -0
- package/src/lib/model/lead-filters.js +39 -0
- package/src/lib/model/lead-filters.js.map +1 -0
- package/src/lib/model/lead-surfaces.d.ts +58 -0
- package/src/lib/model/lead-surfaces.js +67 -0
- package/src/lib/model/lead-surfaces.js.map +1 -0
- package/src/lib/model/leads-csv.d.ts +40 -0
- package/src/lib/model/leads-csv.js +40 -0
- package/src/lib/model/leads-csv.js.map +1 -0
- package/src/lib/model/next-activity-api.d.ts +31 -0
- package/src/lib/model/next-activity-api.js +57 -0
- package/src/lib/model/next-activity-api.js.map +1 -0
- package/src/lib/model/next-activity.d.ts +64 -0
- package/src/lib/model/next-activity.js +47 -0
- package/src/lib/model/next-activity.js.map +1 -0
- package/src/lib/model/person-key-web.d.ts +43 -0
- package/src/lib/model/person-key-web.js +52 -0
- package/src/lib/model/person-key-web.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +282 -0
- package/src/lib/model/record-facts.js +434 -0
- package/src/lib/model/record-facts.js.map +1 -0
- package/src/lib/model/task-api.d.ts +28 -0
- package/src/lib/model/task-api.js +57 -0
- package/src/lib/model/task-api.js.map +1 -0
- package/src/lib/model/task-calendar.d.ts +108 -0
- package/src/lib/model/task-calendar.js +139 -0
- package/src/lib/model/task-calendar.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +177 -0
- package/src/lib/model/task-routes.js +197 -0
- package/src/lib/model/task-routes.js.map +1 -0
- package/src/lib/model/task-scope.d.ts +45 -0
- package/src/lib/model/task-scope.js +51 -0
- package/src/lib/model/task-scope.js.map +1 -0
- package/src/lib/model/task-views.d.ts +149 -0
- package/src/lib/model/task-views.js +286 -0
- package/src/lib/model/task-views.js.map +1 -0
- package/src/lib/model/tasks-csv.d.ts +28 -0
- package/src/lib/model/tasks-csv.js +28 -0
- package/src/lib/model/tasks-csv.js.map +1 -0
- package/src/lib/plugin.d.ts +32 -0
- package/src/lib/plugin.js +208 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/companies-import.d.ts +57 -0
- package/src/lib/server/companies-import.js +242 -0
- package/src/lib/server/companies-import.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +59 -0
- package/src/lib/server/company-delete.js +146 -0
- package/src/lib/server/company-delete.js.map +1 -0
- package/src/lib/server/contact-email-history.d.ts +82 -0
- package/src/lib/server/contact-email-history.js +264 -0
- package/src/lib/server/contact-email-history.js.map +1 -0
- package/src/lib/server/contact-profile.d.ts +41 -0
- package/src/lib/server/contact-profile.js +45 -0
- package/src/lib/server/contact-profile.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +92 -0
- package/src/lib/server/contact-update.js +512 -0
- package/src/lib/server/contact-update.js.map +1 -0
- package/src/lib/server/contacts-import.d.ts +73 -0
- package/src/lib/server/contacts-import.js +263 -0
- package/src/lib/server/contacts-import.js.map +1 -0
- package/src/lib/server/contacts-merge.d.ts +86 -0
- package/src/lib/server/contacts-merge.js +206 -0
- package/src/lib/server/contacts-merge.js.map +1 -0
- package/src/lib/server/deals-import.d.ts +76 -0
- package/src/lib/server/deals-import.js +246 -0
- package/src/lib/server/deals-import.js.map +1 -0
- package/src/lib/server/email-send.d.ts +119 -0
- package/src/lib/server/email-send.js +667 -0
- package/src/lib/server/email-send.js.map +1 -0
- package/src/lib/server/email-template-duplicate.d.ts +19 -0
- package/src/lib/server/email-template-duplicate.js +206 -0
- package/src/lib/server/email-template-duplicate.js.map +1 -0
- package/src/lib/server/erase-person.d.ts +108 -0
- package/src/lib/server/erase-person.js +309 -0
- package/src/lib/server/erase-person.js.map +1 -0
- package/src/lib/server/import-context.d.ts +85 -0
- package/src/lib/server/import-context.js +181 -0
- package/src/lib/server/import-context.js.map +1 -0
- package/src/lib/server/inbound-address.d.ts +61 -0
- package/src/lib/server/inbound-address.js +188 -0
- package/src/lib/server/inbound-address.js.map +1 -0
- package/src/lib/server/lead-convert.d.ts +78 -0
- package/src/lib/server/lead-convert.js +250 -0
- package/src/lib/server/lead-convert.js.map +1 -0
- package/src/lib/server/leads-import.d.ts +84 -0
- package/src/lib/server/leads-import.js +261 -0
- package/src/lib/server/leads-import.js.map +1 -0
- package/src/lib/server/next-activity-routes.d.ts +33 -0
- package/src/lib/server/next-activity-routes.js +110 -0
- package/src/lib/server/next-activity-routes.js.map +1 -0
- package/src/lib/server/org-activity.d.ts +61 -0
- package/src/lib/server/org-activity.js +120 -0
- package/src/lib/server/org-activity.js.map +1 -0
- package/src/lib/server/org-caller.d.ts +110 -0
- package/src/lib/server/org-caller.js +154 -0
- package/src/lib/server/org-caller.js.map +1 -0
- package/src/lib/server/recipe-routes.d.ts +75 -0
- package/src/lib/server/recipe-routes.js +442 -0
- package/src/lib/server/recipe-routes.js.map +1 -0
- package/src/lib/server/record-facts.d.ts +65 -0
- package/src/lib/server/record-facts.js +337 -0
- package/src/lib/server/record-facts.js.map +1 -0
- package/src/lib/server/record-timeline.d.ts +51 -0
- package/src/lib/server/record-timeline.js +215 -0
- package/src/lib/server/record-timeline.js.map +1 -0
- package/src/lib/server/suite-gate.d.ts +42 -0
- package/src/lib/server/suite-gate.js +87 -0
- package/src/lib/server/suite-gate.js.map +1 -0
- package/src/lib/server/task-routes.d.ts +132 -0
- package/src/lib/server/task-routes.js +699 -0
- package/src/lib/server/task-routes.js.map +1 -0
- package/src/lib/server/tasks-import.d.ts +54 -0
- package/src/lib/server/tasks-import.js +157 -0
- package/src/lib/server/tasks-import.js.map +1 -0
- package/src/lib/server-deal-stage.d.ts +88 -0
- package/src/lib/server-deal-stage.js +316 -0
- package/src/lib/server-deal-stage.js.map +1 -0
- package/src/lib/server.d.ts +149 -0
- package/src/lib/server.js +615 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n CRM_COLLECTIONS,\n type CrmCompany,\n filterByNextActivity,\n isNoNextActivityClause,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n gridFilterRequest,\n listFilterColumn,\n type ListFilterRequest,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport { NoNextActivityToggle, nextActivityColumn } from './crm-next-activity-column'\nimport CrmViewsControl from './crm-views-control'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport {\n listFilterConstraints,\n usePagedCollection,\n useFirestore,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport {\n getGridSingleSelectOperators,\n type GridColDef,\n} from '@mui/x-data-grid'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { COMPANY_LIST_FILTER_FIELDS } from '../constants/company-filters'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { crmVisibleToClause, useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CompanyCsvOptions, companiesCsv } from '../model/companies-csv'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport CompaniesBulkBar from './companies-bulk-bar'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { CompanyImportButton } from './company-import-drawer'\nimport { customFieldColumns } from './contact-custom-columns'\n\nexport interface CompaniesSectionProps {\n /** The site the list is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling>\n /** The CRM hub URL, which every company route hangs beneath. */\n basePath: string\n}\n\ntype CompanyRow = Partial<CrmCompany> & { $id: string; updatedAt?: any }\n\n/**\n * `/crm/companies` — the organizations behind the people (AGL-2597).\n *\n * A section of its own rather than a column on the contact list, because a\n * company is known by several contacts and carries records of its own: a\n * domain the email suggestion keys on, an address, an owner, and the deals\n * and tasks filed against it. The row opens `/crm/companies/{id}`, where all\n * of that lives; this list is the cheaper surface and reads nothing a row\n * does not show.\n *\n * ## What the listener carries\n *\n * Paged and ordered by the server — `updatedAt desc`, so a company somebody\n * just touched is on page one — and SCOPED by the same `visibleTo\n * array-contains-any` predicate the contact list runs. That predicate is what\n * the rules evaluate: a filtered query is provable per document, and an\n * unfiltered one is refused rather than quietly returning the whole org's\n * accounts to a site that may see one client's.\n *\n * ## What a filter may be\n *\n * The scope predicate is an array clause, and Firestore carries one per\n * query, so the word-prefix search the contact list offers cannot run here.\n * The name is a prefix range and an exact match instead, and the owner is a\n * choice from the roster — each reaching the whole collection, which is the\n * property a search must have: a company on page four is found, not reported\n * missing. `COMPANY_LIST_FILTER_FIELDS` states what the indexes serve.\n *\n * ## Creating is a drawer\n *\n * \"New company\" opens the same eight-field drawer the company's page edits\n * with, and the new record's page opens when it is saved. A form above a\n * list has nowhere to grow, and this one has an address in it.\n *\n * ## Selection, the bar and the file (AGL-2621)\n *\n * The rows are selectable, and a selection raises `CompaniesBulkBar` over\n * the table. Export CSV writes the loaded page through `companiesCsv()` —\n * the same file the bar writes over the selection, headed as the import\n * reads it — and Import CSV opens the companies drawer beside it.\n */\nexport function CompaniesSection(props: CompaniesSectionProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const { scope, orgId, visibleTo } = useCrmScope({ hostId, org })\n /*\n * The team, read once for this surface: the Owner column names a person\n * from a uid, and the create drawer picks from the same roster. One fetch\n * serves both, which is why the drawer takes it as a prop.\n */\n const members = useOrgMemberOptions(orgId)\n // The org's company fields, for the optional columns below (AGL-2661).\n const companyFields = useContactFieldDefinitions(orgId, 'company')\n\n /*\n * The column filter is the saved VIEW'S first clause (AGL-2617): this\n * grid's panel holds one, and a view of companies holds that one beside\n * the columns and the sort. Declared BEFORE the listener that reads it,\n * as the filter always was — the query is rebuilt from it.\n */\n const views = useCrmSavedView({ section: 'companies', hostId, org, basePath })\n /*\n * ...beside the \"No next activity\" clause (AGL-2661), which is not a\n * query the listener can run — absence has no index — and narrows the\n * loaded page below instead. The grid's one clause is the first that is\n * not it, and setting the grid's clause keeps it.\n */\n const viewFilters = views.state.filters\n const filter: ListFilterRequest | null =\n viewFilters.find((clause) => !isNoNextActivityClause(clause)) ?? null\n const setFilter = useCallback(\n (request: ListFilterRequest | null) =>\n views.setFilters([\n ...(request ? [request] : []),\n ...viewFilters.filter(isNoNextActivityClause),\n ]),\n [views.setFilters, viewFilters],\n )\n\n const {\n rows: loaded,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<CompanyRow>(\n (pageLimit) => {\n if (!scope) return null\n const constraints = listFilterConstraints(\n COMPANY_LIST_FILTER_FIELDS,\n filter,\n )\n return query(\n collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies),\n // Absent at the organization level, where the tokens are `null` (AGL-2630).\n ...crmVisibleToClause(visibleTo),\n ...(constraints ?? [orderBy('updatedAt', 'desc')]),\n limit(pageLimit),\n )\n },\n [firestore, scope, visibleTo, filter],\n { idField: '$id' },\n )\n const companies = useMemo(\n () => filterByNextActivity(loaded, viewFilters),\n [loaded, viewFilters],\n )\n const nowMs = useMemo(() => Date.now(), [])\n\n const [createOpen, setCreateOpen] = useState(false)\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n /*\n * The owner is written by ADDRESS, because the companies import resolves\n * an owner by email; the same options reach the bar, so the page's file\n * and the selection's are one format.\n */\n const csvOptions: CompanyCsvOptions = useMemo(\n () => ({ ownerEmail: members.emailFor }),\n [members.emailFor],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('companies.csv', 'text/csv', companiesCsv(companies, csvOptions))\n }, [companies, csvOptions])\n const openCompany = useCallback(\n (id: string) => router.push(routes.company(id)),\n [router, routes],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Company',\n flex: 1.6,\n minWidth: 240,\n ...listFilterColumn(COMPANY_LIST_FILTER_FIELDS, 'name'),\n valueGetter: (_value, row: CompanyRow) => String(row.name ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Stack\n sx={{ justifyContent: 'center', height: '100%', lineHeight: 1.25 }}\n >\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.name}\n </Typography>\n {row.industry ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ lineHeight: 1.25 }}\n noWrap\n >\n {row.industry}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'domain',\n headerName: 'Domain',\n flex: 1,\n minWidth: 160,\n // Stored normalized, so it could be filtered exactly — but a domain\n // lookup is what the company suggestion on a contact does, and a\n // filter here would need an index of its own for one more way to\n // find a row the name filter already finds.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => String(row.domain ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.domain ? (\n <Typography variant=\"body2\">{row.domain}</Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'contactsCount',\n headerName: 'Contacts',\n width: 110,\n align: 'right',\n headerAlign: 'right',\n /*\n * The denormalized count every link moves (AGL-2613) — a stored\n * number, so a page of companies costs no read per row. Absent on a\n * company nobody has linked since the counter existed, which reads\n * as zero; the company's own page takes the live aggregate.\n */\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => Number(row.contactsCount ?? 0),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.contactsCount ? 'text.primary' : 'text.secondary'}\n >\n {Number(row.contactsCount ?? 0).toLocaleString()}\n </Typography>\n ),\n },\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 1,\n minWidth: 140,\n // Written by the drawer and the bulk bar (AGL-2621); read here, not\n // filtered — a tag filter would need an `array-contains` index of\n // its own beside the scope predicate.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => (row.tags ?? []).join(', '),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.tags?.length ? (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', overflow: 'hidden' }}>\n {row.tags.slice(0, 3).map((tag) => (\n <Chip key={tag} size=\"small\" variant=\"outlined\" label={tag} />\n ))}\n {row.tags.length > 3 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`+${row.tags.length - 3}`}\n </Typography>\n ) : null}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 160,\n sortable: false,\n /*\n * A CHOICE from the roster, not a text box for a uid. The grid's\n * single-select `is` operator is what the panel offers; the handler\n * below maps it onto the declared field's `equals`, which the\n * translator turns into the equality the index serves.\n */\n type: 'singleSelect',\n valueOptions: members.options.map((option) => ({\n value: option.uid,\n label: option.label,\n })),\n filterable: members.options.length > 0,\n filterOperators: getGridSingleSelectOperators().filter(\n (operator) => operator.value === 'is',\n ),\n valueGetter: (_value, row: CompanyRow) => String(row.ownerUid ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.ownerUid ? 'text.primary' : 'text.secondary'}\n >\n {row.ownerUid\n ? members.ready\n ? members.labelFor(row.ownerUid)\n : '…'\n : '—'}\n </Typography>\n ),\n },\n {\n field: 'updatedAt',\n headerName: 'Updated',\n flex: 0.8,\n minWidth: 140,\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n row.updatedAt?.seconds ? new Date(row.updatedAt.seconds * 1000) : null,\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {row.updatedAt?.seconds\n ? new Date(row.updatedAt.seconds * 1000).toLocaleDateString()\n : '—'}\n </Typography>\n ),\n },\n // When the earliest open task against the company is due (AGL-2661).\n nextActivityColumn(nowMs),\n // The org's company fields as optional columns (AGL-2661), read off\n // the row's own `custom` map the way the contacts list reads its own.\n ...customFieldColumns(companyFields.active),\n ],\n [members, companyFields.active, nowMs],\n )\n\n /*\n * The grid's models are the view's (AGL-2617). The filter model shows the\n * view's clause in the panel as a typed one would appear — the owner's\n * stored `equals` back as the single-select `is` the panel offers — so a\n * view opened from its address reads as filtered, not as a mystery.\n */\n const grid = useCrmViewGrid(views, columns)\n const filterModel = useMemo(\n () => ({\n items: filter\n ? [\n {\n id: 'view',\n field: filter.field,\n operator:\n filter.field === 'ownerUid' && filter.op === 'equals' ? 'is' : filter.op,\n value: filter.value,\n },\n ]\n : [],\n }),\n [filter],\n )\n\n const newCompanyButton = (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={!scope}\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n onClick={() => setCreateOpen(true)}\n >\n {'New company'}\n </Button>\n )\n\n return (\n <CardDisplay\n header={'Companies'}\n help={pluginDocsHelp('companies', { anchor: '#the-companies-list' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CrmViewsControl controller={views} allLabel=\"All companies\" />\n <NoNextActivityToggle filters={viewFilters} onChange={views.setFilters} />\n {newCompanyButton}\n </Stack>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flex: 1 }}>\n {'The organizations your contacts belong to. Open one to see its ' +\n 'people, its deals and its open tasks, or to link a contact ' +\n 'to it.'}\n </Typography>\n <CompanyImportButton hostId={hostId} org={org} />\n <Button size=\"small\" onClick={handleExport} disabled={!companies.length}>\n {'Export CSV'}\n </Button>\n </Stack>\n <CompaniesBulkBar\n hostId={hostId}\n scope={scope}\n rows={companies}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n members={members}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rowHeight={TABLE_ROW_HEIGHT}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n rows={companies}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n noRowsLabel=\"No companies yet\"\n noRowsDescription=\"A company groups the contacts who work at one business, with its domain, owner and address. Create the first one, or link a contact to a company from their page.\"\n noRowsAction={newCompanyButton}\n onOpen={(id) => openCompany(String(id))}\n // An empty table while the read is in flight reads as \"you have\n // none\" rather than \"these are on their way\".\n loading={!scope || status === 'loading'}\n /*\n * The grid must NOT also filter. The query answers it, so a client\n * pass could only drop rows the query already matched.\n */\n filterMode=\"server\"\n filterModel={filterModel}\n onFilterModelChange={(model) => {\n const request = gridFilterRequest(model)\n setFilter(\n request && request.field === 'ownerUid' && request.op === 'is'\n ? { ...request, op: 'equals' }\n : request,\n )\n }}\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={companies.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n {/*\n Mounted only while it is open, so the list pays for none of the\n drawer's state — and a fresh mount is what seeds an empty form.\n */}\n {createOpen ? (\n <CompanyEditDrawer\n open\n onClose={() => setCreateOpen(false)}\n hostId={hostId}\n org={org}\n members={members}\n onSaved={openCompany}\n />\n ) : null}\n </CardDisplay>\n )\n}\nCompaniesSection.displayName = 'CompaniesSection'\n\nexport default CompaniesSection\n"],"names":["CRM_COLLECTIONS","filterByNextActivity","isNoNextActivityClause","pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","ListPagination","ListTable","gridFilterRequest","listFilterColumn","useCrmSavedView","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","NoNextActivityToggle","nextActivityColumn","CrmViewsControl","TABLE_ROW_HEIGHT","listFilterConstraints","usePagedCollection","useFirestore","Button","Chip","Stack","Typography","getGridSingleSelectOperators","collection","limit","orderBy","query","useRouter","useCallback","useMemo","useState","COMPANY_LIST_FILTER_FIELDS","useContactFieldDefinitions","crmVisibleToClause","useCrmScope","useOrgMemberOptions","companiesCsv","downloadTextFile","crmRoutes","CompaniesBulkBar","CompanyEditDrawer","CompanyImportButton","customFieldColumns","CompaniesSection","props","viewFilters","hostId","org","basePath","routes","router","firestore","scope","orgId","visibleTo","members","companyFields","views","section","state","filters","filter","find","clause","setFilter","request","setFilters","rows","loaded","status","hasMore","page","setPage","pageSize","setPageSize","pageLimit","constraints","companies","idField","nowMs","Date","now","createOpen","setCreateOpen","selectedIds","setSelectedIds","csvOptions","ownerEmail","emailFor","handleExport","openCompany","id","push","company","columns","field","headerName","flex","minWidth","valueGetter","_value","row","String","name","renderCell","sx","justifyContent","height","lineHeight","variant","industry","color","noWrap","filterable","sortable","domain","width","align","headerAlign","Number","contactsCount","toLocaleString","tags","join","length","direction","spacing","alignItems","overflow","slice","map","tag","size","label","type","valueOptions","options","option","value","uid","filterOperators","operator","ownerUid","ready","labelFor","updatedAt","seconds","toLocaleDateString","active","grid","filterModel","items","op","newCompanyButton","disabled","startIcon","path","onClick","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","controller","allLabel","onChange","flexWrap","rowGap","selected","onSelectedChange","csv","columnOrder","rowHeight","slots","selectable","noRowsLabel","noRowsDescription","noRowsAction","onOpen","loading","filterMode","onFilterModelChange","model","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","onSaved","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type CrmScope } from '../hooks/use-crm-scope';
|
|
2
|
+
import type { CrmRoutes } from '../model/crm-routes';
|
|
3
|
+
export interface CompanyContactsCardProps {
|
|
4
|
+
companyId: string;
|
|
5
|
+
companyName: string;
|
|
6
|
+
crmScope: CrmScope;
|
|
7
|
+
/** The org document, for each contact's own holder at the organization level. */
|
|
8
|
+
org?: Record<string, unknown> | null;
|
|
9
|
+
routes: CrmRoutes;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* THE PEOPLE AT ONE COMPANY, and the control that puts a person there
|
|
13
|
+
* (AGL-2597).
|
|
14
|
+
*
|
|
15
|
+
* ## What it reads
|
|
16
|
+
*
|
|
17
|
+
* Contacts whose `companyIds` mirror carries this company — see
|
|
18
|
+
* `CONTACT_COMPANY_IDS_FIELD` for why a mirror and not the facet — newest
|
|
19
|
+
* activity first, one page at a time under the shared footer, plus one
|
|
20
|
+
* server aggregate for the total. The aggregate is what the footer counts
|
|
21
|
+
* with: a company's people are a real list, bounded by how many the account
|
|
22
|
+
* has met rather than by any taxonomy, and a page the reader can turn is the
|
|
23
|
+
* only honest answer to the fifty-first. The membership is PII and one
|
|
24
|
+
* document per person, so it is read HERE, where somebody opened the
|
|
25
|
+
* company, and never on the list that names companies.
|
|
26
|
+
*
|
|
27
|
+
* ## The scope caveat, stated rather than hidden
|
|
28
|
+
*
|
|
29
|
+
* The listener cannot carry the `visibleTo` predicate beside the
|
|
30
|
+
* `array-contains` on the mirror — Firestore takes one array clause per
|
|
31
|
+
* query — so the rules admit it only to an org-wide member. A member scoped
|
|
32
|
+
* to particular sites is refused, and the card SAYS so: an empty table for
|
|
33
|
+
* them would read as "nobody works here", which is the answer this surface
|
|
34
|
+
* must never give wrongly.
|
|
35
|
+
*
|
|
36
|
+
* ## Linking
|
|
37
|
+
*
|
|
38
|
+
* "Add contact" finds a person by email address or by name among the
|
|
39
|
+
* contacts this site may see — those reads DO carry the scope predicate —
|
|
40
|
+
* and links them through `crm/contact-update` (AGL-2804): the link is the
|
|
41
|
+
* holder's facet, which the server writes, keeping the facet, the mirror and
|
|
42
|
+
* the company's count in step. A person already at another company is moved,
|
|
43
|
+
* and the row says so before the click.
|
|
44
|
+
*/
|
|
45
|
+
export declare function CompanyContactsCard(props: CompanyContactsCardProps): import("react").JSX.Element;
|
|
46
|
+
export declare namespace CompanyContactsCard {
|
|
47
|
+
var displayName: string;
|
|
48
|
+
}
|
|
49
|
+
export default CompanyContactsCard;
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { contactDisplayName, nameSearchKey, normalizeContactEmail, planContactCompanyLink, pluginDocsHelp, readContactCompanyLink, readContactFacet } from "@aglyn/aglyn";
|
|
19
|
+
import { mdiAccountPlusOutline, mdiLinkOff } from "@aglyn/shared-data-mdi";
|
|
20
|
+
import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
22
|
+
import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
|
|
23
|
+
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
24
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
25
|
+
import { useFirestore, usePagedCollection } from "@aglyn/tenant-feature-instance";
|
|
26
|
+
import { Alert, Button, IconButton, Stack, TableBody, TableCell, TableHead, TableRow, TextField, Tooltip, Typography } from "@mui/material";
|
|
27
|
+
import { collection, endAt, getCountFromServer, getDocs, limit, orderBy, query, startAt, where } from "firebase/firestore";
|
|
28
|
+
import { useCallback, useEffect, useState } from "react";
|
|
29
|
+
import { useContactUpdate } from "../hooks/use-contact-update.js";
|
|
30
|
+
import { crmVisibleToClause } from "../hooks/use-crm-scope.js";
|
|
31
|
+
import { CONTACT_COMPANY_IDS_FIELD } from "../model/companies.js";
|
|
32
|
+
import { contactPrimaryGroup } from "../model/contact-record.js";
|
|
33
|
+
/** How many matches a search offers to link. */ const SEARCH_LIMIT = 8;
|
|
34
|
+
/** A very high private-use codepoint, closing a prefix range. */ const HIGH = '\uf8ff';
|
|
35
|
+
/**
|
|
36
|
+
* THE PEOPLE AT ONE COMPANY, and the control that puts a person there
|
|
37
|
+
* (AGL-2597).
|
|
38
|
+
*
|
|
39
|
+
* ## What it reads
|
|
40
|
+
*
|
|
41
|
+
* Contacts whose `companyIds` mirror carries this company — see
|
|
42
|
+
* `CONTACT_COMPANY_IDS_FIELD` for why a mirror and not the facet — newest
|
|
43
|
+
* activity first, one page at a time under the shared footer, plus one
|
|
44
|
+
* server aggregate for the total. The aggregate is what the footer counts
|
|
45
|
+
* with: a company's people are a real list, bounded by how many the account
|
|
46
|
+
* has met rather than by any taxonomy, and a page the reader can turn is the
|
|
47
|
+
* only honest answer to the fifty-first. The membership is PII and one
|
|
48
|
+
* document per person, so it is read HERE, where somebody opened the
|
|
49
|
+
* company, and never on the list that names companies.
|
|
50
|
+
*
|
|
51
|
+
* ## The scope caveat, stated rather than hidden
|
|
52
|
+
*
|
|
53
|
+
* The listener cannot carry the `visibleTo` predicate beside the
|
|
54
|
+
* `array-contains` on the mirror — Firestore takes one array clause per
|
|
55
|
+
* query — so the rules admit it only to an org-wide member. A member scoped
|
|
56
|
+
* to particular sites is refused, and the card SAYS so: an empty table for
|
|
57
|
+
* them would read as "nobody works here", which is the answer this surface
|
|
58
|
+
* must never give wrongly.
|
|
59
|
+
*
|
|
60
|
+
* ## Linking
|
|
61
|
+
*
|
|
62
|
+
* "Add contact" finds a person by email address or by name among the
|
|
63
|
+
* contacts this site may see — those reads DO carry the scope predicate —
|
|
64
|
+
* and links them through `crm/contact-update` (AGL-2804): the link is the
|
|
65
|
+
* holder's facet, which the server writes, keeping the facet, the mirror and
|
|
66
|
+
* the company's count in step. A person already at another company is moved,
|
|
67
|
+
* and the row says so before the click.
|
|
68
|
+
*/ export function CompanyContactsCard(props) {
|
|
69
|
+
const { companyId, companyName, crmScope, org, routes } = props;
|
|
70
|
+
const { scope, consentGroup, visibleTo } = crmScope;
|
|
71
|
+
const firestore = useFirestore();
|
|
72
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
73
|
+
const contactUpdate = useContactUpdate(crmScope.hostId);
|
|
74
|
+
/**
|
|
75
|
+
* The holder a person is read and linked through: the viewing group under
|
|
76
|
+
* a site; at the organization level (AGL-2630) each person's own primary
|
|
77
|
+
* holder, so the link lands on the facet the record page edits.
|
|
78
|
+
*/ const groupIdOf = useCallback((row)=>{
|
|
79
|
+
var _ref;
|
|
80
|
+
return (_ref = consentGroup == null ? void 0 : consentGroup.groupId) != null ? _ref : contactPrimaryGroup(row, org != null ? org : null).groupId;
|
|
81
|
+
}, [
|
|
82
|
+
consentGroup,
|
|
83
|
+
org
|
|
84
|
+
]);
|
|
85
|
+
const { rows: contactDocs, status, hasMore, page, setPage, pageSize, setPageSize } = usePagedCollection((pageLimit)=>scope ? query(collection(firestore, scope[0], scope[1], 'contacts'), where(CONTACT_COMPANY_IDS_FIELD, 'array-contains', companyId), orderBy('updatedAt', 'desc'), limit(pageLimit)) : null, [
|
|
86
|
+
firestore,
|
|
87
|
+
scope,
|
|
88
|
+
companyId
|
|
89
|
+
], {
|
|
90
|
+
idField: '$id'
|
|
91
|
+
});
|
|
92
|
+
const contacts = contactDocs.map((row)=>{
|
|
93
|
+
var _row_email;
|
|
94
|
+
return {
|
|
95
|
+
$id: row.$id,
|
|
96
|
+
name: contactDisplayName(row, groupIdOf(row)),
|
|
97
|
+
email: String((_row_email = row['email']) != null ? _row_email : ''),
|
|
98
|
+
linkedByThisHolder: readContactFacet(row, groupIdOf(row)).companyId === companyId,
|
|
99
|
+
data: row
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
/*
|
|
103
|
+
* The total, as a server aggregate, re-taken when a link is made or
|
|
104
|
+
* removed here — a figure that disagreed with the table under it would
|
|
105
|
+
* read as the link having failed.
|
|
106
|
+
*/ const [count, setCount] = useState(null);
|
|
107
|
+
const [membershipVersion, setMembershipVersion] = useState(0);
|
|
108
|
+
useEffect(()=>{
|
|
109
|
+
if (!scope) return undefined;
|
|
110
|
+
let active = true;
|
|
111
|
+
void getCountFromServer(query(collection(firestore, scope[0], scope[1], 'contacts'), where(CONTACT_COMPANY_IDS_FIELD, 'array-contains', companyId))).then((snapshot)=>{
|
|
112
|
+
if (active) setCount(snapshot.data().count);
|
|
113
|
+
}).catch(()=>{
|
|
114
|
+
if (active) setCount(null);
|
|
115
|
+
});
|
|
116
|
+
return ()=>{
|
|
117
|
+
active = false;
|
|
118
|
+
};
|
|
119
|
+
}, [
|
|
120
|
+
firestore,
|
|
121
|
+
scope,
|
|
122
|
+
companyId,
|
|
123
|
+
membershipVersion
|
|
124
|
+
]);
|
|
125
|
+
const bumpMembership = useCallback(()=>setMembershipVersion((version)=>version + 1), []);
|
|
126
|
+
/*==========================================
|
|
127
|
+
* ADD CONTACT: a search, run on ASK.
|
|
128
|
+
*
|
|
129
|
+
* One text box that decides for itself: an `@` in it is an address and is
|
|
130
|
+
* matched exactly on the normalized email; anything else is a name and is
|
|
131
|
+
* matched as a prefix on `nameLower`. Both are one-shot reads issued when
|
|
132
|
+
* the button is pressed — not a listener per keystroke — because a search
|
|
133
|
+
* is a question the person asked, and each answer costs a page of PII.
|
|
134
|
+
*=========================================*/ const [adding, setAdding] = useState(false);
|
|
135
|
+
const [term, setTerm] = useState('');
|
|
136
|
+
const [searching, setSearching] = useState(false);
|
|
137
|
+
const [results, setResults] = useState(null);
|
|
138
|
+
const [searchError, setSearchError] = useState('');
|
|
139
|
+
const handleSearch = useCallback(async ()=>{
|
|
140
|
+
if (!scope || searching) return;
|
|
141
|
+
const raw = term.trim();
|
|
142
|
+
if (!raw) return;
|
|
143
|
+
setSearching(true);
|
|
144
|
+
setSearchError('');
|
|
145
|
+
try {
|
|
146
|
+
const contactsRef = collection(firestore, scope[0], scope[1], 'contacts');
|
|
147
|
+
// Absent at the organization level, where the tokens are `null` (AGL-2630).
|
|
148
|
+
const scoped = crmVisibleToClause(visibleTo);
|
|
149
|
+
let found;
|
|
150
|
+
if (raw.includes('@')) {
|
|
151
|
+
const email = normalizeContactEmail(raw);
|
|
152
|
+
if (!email) {
|
|
153
|
+
setResults([]);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
found = await getDocs(query(contactsRef, ...scoped, where('email', '==', email), limit(SEARCH_LIMIT)));
|
|
157
|
+
} else {
|
|
158
|
+
const key = nameSearchKey(raw);
|
|
159
|
+
found = await getDocs(query(contactsRef, ...scoped, orderBy('nameLower'), startAt(key), endAt(`${key}${HIGH}`), limit(SEARCH_LIMIT)));
|
|
160
|
+
}
|
|
161
|
+
setResults(found.docs.map((snapshot)=>{
|
|
162
|
+
var _data_email;
|
|
163
|
+
const data = snapshot.data();
|
|
164
|
+
return {
|
|
165
|
+
$id: snapshot.id,
|
|
166
|
+
name: contactDisplayName(data, groupIdOf(data)),
|
|
167
|
+
email: String((_data_email = data['email']) != null ? _data_email : ''),
|
|
168
|
+
linkedByThisHolder: readContactFacet(data, groupIdOf(data)).companyId === companyId,
|
|
169
|
+
data
|
|
170
|
+
};
|
|
171
|
+
}));
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(error);
|
|
174
|
+
setResults(null);
|
|
175
|
+
setSearchError('The search could not be run.');
|
|
176
|
+
} finally{
|
|
177
|
+
setSearching(false);
|
|
178
|
+
}
|
|
179
|
+
}, [
|
|
180
|
+
scope,
|
|
181
|
+
searching,
|
|
182
|
+
term,
|
|
183
|
+
firestore,
|
|
184
|
+
visibleTo,
|
|
185
|
+
groupIdOf,
|
|
186
|
+
companyId
|
|
187
|
+
]);
|
|
188
|
+
const setLink = useCallback(async (row, companyIdOrNull)=>{
|
|
189
|
+
if (!scope) return;
|
|
190
|
+
// Nothing to send when this holder's facet already says what was asked.
|
|
191
|
+
const groupId = groupIdOf(row.data);
|
|
192
|
+
if (!planContactCompanyLink(readContactCompanyLink(row.data, groupId), companyIdOrNull)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
/*
|
|
197
|
+
* The route writes the link with the company's own name beside it, so
|
|
198
|
+
* the person reads as "at Acme" in the contact list and the global
|
|
199
|
+
* search the moment they are linked here and stops when unlinked, and
|
|
200
|
+
* it moves each company's count, so the figure above this table and
|
|
201
|
+
* the one on the companies list cannot disagree.
|
|
202
|
+
*/ await contactUpdate.updateOne(row.$id, {
|
|
203
|
+
companyId: companyIdOrNull
|
|
204
|
+
});
|
|
205
|
+
bumpMembership();
|
|
206
|
+
enqueueSnackbar(companyIdOrNull ? `${row.name || row.email} linked to ${companyName}` : `${row.name || row.email} unlinked`, {
|
|
207
|
+
variant: 'success',
|
|
208
|
+
persist: false
|
|
209
|
+
});
|
|
210
|
+
if (companyIdOrNull) {
|
|
211
|
+
setResults(null);
|
|
212
|
+
setTerm('');
|
|
213
|
+
setAdding(false);
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error(error);
|
|
217
|
+
enqueueSnackbar(error instanceof Error && error.message ? error.message : 'An error has occurred', {
|
|
218
|
+
variant: 'error',
|
|
219
|
+
allowDuplicate: true
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}, [
|
|
223
|
+
scope,
|
|
224
|
+
groupIdOf,
|
|
225
|
+
contactUpdate,
|
|
226
|
+
bumpMembership,
|
|
227
|
+
enqueueSnackbar,
|
|
228
|
+
companyName
|
|
229
|
+
]);
|
|
230
|
+
const denied = status === 'error';
|
|
231
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
232
|
+
header: 'Contacts',
|
|
233
|
+
help: pluginDocsHelp('companies', {
|
|
234
|
+
anchor: '#contacts-at-a-company'
|
|
235
|
+
}),
|
|
236
|
+
contentGutterX: true,
|
|
237
|
+
contentGutterY: true,
|
|
238
|
+
contentBordered: "all",
|
|
239
|
+
HeaderProps: {
|
|
240
|
+
action: /*#__PURE__*/ _jsx(Button, {
|
|
241
|
+
size: "small",
|
|
242
|
+
color: "primary",
|
|
243
|
+
variant: "outlined",
|
|
244
|
+
disabled: !scope,
|
|
245
|
+
startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
246
|
+
path: mdiAccountPlusOutline.path,
|
|
247
|
+
size: 0.8
|
|
248
|
+
}),
|
|
249
|
+
onClick: ()=>setAdding((open)=>!open),
|
|
250
|
+
children: 'Add contact'
|
|
251
|
+
})
|
|
252
|
+
},
|
|
253
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
254
|
+
spacing: 2,
|
|
255
|
+
children: [
|
|
256
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
257
|
+
variant: "body2",
|
|
258
|
+
color: "text.secondary",
|
|
259
|
+
children: count === null ? denied ? '' : 'Counting contacts…' : count === 1 ? '1 contact' : `${count.toLocaleString()} contacts`
|
|
260
|
+
}),
|
|
261
|
+
adding ? /*#__PURE__*/ _jsxs(Stack, {
|
|
262
|
+
spacing: 1,
|
|
263
|
+
children: [
|
|
264
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
265
|
+
direction: "row",
|
|
266
|
+
spacing: 1,
|
|
267
|
+
sx: {
|
|
268
|
+
alignItems: 'center'
|
|
269
|
+
},
|
|
270
|
+
children: [
|
|
271
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
272
|
+
size: "small",
|
|
273
|
+
label: "Find by email or name",
|
|
274
|
+
placeholder: "jane@acme.com or Jane",
|
|
275
|
+
value: term,
|
|
276
|
+
onChange: (event)=>setTerm(event.target.value),
|
|
277
|
+
onKeyDown: (event)=>{
|
|
278
|
+
if (event.key === 'Enter') {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
void handleSearch();
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
sx: {
|
|
284
|
+
flexGrow: 1,
|
|
285
|
+
maxWidth: 360
|
|
286
|
+
}
|
|
287
|
+
}),
|
|
288
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
289
|
+
size: "small",
|
|
290
|
+
variant: "contained",
|
|
291
|
+
disabled: searching || !term.trim(),
|
|
292
|
+
onClick: ()=>void handleSearch(),
|
|
293
|
+
children: searching ? 'Finding…' : 'Find'
|
|
294
|
+
})
|
|
295
|
+
]
|
|
296
|
+
}),
|
|
297
|
+
searchError ? /*#__PURE__*/ _jsx(Alert, {
|
|
298
|
+
severity: "warning",
|
|
299
|
+
children: searchError
|
|
300
|
+
}) : null,
|
|
301
|
+
results && results.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
302
|
+
variant: "body2",
|
|
303
|
+
color: "text.secondary",
|
|
304
|
+
children: 'No contact your site can see matches that.'
|
|
305
|
+
}) : null,
|
|
306
|
+
results == null ? void 0 : results.map((row)=>{
|
|
307
|
+
const elsewhere = readContactFacet(row.data, groupIdOf(row.data)).companyId;
|
|
308
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
309
|
+
direction: "row",
|
|
310
|
+
spacing: 1,
|
|
311
|
+
sx: {
|
|
312
|
+
alignItems: 'center'
|
|
313
|
+
},
|
|
314
|
+
children: [
|
|
315
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
316
|
+
variant: "body2",
|
|
317
|
+
sx: {
|
|
318
|
+
flexGrow: 1
|
|
319
|
+
},
|
|
320
|
+
noWrap: true,
|
|
321
|
+
children: [
|
|
322
|
+
row.name ? `${row.name} · ${row.email}` : row.email,
|
|
323
|
+
elsewhere && elsewhere !== companyId ? /*#__PURE__*/ _jsx(Typography, {
|
|
324
|
+
component: "span",
|
|
325
|
+
variant: "caption",
|
|
326
|
+
color: "text.secondary",
|
|
327
|
+
children: ' — at another company; linking moves them'
|
|
328
|
+
}) : null
|
|
329
|
+
]
|
|
330
|
+
}),
|
|
331
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
332
|
+
size: "small",
|
|
333
|
+
disabled: row.linkedByThisHolder,
|
|
334
|
+
onClick: ()=>void setLink(row, companyId),
|
|
335
|
+
children: row.linkedByThisHolder ? 'Linked' : 'Link'
|
|
336
|
+
})
|
|
337
|
+
]
|
|
338
|
+
}, row.$id);
|
|
339
|
+
})
|
|
340
|
+
]
|
|
341
|
+
}) : null,
|
|
342
|
+
denied ? /*#__PURE__*/ _jsx(Alert, {
|
|
343
|
+
severity: "info",
|
|
344
|
+
children: 'The contacts at this company could not be listed. Your access ' + 'is limited to specific sites, and this list cannot be ' + 'narrowed to them — an organization administrator can see it.'
|
|
345
|
+
}) : contacts.length === 0 && page === 0 ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
|
|
346
|
+
compact: true,
|
|
347
|
+
label: status === 'loading' ? 'Loading contacts…' : 'Nobody is linked to this company yet',
|
|
348
|
+
description: status === 'loading' ? undefined : 'Find a contact by email address or name and link them here.',
|
|
349
|
+
action: status === 'loading' || adding ? undefined : /*#__PURE__*/ _jsx(Button, {
|
|
350
|
+
size: "small",
|
|
351
|
+
variant: "contained",
|
|
352
|
+
color: "primary",
|
|
353
|
+
disabled: !scope,
|
|
354
|
+
startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
355
|
+
path: mdiAccountPlusOutline.path,
|
|
356
|
+
size: 0.8
|
|
357
|
+
}),
|
|
358
|
+
onClick: ()=>setAdding(true),
|
|
359
|
+
children: 'Add contact'
|
|
360
|
+
})
|
|
361
|
+
}) : /*#__PURE__*/ _jsxs(ScrollTable, {
|
|
362
|
+
size: "small",
|
|
363
|
+
children: [
|
|
364
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
365
|
+
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
366
|
+
children: [
|
|
367
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
368
|
+
children: 'Contact'
|
|
369
|
+
}),
|
|
370
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
371
|
+
children: 'Email'
|
|
372
|
+
}),
|
|
373
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
374
|
+
align: "right"
|
|
375
|
+
})
|
|
376
|
+
]
|
|
377
|
+
})
|
|
378
|
+
}),
|
|
379
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
380
|
+
children: contacts.map((row)=>/*#__PURE__*/ _jsxs(TableRow, {
|
|
381
|
+
hover: true,
|
|
382
|
+
children: [
|
|
383
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
384
|
+
children: /*#__PURE__*/ _jsx(AppLink, {
|
|
385
|
+
href: routes.contact(row.$id),
|
|
386
|
+
children: row.name || row.email
|
|
387
|
+
})
|
|
388
|
+
}),
|
|
389
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
390
|
+
children: row.email
|
|
391
|
+
}),
|
|
392
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
393
|
+
align: "right",
|
|
394
|
+
sx: {
|
|
395
|
+
width: 56
|
|
396
|
+
},
|
|
397
|
+
children: row.linkedByThisHolder ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
398
|
+
title: "Unlink from this company",
|
|
399
|
+
children: /*#__PURE__*/ _jsx(IconButton, {
|
|
400
|
+
size: "small",
|
|
401
|
+
"aria-label": `Unlink ${row.name || row.email}`,
|
|
402
|
+
onClick: ()=>void setLink(row, null),
|
|
403
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
404
|
+
path: mdiLinkOff.path,
|
|
405
|
+
size: 0.8
|
|
406
|
+
})
|
|
407
|
+
})
|
|
408
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
409
|
+
variant: "caption",
|
|
410
|
+
color: "text.secondary",
|
|
411
|
+
children: 'Linked by another site'
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
]
|
|
415
|
+
}, row.$id))
|
|
416
|
+
})
|
|
417
|
+
]
|
|
418
|
+
}),
|
|
419
|
+
!denied && (contacts.length > 0 || page > 0) ? /*#__PURE__*/ _jsx(ListPagination, {
|
|
420
|
+
page: page,
|
|
421
|
+
pageSize: pageSize,
|
|
422
|
+
rowCount: contacts.length,
|
|
423
|
+
count: count != null ? count : undefined,
|
|
424
|
+
hasMore: hasMore,
|
|
425
|
+
onPageChange: setPage,
|
|
426
|
+
onPageSizeChange: setPageSize
|
|
427
|
+
}) : null
|
|
428
|
+
]
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
CompanyContactsCard.displayName = 'CompanyContactsCard';
|
|
433
|
+
export default CompanyContactsCard;
|
|
434
|
+
|
|
435
|
+
//# sourceMappingURL=company-contacts-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-contacts-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 contactDisplayName,\n nameSearchKey,\n normalizeContactEmail,\n planContactCompanyLink,\n pluginDocsHelp,\n readContactCompanyLink,\n readContactFacet,\n} from '@aglyn/aglyn'\nimport { mdiAccountPlusOutline, mdiLinkOff } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, usePagedCollection } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n IconButton,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n endAt,\n getCountFromServer,\n getDocs,\n limit,\n orderBy,\n query,\n startAt,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useContactUpdate } from '../hooks/use-contact-update'\nimport { type CrmScope, crmVisibleToClause } from '../hooks/use-crm-scope'\nimport { CONTACT_COMPANY_IDS_FIELD } from '../model/companies'\nimport { contactPrimaryGroup } from '../model/contact-record'\nimport type { CrmRoutes } from '../model/crm-routes'\n\n/** How many matches a search offers to link. */\nconst SEARCH_LIMIT = 8\n\n/** A very high private-use codepoint, closing a prefix range. */\nconst HIGH = '\\uf8ff'\n\nexport interface CompanyContactsCardProps {\n companyId: string\n companyName: string\n crmScope: CrmScope\n /** The org document, for each contact's own holder at the organization level. */\n org?: Record<string, unknown> | null\n routes: CrmRoutes\n}\n\ninterface ContactRow {\n $id: string\n name: string\n email: string\n /** Whether THIS holder's facet is what links the person here. */\n linkedByThisHolder: boolean\n /** Raw document, for the link helper to read the facets from. */\n data: Record<string, unknown>\n}\n\n/**\n * THE PEOPLE AT ONE COMPANY, and the control that puts a person there\n * (AGL-2597).\n *\n * ## What it reads\n *\n * Contacts whose `companyIds` mirror carries this company — see\n * `CONTACT_COMPANY_IDS_FIELD` for why a mirror and not the facet — newest\n * activity first, one page at a time under the shared footer, plus one\n * server aggregate for the total. The aggregate is what the footer counts\n * with: a company's people are a real list, bounded by how many the account\n * has met rather than by any taxonomy, and a page the reader can turn is the\n * only honest answer to the fifty-first. The membership is PII and one\n * document per person, so it is read HERE, where somebody opened the\n * company, and never on the list that names companies.\n *\n * ## The scope caveat, stated rather than hidden\n *\n * The listener cannot carry the `visibleTo` predicate beside the\n * `array-contains` on the mirror — Firestore takes one array clause per\n * query — so the rules admit it only to an org-wide member. A member scoped\n * to particular sites is refused, and the card SAYS so: an empty table for\n * them would read as \"nobody works here\", which is the answer this surface\n * must never give wrongly.\n *\n * ## Linking\n *\n * \"Add contact\" finds a person by email address or by name among the\n * contacts this site may see — those reads DO carry the scope predicate —\n * and links them through `crm/contact-update` (AGL-2804): the link is the\n * holder's facet, which the server writes, keeping the facet, the mirror and\n * the company's count in step. A person already at another company is moved,\n * and the row says so before the click.\n */\nexport function CompanyContactsCard(props: CompanyContactsCardProps) {\n const { companyId, companyName, crmScope, org, routes } = props\n const { scope, consentGroup, visibleTo } = crmScope\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const contactUpdate = useContactUpdate(crmScope.hostId)\n /**\n * The holder a person is read and linked through: the viewing group under\n * a site; at the organization level (AGL-2630) each person's own primary\n * holder, so the link lands on the facet the record page edits.\n */\n const groupIdOf = useCallback(\n (row: Record<string, unknown>) =>\n consentGroup?.groupId ?? contactPrimaryGroup(row, org ?? null).groupId,\n [consentGroup, org],\n )\n\n const {\n rows: contactDocs,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<Record<string, unknown> & { $id: string }>(\n (pageLimit) =>\n scope\n ? query(\n collection(firestore, scope[0], scope[1], 'contacts'),\n where(CONTACT_COMPANY_IDS_FIELD, 'array-contains', companyId),\n orderBy('updatedAt', 'desc'),\n limit(pageLimit),\n )\n : null,\n [firestore, scope, companyId],\n { idField: '$id' },\n )\n const contacts: ContactRow[] = contactDocs.map((row) => ({\n $id: row.$id,\n name: contactDisplayName(row, groupIdOf(row)),\n email: String(row['email'] ?? ''),\n linkedByThisHolder:\n readContactFacet(row, groupIdOf(row)).companyId === companyId,\n data: row,\n }))\n\n /*\n * The total, as a server aggregate, re-taken when a link is made or\n * removed here — a figure that disagreed with the table under it would\n * read as the link having failed.\n */\n const [count, setCount] = useState<number | null>(null)\n const [membershipVersion, setMembershipVersion] = useState(0)\n useEffect(() => {\n if (!scope) return undefined\n let active = true\n void getCountFromServer(\n query(\n collection(firestore, scope[0], scope[1], 'contacts'),\n where(CONTACT_COMPANY_IDS_FIELD, 'array-contains', companyId),\n ),\n )\n .then((snapshot) => {\n if (active) setCount(snapshot.data().count)\n })\n .catch(() => {\n if (active) setCount(null)\n })\n return () => {\n active = false\n }\n }, [firestore, scope, companyId, membershipVersion])\n const bumpMembership = useCallback(\n () => setMembershipVersion((version) => version + 1),\n [],\n )\n\n /*==========================================\n * ADD CONTACT: a search, run on ASK.\n *\n * One text box that decides for itself: an `@` in it is an address and is\n * matched exactly on the normalized email; anything else is a name and is\n * matched as a prefix on `nameLower`. Both are one-shot reads issued when\n * the button is pressed — not a listener per keystroke — because a search\n * is a question the person asked, and each answer costs a page of PII.\n *=========================================*/\n const [adding, setAdding] = useState(false)\n const [term, setTerm] = useState('')\n const [searching, setSearching] = useState(false)\n const [results, setResults] = useState<ContactRow[] | null>(null)\n const [searchError, setSearchError] = useState('')\n\n const handleSearch = useCallback(async () => {\n if (!scope || searching) return\n const raw = term.trim()\n if (!raw) return\n setSearching(true)\n setSearchError('')\n try {\n const contactsRef = collection(firestore, scope[0], scope[1], 'contacts')\n // Absent at the organization level, where the tokens are `null` (AGL-2630).\n const scoped = crmVisibleToClause(visibleTo)\n let found\n if (raw.includes('@')) {\n const email = normalizeContactEmail(raw)\n if (!email) {\n setResults([])\n return\n }\n found = await getDocs(\n query(contactsRef, ...scoped, where('email', '==', email), limit(SEARCH_LIMIT)),\n )\n } else {\n const key = nameSearchKey(raw)\n found = await getDocs(\n query(\n contactsRef,\n ...scoped,\n orderBy('nameLower'),\n startAt(key),\n endAt(`${key}${HIGH}`),\n limit(SEARCH_LIMIT),\n ),\n )\n }\n setResults(\n found.docs.map((snapshot) => {\n const data = snapshot.data()\n return {\n $id: snapshot.id,\n name: contactDisplayName(data, groupIdOf(data)),\n email: String(data['email'] ?? ''),\n linkedByThisHolder:\n readContactFacet(data, groupIdOf(data)).companyId === companyId,\n data,\n }\n }),\n )\n } catch (error) {\n console.error(error)\n setResults(null)\n setSearchError('The search could not be run.')\n } finally {\n setSearching(false)\n }\n }, [scope, searching, term, firestore, visibleTo, groupIdOf, companyId])\n\n const setLink = useCallback(\n async (row: ContactRow, companyIdOrNull: string | null) => {\n if (!scope) return\n // Nothing to send when this holder's facet already says what was asked.\n const groupId = groupIdOf(row.data)\n if (!planContactCompanyLink(readContactCompanyLink(row.data, groupId), companyIdOrNull)) {\n return\n }\n try {\n /*\n * The route writes the link with the company's own name beside it, so\n * the person reads as \"at Acme\" in the contact list and the global\n * search the moment they are linked here and stops when unlinked, and\n * it moves each company's count, so the figure above this table and\n * the one on the companies list cannot disagree.\n */\n await contactUpdate.updateOne(row.$id, { companyId: companyIdOrNull })\n bumpMembership()\n enqueueSnackbar(\n companyIdOrNull\n ? `${row.name || row.email} linked to ${companyName}`\n : `${row.name || row.email} unlinked`,\n { variant: 'success', persist: false },\n )\n if (companyIdOrNull) {\n setResults(null)\n setTerm('')\n setAdding(false)\n }\n } catch (error) {\n console.error(error)\n enqueueSnackbar(\n error instanceof Error && error.message ? error.message : 'An error has occurred',\n { variant: 'error', allowDuplicate: true },\n )\n }\n },\n [scope, groupIdOf, contactUpdate, bumpMembership, enqueueSnackbar, companyName],\n )\n\n const denied = status === 'error'\n\n return (\n <CardDisplay\n header={'Contacts'}\n help={pluginDocsHelp('companies', { anchor: '#contacts-at-a-company' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n disabled={!scope}\n startIcon={<MdiIcon path={mdiAccountPlusOutline.path} size={0.8} />}\n onClick={() => setAdding((open) => !open)}\n >\n {'Add contact'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {count === null\n ? denied\n ? ''\n : 'Counting contacts…'\n : count === 1\n ? '1 contact'\n : `${count.toLocaleString()} contacts`}\n </Typography>\n\n {adding ? (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TextField\n size=\"small\"\n label=\"Find by email or name\"\n placeholder=\"jane@acme.com or Jane\"\n value={term}\n onChange={(event) => setTerm(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n void handleSearch()\n }\n }}\n sx={{ flexGrow: 1, maxWidth: 360 }}\n />\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={searching || !term.trim()}\n onClick={() => void handleSearch()}\n >\n {searching ? 'Finding…' : 'Find'}\n </Button>\n </Stack>\n {searchError ? <Alert severity=\"warning\">{searchError}</Alert> : null}\n {results && results.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No contact your site can see matches that.'}\n </Typography>\n ) : null}\n {results?.map((row) => {\n const elsewhere =\n readContactFacet(row.data, groupIdOf(row.data)).companyId\n return (\n <Stack\n key={row.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }} noWrap>\n {row.name ? `${row.name} · ${row.email}` : row.email}\n {elsewhere && elsewhere !== companyId ? (\n <Typography\n component=\"span\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {' — at another company; linking moves them'}\n </Typography>\n ) : null}\n </Typography>\n <Button\n size=\"small\"\n disabled={row.linkedByThisHolder}\n onClick={() => void setLink(row, companyId)}\n >\n {row.linkedByThisHolder ? 'Linked' : 'Link'}\n </Button>\n </Stack>\n )\n })}\n </Stack>\n ) : null}\n\n {denied ? (\n <Alert severity=\"info\">\n {'The contacts at this company could not be listed. Your access ' +\n 'is limited to specific sites, and this list cannot be ' +\n 'narrowed to them — an organization administrator can see it.'}\n </Alert>\n ) : contacts.length === 0 && page === 0 ? (\n <EmptyStateComponent\n compact\n label={status === 'loading' ? 'Loading contacts…' : 'Nobody is linked to this company yet'}\n description={\n status === 'loading'\n ? undefined\n : 'Find a contact by email address or name and link them here.'\n }\n action={\n status === 'loading' || adding ? undefined : (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={!scope}\n startIcon={<MdiIcon path={mdiAccountPlusOutline.path} size={0.8} />}\n onClick={() => setAdding(true)}\n >\n {'Add contact'}\n </Button>\n )\n }\n />\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Contact'}</TableCell>\n <TableCell>{'Email'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {contacts.map((row) => (\n <TableRow key={row.$id} hover>\n <TableCell>\n <AppLink href={routes.contact(row.$id)}>\n {row.name || row.email}\n </AppLink>\n </TableCell>\n <TableCell>{row.email}</TableCell>\n <TableCell align=\"right\" sx={{ width: 56 }}>\n {/*\n Unlinking is offered only where THIS holder's facet is\n the link. A person here through another site's filing\n is that site's record to change, and the mirror would\n keep them here anyway.\n */}\n {row.linkedByThisHolder ? (\n <Tooltip title=\"Unlink from this company\">\n <IconButton\n size=\"small\"\n aria-label={`Unlink ${row.name || row.email}`}\n onClick={() => void setLink(row, null)}\n >\n <MdiIcon path={mdiLinkOff.path} size={0.8} />\n </IconButton>\n </Tooltip>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Linked by another site'}\n </Typography>\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n {/*\n The footer counts with the server aggregate when it has arrived —\n the one number this card knows that the paged window does not —\n and with the probe row until then.\n */}\n {!denied && (contacts.length > 0 || page > 0) ? (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={contacts.length}\n count={count ?? undefined}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nCompanyContactsCard.displayName = 'CompanyContactsCard'\n\nexport default CompanyContactsCard\n"],"names":["contactDisplayName","nameSearchKey","normalizeContactEmail","planContactCompanyLink","pluginDocsHelp","readContactCompanyLink","readContactFacet","mdiAccountPlusOutline","mdiLinkOff","AppLink","CardDisplay","MdiIcon","ListPagination","EmptyStateComponent","ScrollTable","useSnackbar","useFirestore","usePagedCollection","Alert","Button","IconButton","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Tooltip","Typography","collection","endAt","getCountFromServer","getDocs","limit","orderBy","query","startAt","where","useCallback","useEffect","useState","useContactUpdate","crmVisibleToClause","CONTACT_COMPANY_IDS_FIELD","contactPrimaryGroup","SEARCH_LIMIT","HIGH","CompanyContactsCard","props","companyId","companyName","crmScope","org","routes","scope","consentGroup","visibleTo","firestore","enqueueSnackbar","contactUpdate","hostId","groupIdOf","row","groupId","rows","contactDocs","status","hasMore","page","setPage","pageSize","setPageSize","pageLimit","idField","contacts","map","$id","name","email","String","linkedByThisHolder","data","count","setCount","membershipVersion","setMembershipVersion","undefined","active","then","snapshot","catch","bumpMembership","version","adding","setAdding","term","setTerm","searching","setSearching","results","setResults","searchError","setSearchError","handleSearch","raw","trim","contactsRef","scoped","found","includes","key","docs","id","error","console","setLink","companyIdOrNull","updateOne","variant","persist","Error","message","allowDuplicate","denied","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","size","color","disabled","startIcon","path","onClick","open","spacing","toLocaleString","direction","sx","alignItems","label","placeholder","value","onChange","event","target","onKeyDown","preventDefault","flexGrow","maxWidth","severity","length","elsewhere","noWrap","component","compact","description","align","hover","href","contact","width","title","aria-label","rowCount","onPageChange","onPageSizeChange","displayName"],"mappings":"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"}
|