@aglyn/plugins-crm 1.0.0-beta.218 → 1.0.0-beta.219
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/package.json +14 -13
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.js +12 -4
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/company-detail-page.js +11 -0
- package/src/lib/components/company-detail-page.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +12 -2
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-import-drawer.d.ts +1 -1
- package/src/lib/components/company-picker.js +10 -3
- package/src/lib/components/company-picker.js.map +1 -1
- package/src/lib/components/contact-detail-page.d.ts +4 -4
- package/src/lib/components/contact-detail-page.js +50 -53
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-list-columns.d.ts +3 -0
- package/src/lib/components/contact-list-columns.js +21 -5
- package/src/lib/components/contact-list-columns.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +2 -1
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-timeline-card.js +4 -3
- package/src/lib/components/contact-timeline-card.js.map +1 -1
- package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
- package/src/lib/components/contacts-bulk-bar.js +24 -51
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +9 -3
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-export-all-button.d.ts +1 -1
- package/src/lib/components/crm-export-all-button.js.map +1 -1
- package/src/lib/components/crm-send-email-dialog.js +9 -6
- package/src/lib/components/crm-send-email-dialog.js.map +1 -1
- package/src/lib/components/csv-import-drawer.js +2 -2
- package/src/lib/components/csv-import-drawer.js.map +1 -1
- package/src/lib/components/deal-detail-page.js +18 -2
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +10 -0
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.js +13 -5
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.js +6 -0
- package/src/lib/components/lead-campaigns-card.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +25 -5
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +6 -0
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-surfaces-note.js +4 -5
- package/src/lib/components/lead-surfaces-note.js.map +1 -1
- package/src/lib/components/lead-unqualify-dialog.js +6 -0
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.js +28 -3
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +36 -7
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/org-lead-surfaces-note.js +6 -2
- package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
- package/src/lib/components/recipes-card.js +9 -4
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-sharing-card.d.ts +57 -0
- package/src/lib/components/record-sharing-card.js +244 -0
- package/src/lib/components/record-sharing-card.js.map +1 -0
- package/src/lib/components/settings-section.js +7 -0
- package/src/lib/components/settings-section.js.map +1 -1
- package/src/lib/components/share-records-dialog.d.ts +28 -0
- package/src/lib/components/share-records-dialog.js +184 -0
- package/src/lib/components/share-records-dialog.js.map +1 -0
- package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
- package/src/lib/components/sharing-rule-drawer.js +374 -0
- package/src/lib/components/sharing-rule-drawer.js.map +1 -0
- package/src/lib/components/sharing-rules-card.d.ts +24 -0
- package/src/lib/components/sharing-rules-card.js +287 -0
- package/src/lib/components/sharing-rules-card.js.map +1 -0
- package/src/lib/components/use-crm-api.d.ts +1 -1
- package/src/lib/components/use-crm-api.js.map +1 -1
- package/src/lib/components/use-site-route-context.d.ts +25 -0
- package/src/lib/components/use-site-route-context.js +58 -0
- package/src/lib/components/use-site-route-context.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +31 -0
- package/src/lib/constants/api-routes.js +27 -1
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +37 -0
- package/src/lib/declarations.console-server.js +103 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/declarations.server.d.ts +6 -0
- package/src/lib/declarations.server.js +33 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-contact-remove.d.ts +35 -0
- package/src/lib/hooks/use-contact-remove.js +77 -0
- package/src/lib/hooks/use-contact-remove.js.map +1 -0
- package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
- package/src/lib/hooks/use-crm-sharing.js +109 -0
- package/src/lib/hooks/use-crm-sharing.js.map +1 -0
- package/src/lib/model/companies-csv.d.ts +1 -1
- package/src/lib/model/companies-csv.js +2 -2
- package/src/lib/model/companies-csv.js.map +1 -1
- package/src/lib/model/contact-holder.d.ts +68 -0
- package/src/lib/model/contact-holder.js +129 -0
- package/src/lib/model/contact-holder.js.map +1 -0
- package/src/lib/model/contact-interaction-links.d.ts +46 -0
- package/src/lib/model/contact-interaction-links.js +65 -0
- package/src/lib/model/contact-interaction-links.js.map +1 -0
- package/src/lib/model/contact-merge.d.ts +136 -0
- package/src/lib/model/contact-merge.js +412 -0
- package/src/lib/model/contact-merge.js.map +1 -0
- package/src/lib/model/contact-record.d.ts +7 -1
- package/src/lib/model/contact-record.js +6 -5
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-remove.d.ts +48 -0
- package/src/lib/model/contact-remove.js +22 -0
- package/src/lib/model/contact-remove.js.map +1 -0
- package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
- package/src/lib/model/contacts-bulk-writes.js +2 -82
- package/src/lib/model/contacts-bulk-writes.js.map +1 -1
- package/src/lib/model/contacts-csv.d.ts +2 -2
- package/src/lib/model/contacts-csv.js +2 -2
- package/src/lib/model/contacts-csv.js.map +1 -1
- package/src/lib/model/crm-csv.d.ts +239 -0
- package/src/lib/model/crm-csv.js +467 -0
- package/src/lib/model/crm-csv.js.map +1 -0
- package/src/lib/model/crm-digest.d.ts +185 -0
- package/src/lib/model/crm-digest.js +272 -0
- package/src/lib/model/crm-digest.js.map +1 -0
- package/src/lib/model/crm-import.d.ts +7 -1
- package/src/lib/model/crm-import.js +2 -1
- package/src/lib/model/crm-import.js.map +1 -1
- package/src/lib/model/crm-record-routes.d.ts +5 -0
- package/src/lib/model/crm-record-routes.js +7 -0
- package/src/lib/model/crm-record-routes.js.map +1 -1
- package/src/lib/model/crm-routes.d.ts +6 -5
- package/src/lib/model/crm-routes.js +7 -7
- package/src/lib/model/crm-routes.js.map +1 -1
- package/src/lib/model/crm-search-sources.d.ts +15 -0
- package/src/lib/model/crm-search-sources.js +162 -0
- package/src/lib/model/crm-search-sources.js.map +1 -0
- package/src/lib/model/crm-sharing-api.d.ts +32 -0
- package/src/lib/model/crm-sharing-api.js +59 -0
- package/src/lib/model/crm-sharing-api.js.map +1 -0
- package/src/lib/model/crm-sharing.d.ts +333 -0
- package/src/lib/model/crm-sharing.js +603 -0
- package/src/lib/model/crm-sharing.js.map +1 -0
- package/src/lib/model/deals-csv.d.ts +1 -1
- package/src/lib/model/deals-csv.js +4 -5
- package/src/lib/model/deals-csv.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +1 -1
- package/src/lib/model/leads-csv.d.ts +1 -1
- package/src/lib/model/leads-csv.js +1 -1
- package/src/lib/model/leads-csv.js.map +1 -1
- package/src/lib/model/tasks-csv.d.ts +1 -1
- package/src/lib/model/tasks-csv.js +1 -1
- package/src/lib/model/tasks-csv.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +75 -0
- package/src/lib/plan-entitlements.js +56 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +3 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
- package/src/lib/server/api-v1/contacts-merge.js +120 -0
- package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
- package/src/lib/server/api-v1/contacts.d.ts +18 -0
- package/src/lib/server/api-v1/contacts.js +1076 -0
- package/src/lib/server/api-v1/contacts.js.map +1 -0
- package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
- package/src/lib/server/api-v1/crm-activities.js +269 -0
- package/src/lib/server/api-v1/crm-activities.js.map +1 -0
- package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
- package/src/lib/server/api-v1/crm-companies.js +391 -0
- package/src/lib/server/api-v1/crm-companies.js.map +1 -0
- package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
- package/src/lib/server/api-v1/crm-deals.js +631 -0
- package/src/lib/server/api-v1/crm-deals.js.map +1 -0
- package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
- package/src/lib/server/api-v1/crm-email-templates.js +336 -0
- package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
- package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
- package/src/lib/server/api-v1/crm-leads.js +832 -0
- package/src/lib/server/api-v1/crm-leads.js.map +1 -0
- package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
- package/src/lib/server/api-v1/crm-pipelines.js +146 -0
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
- package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
- package/src/lib/server/api-v1/crm-shared.js +436 -0
- package/src/lib/server/api-v1/crm-shared.js.map +1 -0
- package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
- package/src/lib/server/api-v1/crm-tasks.js +398 -0
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
- package/src/lib/server/api-v1/openapi.d.ts +29 -0
- package/src/lib/server/api-v1/openapi.js +985 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -0
- package/src/lib/server/api-v1/usage.d.ts +26 -0
- package/src/lib/server/api-v1/usage.js +73 -0
- package/src/lib/server/api-v1/usage.js.map +1 -0
- package/src/lib/server/consent-group-participant.js +1 -1
- package/src/lib/server/consent-group-participant.js.map +1 -1
- package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
- package/src/lib/server/contact-lifecycle-floor.js +91 -0
- package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
- package/src/lib/server/contact-remove.d.ts +75 -0
- package/src/lib/server/contact-remove.js +244 -0
- package/src/lib/server/contact-remove.js.map +1 -0
- package/src/lib/server/contact-update.js +2 -1
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/contacts-import.d.ts +47 -11
- package/src/lib/server/contacts-import.js +171 -71
- package/src/lib/server/contacts-import.js.map +1 -1
- package/src/lib/server/contacts-merge.d.ts +1 -1
- package/src/lib/server/contacts-merge.js +3 -2
- package/src/lib/server/contacts-merge.js.map +1 -1
- package/src/lib/server/convert-host-lead.d.ts +189 -0
- package/src/lib/server/convert-host-lead.js +518 -0
- package/src/lib/server/convert-host-lead.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +108 -0
- package/src/lib/server/crm-sharing.js +790 -0
- package/src/lib/server/crm-sharing.js.map +1 -0
- package/src/lib/server/daily-digest-route.d.ts +107 -0
- package/src/lib/server/daily-digest-route.js +588 -0
- package/src/lib/server/daily-digest-route.js.map +1 -0
- package/src/lib/server/export-route.d.ts +60 -0
- package/src/lib/server/export-route.js +447 -0
- package/src/lib/server/export-route.js.map +1 -0
- package/src/lib/server/inbound-route.d.ts +74 -0
- package/src/lib/server/inbound-route.js +257 -0
- package/src/lib/server/inbound-route.js.map +1 -0
- package/src/lib/server/lead-convert.d.ts +1 -1
- package/src/lib/server/lead-convert.js +2 -2
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/merge-contacts.d.ts +55 -0
- package/src/lib/server/merge-contacts.js +274 -0
- package/src/lib/server/merge-contacts.js.map +1 -0
- package/src/lib/server/pipeline-record-index.d.ts +36 -0
- package/src/lib/server/pipeline-record-index.js +87 -0
- package/src/lib/server/pipeline-record-index.js.map +1 -0
- package/src/lib/server/record-facts.js +2 -1
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/task-reminders-route.d.ts +82 -0
- package/src/lib/server/task-reminders-route.js +497 -0
- package/src/lib/server/task-reminders-route.js.map +1 -0
- package/src/lib/server-deal-stage.js +2 -1
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +5 -2
- package/src/lib/server.js +47 -2
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
- package/src/lib/testing/stand-in-site-record-routes.js +55 -0
- package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
- package/src/lib/usage-axes.d.ts +35 -0
- package/src/lib/usage-axes.js +68 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +0 -24
- package/src/lib/components/use-emails-hub-path.js +0 -59
- package/src/lib/components/use-emails-hub-path.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-crm",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.219",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,18 +25,19 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
37
|
-
"@aglyn/
|
|
38
|
-
"@aglyn/tenant-
|
|
39
|
-
"@aglyn/tenant-
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.219",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.219",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.219",
|
|
31
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.219",
|
|
32
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.219",
|
|
33
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.219",
|
|
34
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.219",
|
|
35
|
+
"@aglyn/shared-util-email": "1.0.0-beta.219",
|
|
36
|
+
"@aglyn/shared-util-http": "1.0.0-beta.219",
|
|
37
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.219",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.219",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.219",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.219",
|
|
40
41
|
"@dnd-kit/core": "^6.3.1",
|
|
41
42
|
"@dnd-kit/utilities": "^3.2.2",
|
|
42
43
|
"@swc/helpers": "0.5.23",
|
package/src/index.d.ts
CHANGED
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../libs/plugins/crm/src/index.ts"],"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\nexport * from './lib/constants/bundle-common'\nexport * from './lib/plugin'\n// The hub's section ids and the one way to link into it (AGL-2595), so a\n// surface outside this plugin — a campaign naming a contact, an order\n// naming a company — builds the same address the hub resolves.\nexport * from './lib/components/crm-console-sections'\nexport * from './lib/model/crm-routes'\n// The org's custom contact field definitions, read once and shared\n// (AGL-2601): the contact list's columns, the profile card, the form editor's\n// mapping picker, an import's target list and an audience filter all ask the\n// same question of the same small collection.\nexport * from './lib/hooks/use-contact-field-definitions'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,cAAc,mCAA+B;AAC7C,cAAc,kBAAc;AAC5B,yEAAyE;AACzE,sEAAsE;AACtE,+DAA+D;AAC/D,cAAc,2CAAuC;AACrD,cAAc,4BAAwB;AACtC,mEAAmE;AACnE,8EAA8E;AAC9E,6EAA6E;AAC7E,8CAA8C;AAC9C,cAAc,+CAA2C"}
|
|
1
|
+
{"version":3,"sources":["../../../../../libs/plugins/crm/src/index.ts"],"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\nexport * from './lib/constants/bundle-common'\nexport * from './lib/plugin'\n// The hub's section ids and the one way to link into it (AGL-2595), so a\n// surface outside this plugin — a campaign naming a contact, an order\n// naming a company — builds the same address the hub resolves.\nexport * from './lib/components/crm-console-sections'\nexport * from './lib/model/crm-routes'\n// The org's custom contact field definitions, read once and shared\n// (AGL-2601): the contact list's columns, the profile card, the form editor's\n// mapping picker, an import's target list and an audience filter all ask the\n// same question of the same small collection.\nexport * from './lib/hooks/use-contact-field-definitions'\n// Type-only (AGL-3080): the plugin's entitlement keys are declared by module\n// augmentation in this module, so every program that loads the package\n// type-checks them. The figures reach core through the manifest generator.\nexport type { crmPlanEntitlements } from './lib/plan-entitlements'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,cAAc,mCAA+B;AAC7C,cAAc,kBAAc;AAC5B,yEAAyE;AACzE,sEAAsE;AACtE,+DAA+D;AAC/D,cAAc,2CAAuC;AACrD,cAAc,4BAAwB;AACtC,mEAAmE;AACnE,8EAA8E;AAC9E,6EAA6E;AAC7E,8CAA8C;AAC9C,cAAc,+CAA2C"}
|
|
@@ -51,6 +51,7 @@ import { crmBulkWriters, runCrmBulkCalls, runCrmBulkWrites } from "../model/crm-
|
|
|
51
51
|
import { CrmBulkBarFrame, CrmBulkValueDialog, countNoun } from "./crm-bulk-bar-frame.js";
|
|
52
52
|
import CrmExportAllButton from "./crm-export-all-button.js";
|
|
53
53
|
import { useCrmApi } from "./use-crm-api.js";
|
|
54
|
+
import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
|
|
54
55
|
const NOUN = {
|
|
55
56
|
singular: 'company',
|
|
56
57
|
plural: 'companies'
|
|
@@ -80,7 +81,7 @@ const doneSentence = (action, done)=>{
|
|
|
80
81
|
}
|
|
81
82
|
CompaniesBulkBar.displayName = 'CompaniesBulkBar';
|
|
82
83
|
function CompaniesBulkBarBody(props) {
|
|
83
|
-
var _members_error,
|
|
84
|
+
var _ref, _members_error, _ref1;
|
|
84
85
|
const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props;
|
|
85
86
|
const firestore = useFirestore();
|
|
86
87
|
const { confirm } = useConfirmationContext();
|
|
@@ -89,6 +90,7 @@ function CompaniesBulkBarBody(props) {
|
|
|
89
90
|
const { busy, report, apply, dismissReport } = useCrmBulkApply({
|
|
90
91
|
recordKind: 'company'
|
|
91
92
|
});
|
|
93
|
+
const followUpSharing = useCrmSharingFollowUp(hostId != null ? hostId : null, (_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null);
|
|
92
94
|
const selectedRows = useMemo(()=>{
|
|
93
95
|
const chosen = new Set(selected);
|
|
94
96
|
return rows.filter((row)=>chosen.has(row.$id));
|
|
@@ -112,11 +114,17 @@ function CompaniesBulkBarBody(props) {
|
|
|
112
114
|
const runPlan = useCallback((action, plan)=>apply({
|
|
113
115
|
attempted: plan.writes.length,
|
|
114
116
|
skipped: plan.skipped,
|
|
115
|
-
job:
|
|
117
|
+
job: async ()=>{
|
|
118
|
+
const outcome = await runCrmBulkWrites(writers, plan.writes, (write)=>write.label);
|
|
119
|
+
// A client-direct write owes the sharing rules a re-evaluation (AGL-3336).
|
|
120
|
+
followUpSharing('companies', plan.writes.filter((write)=>write.kind === 'update').map((write)=>write.id));
|
|
121
|
+
return outcome;
|
|
122
|
+
},
|
|
116
123
|
done: (count)=>doneSentence(action, count)
|
|
117
124
|
}), [
|
|
118
125
|
apply,
|
|
119
|
-
writers
|
|
126
|
+
writers,
|
|
127
|
+
followUpSharing
|
|
120
128
|
]);
|
|
121
129
|
const handleApply = useCallback(async ()=>{
|
|
122
130
|
if (!pending || !scope) return;
|
|
@@ -254,7 +262,7 @@ function CompaniesBulkBarBody(props) {
|
|
|
254
262
|
}),
|
|
255
263
|
/*#__PURE__*/ _jsx(CrmExportAllButton, {
|
|
256
264
|
resource: "companies",
|
|
257
|
-
orgId: (
|
|
265
|
+
orgId: (_ref1 = scope == null ? void 0 : scope[1]) != null ? _ref1 : null,
|
|
258
266
|
hostId: hostId,
|
|
259
267
|
disabled: busy
|
|
260
268
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-bulk-bar.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\n/**\n * The bar over the companies table, for whatever rows are ticked\n * (AGL-2621).\n *\n * Tag or untag them, hand them to an owner, take them into a spreadsheet,\n * or delete them. The patches come from `companies-bulk-writes.ts` and go\n * through the shared runner; the delete is the record page's own\n * `crm/company-delete`, per company, one after another — the route unlinks a\n * company's contacts before it goes, and a company past the detach bound is\n * left standing and NAMED, the way the page says it, so a second delete\n * continues where this one stopped.\n *\n * ## The delete is logged, per company\n *\n * Each company that goes is one line in the site's activity feed, as it\n * would be from its own page. A bulk delete of forty companies is forty\n * acts and forty lines; a single \"deleted 40 companies\" line would name\n * none of them, and the feed exists to answer \"where did Acme go\".\n */\n\nimport { CRM_COLLECTIONS, crmMemberPickerLabel } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useHostActivityLogger } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyBulkRow,\n planCompanyAddTag,\n planCompanyRemoveTag,\n planCompanySetOwner,\n} from '../model/companies-bulk-writes'\nimport {\n type CompanyCsvOptions,\n type CompanyCsvRow,\n companiesCsv,\n} from '../model/companies-csv'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport { deleteCompanyThroughRoute } from '../model/company-delete'\nimport { normalizeBulkTag } from '../model/contacts-bulk-writes'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport {\n type CrmBulkPlan,\n crmBulkWriters,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport CrmExportAllButton from './crm-export-all-button'\nimport { useCrmApi } from './use-crm-api'\n\nexport interface CompaniesBulkBarProps {\n /**\n * The site whose activity feed the bar's acts are logged in, or `null`\n * at the organization level (AGL-2630), where the selection spans sites\n * and no one feed is written.\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly ['orgs', string] | null\n rows: readonly (CompanyBulkRow & CompanyCsvRow)[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Owner column. */\n members: OrgMemberOptions\n /** How the export names an owner — the table's own options. */\n csv?: CompanyCsvOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'company', plural: 'companies' }\n\ntype PendingAction = 'add-tag' | 'remove-tag' | 'owner'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n 'add-tag': 'Add a tag',\n 'remove-tag': 'Remove a tag',\n owner: 'Set the owner',\n}\n\nconst doneSentence = (action: PendingAction, done: number): string => {\n const rows = countNoun(done, NOUN)\n switch (action) {\n case 'add-tag':\n return `Tagged ${rows}`\n case 'remove-tag':\n return `Removed the tag from ${rows}`\n case 'owner':\n return `Owner set on ${rows}`\n }\n}\n\n/**\n * The bar, or nothing — a separate boundary so the body's hooks never run\n * on the visit to the list that ticks no row.\n */\nexport function CompaniesBulkBar(props: CompaniesBulkBarProps) {\n if (!props.selected.length) return null\n return <CompaniesBulkBarBody {...props} />\n}\nCompaniesBulkBar.displayName = 'CompaniesBulkBar'\n\nfunction CompaniesBulkBarBody(props: CompaniesBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props\n const firestore = useFirestore()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId ?? undefined)\n const callCrm = useCrmApi(hostId)\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'company' })\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.companies, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n const runPlan = useCallback(\n (action: PendingAction, plan: CrmBulkPlan) =>\n apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: () => runCrmBulkWrites(writers, plan.writes, (write) => write.label),\n done: (count) => doneSentence(action, count),\n }),\n [apply, writers],\n )\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const nowMs = Date.now()\n const tag = normalizeBulkTag(value)\n if (pending !== 'owner' && !tag) return\n const plan: CrmBulkPlan =\n pending === 'add-tag'\n ? planCompanyAddTag(selectedRows, tag ?? '', nowMs)\n : pending === 'remove-tag'\n ? planCompanyRemoveTag(selectedRows, tag ?? '', nowMs)\n : planCompanySetOwner(selectedRows, value || null, nowMs)\n const action = pending\n setPending(null)\n await runPlan(action, plan)\n }, [pending, scope, value, selectedRows, runPlan])\n\n const handleExport = useCallback(() => {\n downloadTextFile('companies-selected.csv', 'text/csv', companiesCsv(selectedRows, csv))\n }, [selectedRows, csv])\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this company?' : `Delete ${count} companies?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed from Companies and ` +\n 'unlinked from every contact at them. The contacts themselves are ' +\n 'kept, and so is anything else filed against the companies.',\n confirmationText: count === 1 ? 'Delete company' : 'Delete companies',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const outcome = await apply({\n attempted: count,\n skipped: [],\n job: () =>\n runCrmBulkCalls(\n selectedRows,\n (row) => String(row.name || row.$id),\n async (row) => {\n const result = await deleteCompanyThroughRoute(callCrm, row.$id)\n if (!result.deleted) {\n throw new Error(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n 'and more remain — delete again to continue',\n )\n }\n logActivity('Deleted company', {\n type: 'company',\n id: row.$id,\n name: row.name,\n })\n },\n ),\n done: (done) => `Deleted ${countNoun(done, NOUN)}`,\n })\n // The rows that went are gone from the table; the refused ones stay\n // selected, so the reader can see which they are and try again.\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows\n .filter((row) => refused.has(String(row.name || row.$id)))\n .map((row) => row.$id),\n )\n }, [scope, selectedRows, confirm, apply, callCrm, logActivity, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply={pending === 'owner' || Boolean(normalizeBulkTag(value))}\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'add-tag' || pending === 'remove-tag' ? (\n <TextField\n autoFocus\n size=\"small\"\n label=\"Tag\"\n placeholder=\"enterprise\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText={\n pending === 'add-tag'\n ? 'Lowercased, like the tags on a company'\n : 'Removed wherever it is present'\n }\n />\n ) : pending === 'owner' ? (\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={!members.ready}\n error={Boolean(members.error)}\n helperText={members.error ?? (members.ready ? undefined : 'Loading the team…')}\n >\n <MenuItem value=\"\">{'Nobody — clear the owner'}</MenuItem>\n {members.options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('add-tag')}>\n {'Add tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('remove-tag')}>\n {'Remove tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('owner')}>\n {'Set owner'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={handleExport}>\n {'Export CSV'}\n </Button>\n {/*\n The selection's file is the rows on screen; this one is the whole\n collection, streamed by the server (AGL-2662).\n */}\n <CrmExportAllButton\n resource=\"companies\"\n orgId={scope?.[1] ?? null}\n hostId={hostId}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nCompaniesBulkBarBody.displayName = 'CompaniesBulkBarBody'\n\nexport default CompaniesBulkBar\n"],"names":["CRM_COLLECTIONS","crmMemberPickerLabel","useConfirmationContext","useFirestore","useHostActivityLogger","Button","MenuItem","TextField","doc","useCallback","useMemo","useState","useCrmBulkApply","planCompanyAddTag","planCompanyRemoveTag","planCompanySetOwner","companiesCsv","COMPANY_DETACH_LIMIT","deleteCompanyThroughRoute","normalizeBulkTag","downloadTextFile","crmBulkWriters","runCrmBulkCalls","runCrmBulkWrites","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportAllButton","useCrmApi","NOUN","singular","plural","ACTION_TITLES","owner","doneSentence","action","done","rows","CompaniesBulkBar","props","selected","length","CompaniesBulkBarBody","displayName","members","hostId","scope","onSelectedChange","csv","firestore","confirm","logActivity","undefined","callCrm","busy","report","apply","dismissReport","recordKind","selectedRows","chosen","Set","filter","row","has","$id","pending","setPending","value","setValue","writers","id","companies","openAction","runPlan","plan","attempted","writes","skipped","job","write","label","count","handleApply","nowMs","Date","now","tag","handleExport","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","outcome","String","name","result","deleted","Error","toLocaleString","type","refused","map","noun","onClear","onDismissReport","extras","open","canApply","Boolean","onClose","onApply","autoFocus","size","placeholder","onChange","event","target","helperText","select","disabled","ready","error","options","member","uid","onClick","resource","orgId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-bulk-bar.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\n/**\n * The bar over the companies table, for whatever rows are ticked\n * (AGL-2621).\n *\n * Tag or untag them, hand them to an owner, take them into a spreadsheet,\n * or delete them. The patches come from `companies-bulk-writes.ts` and go\n * through the shared runner; the delete is the record page's own\n * `crm/company-delete`, per company, one after another — the route unlinks a\n * company's contacts before it goes, and a company past the detach bound is\n * left standing and NAMED, the way the page says it, so a second delete\n * continues where this one stopped.\n *\n * ## The delete is logged, per company\n *\n * Each company that goes is one line in the site's activity feed, as it\n * would be from its own page. A bulk delete of forty companies is forty\n * acts and forty lines; a single \"deleted 40 companies\" line would name\n * none of them, and the feed exists to answer \"where did Acme go\".\n */\n\nimport { CRM_COLLECTIONS, crmMemberPickerLabel } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useHostActivityLogger } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyBulkRow,\n planCompanyAddTag,\n planCompanyRemoveTag,\n planCompanySetOwner,\n} from '../model/companies-bulk-writes'\nimport {\n type CompanyCsvOptions,\n type CompanyCsvRow,\n companiesCsv,\n} from '../model/companies-csv'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport { deleteCompanyThroughRoute } from '../model/company-delete'\nimport { normalizeBulkTag } from '../model/contacts-bulk-writes'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport {\n type CrmBulkPlan,\n crmBulkWriters,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport CrmExportAllButton from './crm-export-all-button'\nimport { useCrmApi } from './use-crm-api'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\nexport interface CompaniesBulkBarProps {\n /**\n * The site whose activity feed the bar's acts are logged in, or `null`\n * at the organization level (AGL-2630), where the selection spans sites\n * and no one feed is written.\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly ['orgs', string] | null\n rows: readonly (CompanyBulkRow & CompanyCsvRow)[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Owner column. */\n members: OrgMemberOptions\n /** How the export names an owner — the table's own options. */\n csv?: CompanyCsvOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'company', plural: 'companies' }\n\ntype PendingAction = 'add-tag' | 'remove-tag' | 'owner'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n 'add-tag': 'Add a tag',\n 'remove-tag': 'Remove a tag',\n owner: 'Set the owner',\n}\n\nconst doneSentence = (action: PendingAction, done: number): string => {\n const rows = countNoun(done, NOUN)\n switch (action) {\n case 'add-tag':\n return `Tagged ${rows}`\n case 'remove-tag':\n return `Removed the tag from ${rows}`\n case 'owner':\n return `Owner set on ${rows}`\n }\n}\n\n/**\n * The bar, or nothing — a separate boundary so the body's hooks never run\n * on the visit to the list that ticks no row.\n */\nexport function CompaniesBulkBar(props: CompaniesBulkBarProps) {\n if (!props.selected.length) return null\n return <CompaniesBulkBarBody {...props} />\n}\nCompaniesBulkBar.displayName = 'CompaniesBulkBar'\n\nfunction CompaniesBulkBarBody(props: CompaniesBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props\n const firestore = useFirestore()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId ?? undefined)\n const callCrm = useCrmApi(hostId)\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'company' })\n const followUpSharing = useCrmSharingFollowUp(hostId ?? null, scope?.[1] ?? null)\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.companies, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n const runPlan = useCallback(\n (action: PendingAction, plan: CrmBulkPlan) =>\n apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: async () => {\n const outcome = await runCrmBulkWrites(writers, plan.writes, (write) => write.label)\n // A client-direct write owes the sharing rules a re-evaluation (AGL-3336).\n followUpSharing(\n 'companies',\n plan.writes.filter((write) => write.kind === 'update').map((write) => write.id),\n )\n return outcome\n },\n done: (count) => doneSentence(action, count),\n }),\n [apply, writers, followUpSharing],\n )\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const nowMs = Date.now()\n const tag = normalizeBulkTag(value)\n if (pending !== 'owner' && !tag) return\n const plan: CrmBulkPlan =\n pending === 'add-tag'\n ? planCompanyAddTag(selectedRows, tag ?? '', nowMs)\n : pending === 'remove-tag'\n ? planCompanyRemoveTag(selectedRows, tag ?? '', nowMs)\n : planCompanySetOwner(selectedRows, value || null, nowMs)\n const action = pending\n setPending(null)\n await runPlan(action, plan)\n }, [pending, scope, value, selectedRows, runPlan])\n\n const handleExport = useCallback(() => {\n downloadTextFile('companies-selected.csv', 'text/csv', companiesCsv(selectedRows, csv))\n }, [selectedRows, csv])\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this company?' : `Delete ${count} companies?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed from Companies and ` +\n 'unlinked from every contact at them. The contacts themselves are ' +\n 'kept, and so is anything else filed against the companies.',\n confirmationText: count === 1 ? 'Delete company' : 'Delete companies',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const outcome = await apply({\n attempted: count,\n skipped: [],\n job: () =>\n runCrmBulkCalls(\n selectedRows,\n (row) => String(row.name || row.$id),\n async (row) => {\n const result = await deleteCompanyThroughRoute(callCrm, row.$id)\n if (!result.deleted) {\n throw new Error(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n 'and more remain — delete again to continue',\n )\n }\n logActivity('Deleted company', {\n type: 'company',\n id: row.$id,\n name: row.name,\n })\n },\n ),\n done: (done) => `Deleted ${countNoun(done, NOUN)}`,\n })\n // The rows that went are gone from the table; the refused ones stay\n // selected, so the reader can see which they are and try again.\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows\n .filter((row) => refused.has(String(row.name || row.$id)))\n .map((row) => row.$id),\n )\n }, [scope, selectedRows, confirm, apply, callCrm, logActivity, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply={pending === 'owner' || Boolean(normalizeBulkTag(value))}\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'add-tag' || pending === 'remove-tag' ? (\n <TextField\n autoFocus\n size=\"small\"\n label=\"Tag\"\n placeholder=\"enterprise\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText={\n pending === 'add-tag'\n ? 'Lowercased, like the tags on a company'\n : 'Removed wherever it is present'\n }\n />\n ) : pending === 'owner' ? (\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={!members.ready}\n error={Boolean(members.error)}\n helperText={members.error ?? (members.ready ? undefined : 'Loading the team…')}\n >\n <MenuItem value=\"\">{'Nobody — clear the owner'}</MenuItem>\n {members.options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('add-tag')}>\n {'Add tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('remove-tag')}>\n {'Remove tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('owner')}>\n {'Set owner'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={handleExport}>\n {'Export CSV'}\n </Button>\n {/*\n The selection's file is the rows on screen; this one is the whole\n collection, streamed by the server (AGL-2662).\n */}\n <CrmExportAllButton\n resource=\"companies\"\n orgId={scope?.[1] ?? null}\n hostId={hostId}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nCompaniesBulkBarBody.displayName = 'CompaniesBulkBarBody'\n\nexport default CompaniesBulkBar\n"],"names":["CRM_COLLECTIONS","crmMemberPickerLabel","useConfirmationContext","useFirestore","useHostActivityLogger","Button","MenuItem","TextField","doc","useCallback","useMemo","useState","useCrmBulkApply","planCompanyAddTag","planCompanyRemoveTag","planCompanySetOwner","companiesCsv","COMPANY_DETACH_LIMIT","deleteCompanyThroughRoute","normalizeBulkTag","downloadTextFile","crmBulkWriters","runCrmBulkCalls","runCrmBulkWrites","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportAllButton","useCrmApi","useCrmSharingFollowUp","NOUN","singular","plural","ACTION_TITLES","owner","doneSentence","action","done","rows","CompaniesBulkBar","props","selected","length","CompaniesBulkBarBody","displayName","members","hostId","scope","onSelectedChange","csv","firestore","confirm","logActivity","undefined","callCrm","busy","report","apply","dismissReport","recordKind","followUpSharing","selectedRows","chosen","Set","filter","row","has","$id","pending","setPending","value","setValue","writers","id","companies","openAction","runPlan","plan","attempted","writes","skipped","job","outcome","write","label","kind","map","count","handleApply","nowMs","Date","now","tag","handleExport","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","String","name","result","deleted","Error","toLocaleString","type","refused","noun","onClear","onDismissReport","extras","open","canApply","Boolean","onClose","onApply","autoFocus","size","placeholder","onChange","event","target","helperText","select","disabled","ready","error","options","member","uid","onClick","resource","orgId"],"mappings":"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"}
|
|
@@ -33,6 +33,7 @@ import { RecordActivityCard } from "./record-activity-card.js";
|
|
|
33
33
|
import RecordFilesCard from "./record-files-card.js";
|
|
34
34
|
import { RecordTasksCard } from "./record-tasks-card.js";
|
|
35
35
|
import CompanyPropertiesCard from "./company-properties-card.js";
|
|
36
|
+
import { RecordSharingCard } from "./record-sharing-card.js";
|
|
36
37
|
import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
|
|
37
38
|
/**
|
|
38
39
|
* `/crm/companies/{companyId}` — one organization (AGL-2597).
|
|
@@ -49,6 +50,7 @@ import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
|
|
|
49
50
|
* not a link anybody can paste.
|
|
50
51
|
*/ export function CompanyDetailPage(props) {
|
|
51
52
|
var _company_hostId, _company_name, _company_name1, _company_name2;
|
|
53
|
+
var _crmScope_consentGroup;
|
|
52
54
|
const { id, basePath, hostId, org } = props;
|
|
53
55
|
const routes = crmRoutes(basePath);
|
|
54
56
|
const router = useRouter();
|
|
@@ -150,6 +152,15 @@ import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
|
|
|
150
152
|
mediaIds: company.mediaIds,
|
|
151
153
|
topic: "companies"
|
|
152
154
|
}),
|
|
155
|
+
/*#__PURE__*/ _jsx(RecordSharingCard, {
|
|
156
|
+
object: "companies",
|
|
157
|
+
id: id,
|
|
158
|
+
record: company,
|
|
159
|
+
hostId: hostId != null ? hostId : null,
|
|
160
|
+
orgId: orgId,
|
|
161
|
+
org: org,
|
|
162
|
+
viewingHostIds: (_crmScope_consentGroup = crmScope.consentGroup) == null ? void 0 : _crmScope_consentGroup.hostIds
|
|
163
|
+
}),
|
|
153
164
|
/*#__PURE__*/ _jsx(RecordActivityCard, {
|
|
154
165
|
hostId: hostId,
|
|
155
166
|
org: org,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-detail-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, type CrmCompany, pluginDocsHelp } from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback } from 'react'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport CompanyContactsCard from './company-contacts-card'\nimport CompanyDealsCard from './company-deals-card'\nimport { CrmRecordHeader } from './crm-record-header'\nimport { RecordActivityCard } from './record-activity-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport CompanyPropertiesCard from './company-properties-card'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\n\n/**\n * `/crm/companies/{companyId}` — one organization (AGL-2597).\n *\n * The record behind a row of the Companies section: its properties, its\n * people, and — as the rest of the CRM lands — its deals, its open tasks\n * and the activity logged against it. Each of those is a card in a file of\n * its own, so the agent shipping deals replaces one file and touches nothing\n * here; this page reads the ONE document every card is about and hands it\n * down.\n *\n * A route rather than a drawer because a company is what a deal, a task and\n * a contact all point at, and a link that opens a drawer on top of a list is\n * not a link anybody can paste.\n */\nexport function CompanyDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const crmScope = useCrmScope({ hostId, org })\n const { scope, orgId } = crmScope\n const members = useOrgMemberOptions(orgId)\n\n const {\n data: company,\n status,\n fromCache,\n } = useFirestoreDoc<CrmCompany>(\n () =>\n scope\n ? doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id)\n : null,\n [firestore, scope, id],\n )\n\n const onDeleted = useCallback(\n () => router.push(routes.section('companies')),\n [router, routes],\n )\n\n if (!company) {\n /*\n * Loading and MISSING are different answers. A company that cannot be\n * read is a different situation from an empty one, and reading `status`\n * rather than the absence of data is what stops the refusal being\n * flashed on every arrival, before the org scope has resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={undefined}\n help={pluginDocsHelp('companies', { anchor: '#a-companys-page' })}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n loading={!settled}\n >\n {settled ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This company could not be loaded. It may have been deleted.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the company's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={company.hostId ?? null}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the cards under it say what they\n hold rather than repeating the name. */}\n <Stack spacing={2}>\n <CompanyPropertiesCard\n company={{ ...company, $id: id }}\n seed={{ fromCache, unreadable: status === 'error' }}\n hostId={hostId}\n org={org}\n crmScope={crmScope}\n members={members}\n routes={routes}\n onDeleted={onDeleted}\n />\n {/* What an assistant proposes about the company (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"company\"\n recordId={id}\n name={String(company.name ?? '')}\n taskLink={{ companyId: id }}\n />\n <CompanyContactsCard\n companyId={id}\n companyName={String(company.name ?? '')}\n crmScope={crmScope}\n org={org as Record<string, unknown> | undefined}\n routes={routes}\n />\n <CompanyDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n companyId={id}\n companyName={String(company.name ?? '')}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} companyId={id} />\n <RecordFilesCard\n scope={scope}\n collection={CRM_COLLECTIONS.companies}\n recordId={id}\n mediaIds={company.mediaIds}\n topic=\"companies\"\n />\n <RecordActivityCard hostId={hostId} org={org} companyId={id} />\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nCompanyDetailPage.displayName = 'CompanyDetailPage'\n\nexport default CompanyDetailPage\n"],"names":["CRM_COLLECTIONS","pluginDocsHelp","useFirestore","useFirestoreDoc","Stack","Typography","doc","useRouter","useCallback","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CompanyContactsCard","CompanyDealsCard","CrmRecordHeader","RecordActivityCard","RecordFilesCard","RecordTasksCard","CompanyPropertiesCard","CrmRecordInsightsZone","CompanyDetailPage","props","company","id","basePath","hostId","org","routes","router","firestore","crmScope","scope","orgId","members","data","status","fromCache","companies","onDeleted","push","section","settled","Boolean","kind","title","undefined","help","anchor","backHref","backLabel","loading","variant","color","spacing","$id","seed","unreadable","recordId","name","String","taskLink","companyId","companyName","collection","mediaIds","topic","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-detail-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, type CrmCompany, pluginDocsHelp } from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback } from 'react'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport CompanyContactsCard from './company-contacts-card'\nimport CompanyDealsCard from './company-deals-card'\nimport { CrmRecordHeader } from './crm-record-header'\nimport { RecordActivityCard } from './record-activity-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport CompanyPropertiesCard from './company-properties-card'\nimport { RecordSharingCard } from './record-sharing-card'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\n\n/**\n * `/crm/companies/{companyId}` — one organization (AGL-2597).\n *\n * The record behind a row of the Companies section: its properties, its\n * people, and — as the rest of the CRM lands — its deals, its open tasks\n * and the activity logged against it. Each of those is a card in a file of\n * its own, so the agent shipping deals replaces one file and touches nothing\n * here; this page reads the ONE document every card is about and hands it\n * down.\n *\n * A route rather than a drawer because a company is what a deal, a task and\n * a contact all point at, and a link that opens a drawer on top of a list is\n * not a link anybody can paste.\n */\nexport function CompanyDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const crmScope = useCrmScope({ hostId, org })\n const { scope, orgId } = crmScope\n const members = useOrgMemberOptions(orgId)\n\n const {\n data: company,\n status,\n fromCache,\n } = useFirestoreDoc<CrmCompany>(\n () =>\n scope\n ? doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id)\n : null,\n [firestore, scope, id],\n )\n\n const onDeleted = useCallback(\n () => router.push(routes.section('companies')),\n [router, routes],\n )\n\n if (!company) {\n /*\n * Loading and MISSING are different answers. A company that cannot be\n * read is a different situation from an empty one, and reading `status`\n * rather than the absence of data is what stops the refusal being\n * flashed on every arrival, before the org scope has resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={undefined}\n help={pluginDocsHelp('companies', { anchor: '#a-companys-page' })}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n loading={!settled}\n >\n {settled ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This company could not be loaded. It may have been deleted.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the company's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={company.hostId ?? null}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the cards under it say what they\n hold rather than repeating the name. */}\n <Stack spacing={2}>\n <CompanyPropertiesCard\n company={{ ...company, $id: id }}\n seed={{ fromCache, unreadable: status === 'error' }}\n hostId={hostId}\n org={org}\n crmScope={crmScope}\n members={members}\n routes={routes}\n onDeleted={onDeleted}\n />\n {/* What an assistant proposes about the company (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"company\"\n recordId={id}\n name={String(company.name ?? '')}\n taskLink={{ companyId: id }}\n />\n <CompanyContactsCard\n companyId={id}\n companyName={String(company.name ?? '')}\n crmScope={crmScope}\n org={org as Record<string, unknown> | undefined}\n routes={routes}\n />\n <CompanyDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n companyId={id}\n companyName={String(company.name ?? '')}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} companyId={id} />\n <RecordFilesCard\n scope={scope}\n collection={CRM_COLLECTIONS.companies}\n recordId={id}\n mediaIds={company.mediaIds}\n topic=\"companies\"\n />\n {/* Where the company is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"companies\"\n id={id}\n record={company as unknown as Record<string, unknown>}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={crmScope.consentGroup?.hostIds}\n />\n <RecordActivityCard hostId={hostId} org={org} companyId={id} />\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nCompanyDetailPage.displayName = 'CompanyDetailPage'\n\nexport default CompanyDetailPage\n"],"names":["CRM_COLLECTIONS","pluginDocsHelp","useFirestore","useFirestoreDoc","Stack","Typography","doc","useRouter","useCallback","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CompanyContactsCard","CompanyDealsCard","CrmRecordHeader","RecordActivityCard","RecordFilesCard","RecordTasksCard","CompanyPropertiesCard","RecordSharingCard","CrmRecordInsightsZone","CompanyDetailPage","props","company","crmScope","id","basePath","hostId","org","routes","router","firestore","scope","orgId","members","data","status","fromCache","companies","onDeleted","push","section","settled","Boolean","kind","title","undefined","help","anchor","backHref","backLabel","loading","variant","color","spacing","$id","seed","unreadable","recordId","name","String","taskLink","companyId","companyName","collection","mediaIds","topic","object","record","viewingHostIds","consentGroup","hostIds","displayName"],"mappings":"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"}
|
|
@@ -35,6 +35,7 @@ import { crmCustomDraftDocument, crmCustomDraftMissingRequired, crmCustomDraftVa
|
|
|
35
35
|
import { CrmCustomFieldControl } from "./crm-custom-field-control.js";
|
|
36
36
|
import { CrmSitePicker } from "./crm-site-picker.js";
|
|
37
37
|
import { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
|
|
38
|
+
import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
|
|
38
39
|
const ADDRESS_FIELDS = [
|
|
39
40
|
{
|
|
40
41
|
key: 'line1',
|
|
@@ -74,7 +75,7 @@ const ADDRESS_FIELDS = [
|
|
|
74
75
|
* `acme` or a phone number with no country code is refused — as a message
|
|
75
76
|
* beside the button, with the typed values left where they are.
|
|
76
77
|
*/ export function CompanyEditDrawer(props) {
|
|
77
|
-
var _ref, _members_error;
|
|
78
|
+
var _ref, _ref1, _members_error;
|
|
78
79
|
const { open, onClose, hostId, org, company, seed, members, onSaved } = props;
|
|
79
80
|
const firestore = useFirestore();
|
|
80
81
|
const { data: user } = useUser();
|
|
@@ -83,6 +84,8 @@ const ADDRESS_FIELDS = [
|
|
|
83
84
|
hostId,
|
|
84
85
|
org
|
|
85
86
|
});
|
|
87
|
+
// A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).
|
|
88
|
+
const followUpSharing = useCrmSharingFollowUp(hostId, (_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null);
|
|
86
89
|
// The feed the act is logged in is the level it was PERFORMED at
|
|
87
90
|
// (AGL-2738): this site's under a site, the organization's at the org
|
|
88
91
|
// hub. `createHostId` is where the record is STAMPED, which at the hub is
|
|
@@ -97,7 +100,7 @@ const ADDRESS_FIELDS = [
|
|
|
97
100
|
* keys the reader has touched over what the company stores. Saved as the
|
|
98
101
|
* difference — see `crm-custom-draft` — beside the fixed fields, in the
|
|
99
102
|
* same write, so a company is never half-saved.
|
|
100
|
-
*/ const fields = useContactFieldDefinitions((
|
|
103
|
+
*/ const fields = useContactFieldDefinitions((_ref1 = scope == null ? void 0 : scope[1]) != null ? _ref1 : null, 'company');
|
|
101
104
|
const storedCustom = useMemo(()=>{
|
|
102
105
|
var _ref;
|
|
103
106
|
return (_ref = company == null ? void 0 : company.custom) != null ? _ref : {};
|
|
@@ -180,6 +183,9 @@ const ADDRESS_FIELDS = [
|
|
|
180
183
|
}));
|
|
181
184
|
});
|
|
182
185
|
if (!verdict.ok) return setError(verdict.message);
|
|
186
|
+
followUpSharing('companies', [
|
|
187
|
+
company.$id
|
|
188
|
+
]);
|
|
183
189
|
enqueueSnackbar('Company saved', {
|
|
184
190
|
variant: 'success',
|
|
185
191
|
persist: false
|
|
@@ -217,6 +223,9 @@ const ADDRESS_FIELDS = [
|
|
|
217
223
|
// Setup → Activity shows CRM work (AGL-2622): the company is org
|
|
218
224
|
// data, but the act happened in this site's console and belongs in
|
|
219
225
|
// its feed. Creation only — an edit is a save the card reports.
|
|
226
|
+
followUpSharing('companies', [
|
|
227
|
+
id
|
|
228
|
+
]);
|
|
220
229
|
logActivity('Added company', {
|
|
221
230
|
type: 'company',
|
|
222
231
|
id,
|
|
@@ -238,6 +247,7 @@ const ADDRESS_FIELDS = [
|
|
|
238
247
|
}, [
|
|
239
248
|
busy,
|
|
240
249
|
scope,
|
|
250
|
+
followUpSharing,
|
|
241
251
|
draft,
|
|
242
252
|
company,
|
|
243
253
|
bandFull,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-edit-drawer.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 type AglynPostalAddress,\n createResourceUid,\n CRM_COLLECTIONS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n type CrmCompany,\n crmNewRecordListFields,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, HelpTip, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyDraft,\n companyDraftFields,\n companyDraftFrom,\n EMPTY_COMPANY_DRAFT,\n} from '../model/companies'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from '../model/crm-list-query'\n\nexport interface CompanyEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW company asks which site captures it and\n * an edit keeps the company's own.\n */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n /**\n * The company being EDITED, or nothing to create one.\n *\n * The two modes differ in what the fields start as and in which write\n * runs: an edit opens on the stored values and updates the document, a\n * create opens empty and stamps the scope, provenance and timestamps a new\n * record must carry.\n */\n company?: (Partial<CrmCompany> & { $id: string }) | null\n /**\n * Whether the stored values the edit opened on can be trusted.\n *\n * Every optional field is written on every save — a blank one is a\n * DELETE — so a form seeded from a cached read would write the cache's\n * blanks over the server's values. The card that holds the listen reports\n * its own `fromCache` and `status`; the drawer refuses to save on a seed\n * the server never confirmed, and says so.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n /**\n * The team, for the owner picker.\n *\n * Handed in rather than fetched here, because the surface opening this\n * drawer already holds the roster to name owners in its rows, and one\n * fetch per surface is the bargain.\n */\n members: OrgMemberOptions\n /** Called with the record's id after a successful write. */\n onSaved: (companyId: string) => void\n}\n\nconst ADDRESS_FIELDS: ReadonlyArray<{\n key: keyof AglynPostalAddress\n label: string\n}> = [\n { key: 'line1', label: 'Address line 1' },\n { key: 'line2', label: 'Address line 2' },\n { key: 'city', label: 'City' },\n { key: 'state', label: 'State or region' },\n { key: 'postalCode', label: 'Postal code' },\n { key: 'country', label: 'Country (two-letter code)' },\n]\n\n/**\n * ONE COMPANY, created or edited (AGL-2597).\n *\n * A drawer rather than a form above the list, and the same drawer for both\n * jobs: a company is eight fields and an address, which is too much to wedge\n * over a table and exactly the same set whether the record exists yet. The\n * list opens it empty; the company's page opens it on the record.\n *\n * Nothing is written per keystroke. The draft is held here and turned into a\n * document by `companyDraftFields` on Save, which is where a domain typed as\n * `acme` or a phone number with no country code is refused — as a message\n * beside the button, with the typed values left where they are.\n */\nexport function CompanyEditDrawer(props: CompanyEditDrawerProps) {\n const { open, onClose, hostId, org, company, seed, members, onSaved } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { scope, createTokens, createHostId } = useCrmScope({ hostId, org })\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the record is STAMPED, which at the hub is\n // a site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n\n const [draft, setDraft] = useState<CompanyDraft>(EMPTY_COMPANY_DRAFT)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's company definitions, and the\n * keys the reader has touched over what the company stores. Saved as the\n * difference — see `crm-custom-draft` — beside the fixed fields, in the\n * same write, so a company is never half-saved.\n */\n const fields = useContactFieldDefinitions(scope?.[1] ?? null, 'company')\n const storedCustom = useMemo(() => company?.custom ?? {}, [company?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /*\n * THE RECORDS BAND (AGL-2611), read only for a CREATE: a company is a\n * record of the band the contacts list is banded by, and on an org at a\n * full hard band this drawer refuses the way `upsertHostContact` refuses a\n * capture — same number, same sentence. An edit is not a record and pays\n * for no aggregate. Only a SETTLED refusal refuses: three reads that have\n * not answered are not a full band.\n */\n const records = useCrmRecordsQuota(open && !company ? scope : null, org)\n const bandFull = !company && records.ready && !records.quota.allowed\n\n /*\n * Seeded when the drawer OPENS, and not on every render of the card\n * behind it: the company listen is live, so re-seeding on each snapshot\n * would take a field away from whoever is typing in it — including on the\n * snapshot their own save produces. The create case seeds the owner to the\n * person filing the record, which is the answer nine times in ten and one\n * click to change.\n */\n const companyId = company?.$id\n useEffect(() => {\n if (!open) return\n setDraft(\n company\n ? companyDraftFrom(company)\n : { ...EMPTY_COMPANY_DRAFT, ownerUid: user?.uid ?? '' },\n )\n setCustom({})\n setError('')\n // The stored fields are read once per opening; `company` is a new object\n // on every snapshot and must not re-seed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, companyId])\n\n const patch = useCallback(\n (field: keyof Omit<CompanyDraft, 'address'>, value: string) =>\n setDraft((current) => ({ ...current, [field]: value })),\n [],\n )\n const patchAddress = useCallback(\n (field: keyof AglynPostalAddress, value: string) =>\n setDraft((current) => ({\n ...current,\n address: { ...current.address, [field]: value },\n })),\n [],\n )\n\n const handleSave = useCallback(async () => {\n if (busy || !scope) return\n const result = companyDraftFields(draft)\n if (result.ok === false) return setError(result.error)\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n company ? 'edit' : 'create',\n )\n if (missing.length) return setError(`${missing.join(', ')} is required.`)\n const customDocument = crmCustomDraftDocument(custom)\n setError('')\n setBusy(true)\n try {\n if (company) {\n const ref = doc(\n firestore,\n scope[0],\n scope[1],\n CRM_COLLECTIONS.companies,\n company.$id,\n )\n // The guard WRAPS the write — an early return is a shape you can\n // keep while losing the protection.\n const verdict = await writeGuardedBySeed(\n {\n subject: 'company',\n unreadable: seed?.unreadable ?? false,\n fromCache: seed?.fromCache ?? false,\n },\n async () => {\n const cleared = Object.fromEntries(\n result.cleared.map((field) => [field, deleteField()]),\n )\n // The stored record the list fields are computed from: the row\n // without the id the listener stamped on it.\n const { $id: _rowId, ...stored } = company\n const searchFields = crmClientListFields(\n 'companies',\n stored,\n { ...result.set, ...cleared },\n CRM_CLIENT_SEARCH_FIELDS,\n )\n await updateDoc(ref, {\n ...result.set,\n // A blank optional field is a DELETE on an edit, or the old\n // domain would stay stored and keep matching people by email.\n ...cleared,\n // The name and domain are what the Companies list searches\n // (AGL-3321), from the listener's row with the edit over it.\n ...searchFields,\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n updatedAt: serverTimestamp(),\n })\n },\n )\n if (!verdict.ok) return setError(verdict.message)\n enqueueSnackbar('Company saved', { variant: 'success', persist: false })\n onSaved(company.$id)\n } else {\n if (bandFull) return setError(CRM_RECORDS_BAND_FULL_MESSAGE)\n const id = createResourceUid()\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n await setDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n {\n ...result.set,\n ...(customDocument ? { custom: customDocument } : {}),\n // What the Companies list searches and filters by (AGL-3321).\n ...crmNewRecordListFields('companies', {\n ...result.set,\n visibleTo: [...createTokens],\n }),\n /*\n * The scope every CRM creator stamps: the whole org when the org\n * shares by default, and otherwise the sites that present as one\n * sender. Without it the record is seen by NOBODY — an absent\n * `visibleTo` matches no reader's predicate — and the rules\n * refuse a scoped member creating outside their own tokens.\n */\n visibleTo: [...createTokens],\n // Provenance: the site whose console filed it, never rewritten.\n hostId: createHostId,\n createdByUid: user?.uid ?? '',\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n },\n )\n // Setup → Activity shows CRM work (AGL-2622): the company is org\n // data, but the act happened in this site's console and belongs in\n // its feed. Creation only — an edit is a save the card reports.\n logActivity('Added company', { type: 'company', id, name: draft.name.trim() })\n enqueueSnackbar(`Company \"${draft.name.trim()}\" created`, {\n variant: 'success',\n persist: false,\n })\n onSaved(id)\n }\n onClose()\n } catch (caught) {\n console.error(caught)\n setError('The company could not be saved.')\n } finally {\n setBusy(false)\n }\n }, [\n busy,\n scope,\n draft,\n company,\n bandFull,\n firestore,\n seed,\n createTokens,\n createHostId,\n user,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n fields.active,\n storedCustom,\n custom,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>{'close drawer'}</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {company ? 'Edit company' : 'New company'}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('companies', { anchor: '#create-a-company' })}\n />\n </>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new company names the site that\n captures it. Nothing under a site, and nothing on an edit — a\n company keeps the site it was made on (AGL-2630).\n */}\n {company ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this company belongs to — it decides which of your sites may see it.\"\n />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organization behind one or more of your contacts. The ' +\n 'domain is what suggests this company for a person from their ' +\n 'email address, so enter the bare hostname.'}\n </Typography>\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={draft.name}\n onChange={(event) => patch('name', event.target.value)}\n autoFocus\n />\n <TextField\n size=\"small\"\n label=\"Domain\"\n placeholder=\"example.com\"\n value={draft.domain}\n onChange={(event) => patch('domain', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n placeholder=\"https://www.example.com\"\n value={draft.website}\n onChange={(event) => patch('website', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n placeholder=\"+1 512 555 0123\"\n value={draft.phone}\n onChange={(event) => patch('phone', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Industry\"\n value={draft.industry}\n onChange={(event) => patch('industry', event.target.value)}\n />\n {/*\n The owner is chosen from the team, never typed: the value is a\n uid, and a picker that is still loading says so rather than\n offering an empty list that reads as \"nobody can own this\".\n */}\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={draft.ownerUid}\n disabled={!members.ready}\n helperText={\n !members.ready\n ? 'Loading your team…'\n : (members.error ?? 'The team member responsible for this account.')\n }\n onChange={(event) => patch('ownerUid', event.target.value)}\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {draft.ownerUid &&\n !members.options.some((option) => option.uid === draft.ownerUid) ? (\n <MenuItem value={draft.ownerUid}>\n {members.labelFor(draft.ownerUid)}\n </MenuItem>\n ) : null}\n {members.options.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {crmMemberPickerLabel(option)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"enterprise, west\"\n helperText=\"Comma-separated, lowercased — the same tags the bulk bar adds\"\n value={draft.tags}\n onChange={(event) => patch('tags', event.target.value)}\n />\n <Typography variant=\"subtitle2\">{'Address'}</Typography>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={field.key}\n size=\"small\"\n label={field.label}\n value={draft.address[field.key] ?? ''}\n onChange={(event) => patchAddress(field.key, event.target.value)}\n />\n ))}\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={draft.notes}\n onChange={(event) => patch('notes', event.target.value)}\n multiline\n minRows={3}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n // A new company waits for its capturing site.\n disabled={busy || !scope || !draft.name.trim() || (!company && !createHostId)}\n onClick={() => void handleSave()}\n >\n {busy\n ? 'Saving…'\n : company\n ? 'Save company'\n : 'Create company'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nCompanyEditDrawer.displayName = 'CompanyEditDrawer'\n\nexport default CompanyEditDrawer\n"],"names":["createResourceUid","CRM_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","crmNewRecordListFields","pluginDocsHelp","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","HelpTip","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","deleteField","doc","serverTimestamp","setDoc","updateDoc","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordsQuota","useCrmScope","companyDraftFields","companyDraftFrom","EMPTY_COMPANY_DRAFT","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","crmClientListFields","ADDRESS_FIELDS","key","label","CompanyEditDrawer","props","members","open","onClose","hostId","org","company","seed","onSaved","firestore","data","user","enqueueSnackbar","scope","createTokens","createHostId","logActivity","draft","setDraft","busy","setBusy","error","setError","fields","storedCustom","custom","setCustom","records","bandFull","ready","quota","allowed","companyId","$id","ownerUid","uid","patch","field","value","current","patchAddress","address","handleSave","result","ok","missing","active","length","join","customDocument","ref","companies","verdict","subject","unreadable","fromCache","cleared","Object","fromEntries","map","_rowId","stored","searchFields","set","updatedAt","message","variant","persist","id","visibleTo","createdByUid","createdAt","type","name","trim","caught","console","anchor","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","gutterY","spacing","disabled","helperText","severity","size","required","onChange","event","target","autoFocus","placeholder","domain","website","phone","industry","select","options","some","option","labelFor","tags","notes","multiline","minRows","definition","direction","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-edit-drawer.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 type AglynPostalAddress,\n createResourceUid,\n CRM_COLLECTIONS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n type CrmCompany,\n crmNewRecordListFields,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, HelpTip, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyDraft,\n companyDraftFields,\n companyDraftFrom,\n EMPTY_COMPANY_DRAFT,\n} from '../model/companies'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\nexport interface CompanyEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW company asks which site captures it and\n * an edit keeps the company's own.\n */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n /**\n * The company being EDITED, or nothing to create one.\n *\n * The two modes differ in what the fields start as and in which write\n * runs: an edit opens on the stored values and updates the document, a\n * create opens empty and stamps the scope, provenance and timestamps a new\n * record must carry.\n */\n company?: (Partial<CrmCompany> & { $id: string }) | null\n /**\n * Whether the stored values the edit opened on can be trusted.\n *\n * Every optional field is written on every save — a blank one is a\n * DELETE — so a form seeded from a cached read would write the cache's\n * blanks over the server's values. The card that holds the listen reports\n * its own `fromCache` and `status`; the drawer refuses to save on a seed\n * the server never confirmed, and says so.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n /**\n * The team, for the owner picker.\n *\n * Handed in rather than fetched here, because the surface opening this\n * drawer already holds the roster to name owners in its rows, and one\n * fetch per surface is the bargain.\n */\n members: OrgMemberOptions\n /** Called with the record's id after a successful write. */\n onSaved: (companyId: string) => void\n}\n\nconst ADDRESS_FIELDS: ReadonlyArray<{\n key: keyof AglynPostalAddress\n label: string\n}> = [\n { key: 'line1', label: 'Address line 1' },\n { key: 'line2', label: 'Address line 2' },\n { key: 'city', label: 'City' },\n { key: 'state', label: 'State or region' },\n { key: 'postalCode', label: 'Postal code' },\n { key: 'country', label: 'Country (two-letter code)' },\n]\n\n/**\n * ONE COMPANY, created or edited (AGL-2597).\n *\n * A drawer rather than a form above the list, and the same drawer for both\n * jobs: a company is eight fields and an address, which is too much to wedge\n * over a table and exactly the same set whether the record exists yet. The\n * list opens it empty; the company's page opens it on the record.\n *\n * Nothing is written per keystroke. The draft is held here and turned into a\n * document by `companyDraftFields` on Save, which is where a domain typed as\n * `acme` or a phone number with no country code is refused — as a message\n * beside the button, with the typed values left where they are.\n */\nexport function CompanyEditDrawer(props: CompanyEditDrawerProps) {\n const { open, onClose, hostId, org, company, seed, members, onSaved } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { scope, createTokens, createHostId } = useCrmScope({ hostId, org })\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, scope?.[1] ?? null)\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the record is STAMPED, which at the hub is\n // a site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n\n const [draft, setDraft] = useState<CompanyDraft>(EMPTY_COMPANY_DRAFT)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's company definitions, and the\n * keys the reader has touched over what the company stores. Saved as the\n * difference — see `crm-custom-draft` — beside the fixed fields, in the\n * same write, so a company is never half-saved.\n */\n const fields = useContactFieldDefinitions(scope?.[1] ?? null, 'company')\n const storedCustom = useMemo(() => company?.custom ?? {}, [company?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /*\n * THE RECORDS BAND (AGL-2611), read only for a CREATE: a company is a\n * record of the band the contacts list is banded by, and on an org at a\n * full hard band this drawer refuses the way `upsertHostContact` refuses a\n * capture — same number, same sentence. An edit is not a record and pays\n * for no aggregate. Only a SETTLED refusal refuses: three reads that have\n * not answered are not a full band.\n */\n const records = useCrmRecordsQuota(open && !company ? scope : null, org)\n const bandFull = !company && records.ready && !records.quota.allowed\n\n /*\n * Seeded when the drawer OPENS, and not on every render of the card\n * behind it: the company listen is live, so re-seeding on each snapshot\n * would take a field away from whoever is typing in it — including on the\n * snapshot their own save produces. The create case seeds the owner to the\n * person filing the record, which is the answer nine times in ten and one\n * click to change.\n */\n const companyId = company?.$id\n useEffect(() => {\n if (!open) return\n setDraft(\n company\n ? companyDraftFrom(company)\n : { ...EMPTY_COMPANY_DRAFT, ownerUid: user?.uid ?? '' },\n )\n setCustom({})\n setError('')\n // The stored fields are read once per opening; `company` is a new object\n // on every snapshot and must not re-seed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, companyId])\n\n const patch = useCallback(\n (field: keyof Omit<CompanyDraft, 'address'>, value: string) =>\n setDraft((current) => ({ ...current, [field]: value })),\n [],\n )\n const patchAddress = useCallback(\n (field: keyof AglynPostalAddress, value: string) =>\n setDraft((current) => ({\n ...current,\n address: { ...current.address, [field]: value },\n })),\n [],\n )\n\n const handleSave = useCallback(async () => {\n if (busy || !scope) return\n const result = companyDraftFields(draft)\n if (result.ok === false) return setError(result.error)\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n company ? 'edit' : 'create',\n )\n if (missing.length) return setError(`${missing.join(', ')} is required.`)\n const customDocument = crmCustomDraftDocument(custom)\n setError('')\n setBusy(true)\n try {\n if (company) {\n const ref = doc(\n firestore,\n scope[0],\n scope[1],\n CRM_COLLECTIONS.companies,\n company.$id,\n )\n // The guard WRAPS the write — an early return is a shape you can\n // keep while losing the protection.\n const verdict = await writeGuardedBySeed(\n {\n subject: 'company',\n unreadable: seed?.unreadable ?? false,\n fromCache: seed?.fromCache ?? false,\n },\n async () => {\n const cleared = Object.fromEntries(\n result.cleared.map((field) => [field, deleteField()]),\n )\n // The stored record the list fields are computed from: the row\n // without the id the listener stamped on it.\n const { $id: _rowId, ...stored } = company\n const searchFields = crmClientListFields(\n 'companies',\n stored,\n { ...result.set, ...cleared },\n CRM_CLIENT_SEARCH_FIELDS,\n )\n await updateDoc(ref, {\n ...result.set,\n // A blank optional field is a DELETE on an edit, or the old\n // domain would stay stored and keep matching people by email.\n ...cleared,\n // The name and domain are what the Companies list searches\n // (AGL-3321), from the listener's row with the edit over it.\n ...searchFields,\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n updatedAt: serverTimestamp(),\n })\n },\n )\n if (!verdict.ok) return setError(verdict.message)\n followUpSharing('companies', [company.$id])\n enqueueSnackbar('Company saved', { variant: 'success', persist: false })\n onSaved(company.$id)\n } else {\n if (bandFull) return setError(CRM_RECORDS_BAND_FULL_MESSAGE)\n const id = createResourceUid()\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n await setDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n {\n ...result.set,\n ...(customDocument ? { custom: customDocument } : {}),\n // What the Companies list searches and filters by (AGL-3321).\n ...crmNewRecordListFields('companies', {\n ...result.set,\n visibleTo: [...createTokens],\n }),\n /*\n * The scope every CRM creator stamps: the whole org when the org\n * shares by default, and otherwise the sites that present as one\n * sender. Without it the record is seen by NOBODY — an absent\n * `visibleTo` matches no reader's predicate — and the rules\n * refuse a scoped member creating outside their own tokens.\n */\n visibleTo: [...createTokens],\n // Provenance: the site whose console filed it, never rewritten.\n hostId: createHostId,\n createdByUid: user?.uid ?? '',\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n },\n )\n // Setup → Activity shows CRM work (AGL-2622): the company is org\n // data, but the act happened in this site's console and belongs in\n // its feed. Creation only — an edit is a save the card reports.\n followUpSharing('companies', [id])\n logActivity('Added company', { type: 'company', id, name: draft.name.trim() })\n enqueueSnackbar(`Company \"${draft.name.trim()}\" created`, {\n variant: 'success',\n persist: false,\n })\n onSaved(id)\n }\n onClose()\n } catch (caught) {\n console.error(caught)\n setError('The company could not be saved.')\n } finally {\n setBusy(false)\n }\n }, [\n busy,\n scope,\n followUpSharing,\n draft,\n company,\n bandFull,\n firestore,\n seed,\n createTokens,\n createHostId,\n user,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n fields.active,\n storedCustom,\n custom,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>{'close drawer'}</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {company ? 'Edit company' : 'New company'}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('companies', { anchor: '#create-a-company' })}\n />\n </>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new company names the site that\n captures it. Nothing under a site, and nothing on an edit — a\n company keeps the site it was made on (AGL-2630).\n */}\n {company ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this company belongs to — it decides which of your sites may see it.\"\n />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organization behind one or more of your contacts. The ' +\n 'domain is what suggests this company for a person from their ' +\n 'email address, so enter the bare hostname.'}\n </Typography>\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={draft.name}\n onChange={(event) => patch('name', event.target.value)}\n autoFocus\n />\n <TextField\n size=\"small\"\n label=\"Domain\"\n placeholder=\"example.com\"\n value={draft.domain}\n onChange={(event) => patch('domain', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n placeholder=\"https://www.example.com\"\n value={draft.website}\n onChange={(event) => patch('website', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n placeholder=\"+1 512 555 0123\"\n value={draft.phone}\n onChange={(event) => patch('phone', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Industry\"\n value={draft.industry}\n onChange={(event) => patch('industry', event.target.value)}\n />\n {/*\n The owner is chosen from the team, never typed: the value is a\n uid, and a picker that is still loading says so rather than\n offering an empty list that reads as \"nobody can own this\".\n */}\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={draft.ownerUid}\n disabled={!members.ready}\n helperText={\n !members.ready\n ? 'Loading your team…'\n : (members.error ?? 'The team member responsible for this account.')\n }\n onChange={(event) => patch('ownerUid', event.target.value)}\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {draft.ownerUid &&\n !members.options.some((option) => option.uid === draft.ownerUid) ? (\n <MenuItem value={draft.ownerUid}>\n {members.labelFor(draft.ownerUid)}\n </MenuItem>\n ) : null}\n {members.options.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {crmMemberPickerLabel(option)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"enterprise, west\"\n helperText=\"Comma-separated, lowercased — the same tags the bulk bar adds\"\n value={draft.tags}\n onChange={(event) => patch('tags', event.target.value)}\n />\n <Typography variant=\"subtitle2\">{'Address'}</Typography>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={field.key}\n size=\"small\"\n label={field.label}\n value={draft.address[field.key] ?? ''}\n onChange={(event) => patchAddress(field.key, event.target.value)}\n />\n ))}\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={draft.notes}\n onChange={(event) => patch('notes', event.target.value)}\n multiline\n minRows={3}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n // A new company waits for its capturing site.\n disabled={busy || !scope || !draft.name.trim() || (!company && !createHostId)}\n onClick={() => void handleSave()}\n >\n {busy\n ? 'Saving…'\n : company\n ? 'Save company'\n : 'Create company'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nCompanyEditDrawer.displayName = 'CompanyEditDrawer'\n\nexport default CompanyEditDrawer\n"],"names":["createResourceUid","CRM_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","crmNewRecordListFields","pluginDocsHelp","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","HelpTip","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","deleteField","doc","serverTimestamp","setDoc","updateDoc","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordsQuota","useCrmScope","companyDraftFields","companyDraftFrom","EMPTY_COMPANY_DRAFT","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","crmClientListFields","useCrmSharingFollowUp","ADDRESS_FIELDS","key","label","CompanyEditDrawer","props","members","open","onClose","hostId","org","company","seed","onSaved","firestore","data","user","enqueueSnackbar","scope","createTokens","createHostId","followUpSharing","logActivity","draft","setDraft","busy","setBusy","error","setError","fields","storedCustom","custom","setCustom","records","bandFull","ready","quota","allowed","companyId","$id","ownerUid","uid","patch","field","value","current","patchAddress","address","handleSave","result","ok","missing","active","length","join","customDocument","ref","companies","verdict","subject","unreadable","fromCache","cleared","Object","fromEntries","map","_rowId","stored","searchFields","set","updatedAt","message","variant","persist","id","visibleTo","createdByUid","createdAt","type","name","trim","caught","console","anchor","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","gutterY","spacing","disabled","helperText","severity","size","required","onChange","event","target","autoFocus","placeholder","domain","website","phone","industry","select","options","some","option","labelFor","tags","notes","multiline","minRows","definition","direction","displayName"],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@ export declare const COMPANIES_IMPORT_URL = "/api/crm/companies-import";
|
|
|
16
16
|
/** The company vocabulary, over the org's company custom fields (AGL-2661). */
|
|
17
17
|
export declare function companyImportVocabulary(fields: readonly ContactFieldDefinition[]): CsvImportVocabulary<CompanyImportField, CompanyImportRawRow & Record<string, unknown>, CompanyImportSkippedRow>;
|
|
18
18
|
/** The vocabulary with no custom fields — what a surface without an org resolves to. */
|
|
19
|
-
export declare const COMPANY_IMPORT_VOCABULARY: CsvImportVocabulary<"name" | "
|
|
19
|
+
export declare const COMPANY_IMPORT_VOCABULARY: CsvImportVocabulary<"name" | "phone" | "domain" | "notes" | "tags" | "ownerEmail" | "website" | "industry" | "addressLine1" | "addressLine2" | "addressCity" | "addressState" | "addressPostalCode" | "addressCountry", Partial<Record<"name" | "phone" | "domain" | "notes" | "tags" | "ownerEmail" | "website" | "industry" | "addressLine1" | "addressLine2" | "addressCity" | "addressState" | "addressPostalCode" | "addressCountry", unknown>> & {
|
|
20
20
|
custom?: unknown;
|
|
21
21
|
} & Record<string, unknown>, CompanyImportSkippedRow>;
|
|
22
22
|
export interface CompanyImportDrawerProps {
|
|
@@ -24,6 +24,7 @@ import { useCallback, useMemo, useState } from "react";
|
|
|
24
24
|
import { readCrmRecordsQuota } from "../hooks/use-crm-records-quota.js";
|
|
25
25
|
import { crmVisibleToClause, useCrmScope } from "../hooks/use-crm-scope.js";
|
|
26
26
|
import { companyDraftFields, EMPTY_COMPANY_DRAFT, suggestCompanyForEmail } from "../model/companies.js";
|
|
27
|
+
import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
|
|
27
28
|
export { suggestCompanyForEmail };
|
|
28
29
|
/**
|
|
29
30
|
* How many companies the picker lists.
|
|
@@ -97,7 +98,7 @@ export { suggestCompanyForEmail };
|
|
|
97
98
|
* `readCrmRecordsQuota` — and never for an edit, because linking an
|
|
98
99
|
* existing company adds no record.
|
|
99
100
|
*/ export function useCreateCompany(props) {
|
|
100
|
-
var _ref;
|
|
101
|
+
var _ref, _ref1;
|
|
101
102
|
const { hostId, org } = props;
|
|
102
103
|
const firestore = useFirestore();
|
|
103
104
|
const { data: user } = useUser();
|
|
@@ -108,7 +109,8 @@ export { suggestCompanyForEmail };
|
|
|
108
109
|
hostId,
|
|
109
110
|
org
|
|
110
111
|
});
|
|
111
|
-
const
|
|
112
|
+
const followUpSharing = useCrmSharingFollowUp(hostId, (_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null);
|
|
113
|
+
const uid = (_ref1 = user == null ? void 0 : user.uid) != null ? _ref1 : '';
|
|
112
114
|
return useMemo(()=>{
|
|
113
115
|
if (!scope || !createHostId) return null;
|
|
114
116
|
return async (name)=>{
|
|
@@ -133,6 +135,10 @@ export { suggestCompanyForEmail };
|
|
|
133
135
|
createdAt: serverTimestamp(),
|
|
134
136
|
updatedAt: serverTimestamp()
|
|
135
137
|
}));
|
|
138
|
+
// A rule may share companies created on this site (AGL-3336).
|
|
139
|
+
followUpSharing('companies', [
|
|
140
|
+
id
|
|
141
|
+
]);
|
|
136
142
|
return {
|
|
137
143
|
id,
|
|
138
144
|
name: String(result.set['name']),
|
|
@@ -145,7 +151,8 @@ export { suggestCompanyForEmail };
|
|
|
145
151
|
firestore,
|
|
146
152
|
createHostId,
|
|
147
153
|
uid,
|
|
148
|
-
org
|
|
154
|
+
org,
|
|
155
|
+
followUpSharing
|
|
149
156
|
]);
|
|
150
157
|
}
|
|
151
158
|
/** The sentinel id of the "Create …" row the list grows when nothing matches. */ const CREATE_OPTION_ID = '__create__';
|