@aglyn/plugins-crm 1.0.0-beta.232 → 1.0.0-beta.234

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.
Files changed (55) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/companies-section.js +43 -8
  3. package/src/lib/components/companies-section.js.map +1 -1
  4. package/src/lib/components/company-edit-drawer.js +3 -1
  5. package/src/lib/components/company-edit-drawer.js.map +1 -1
  6. package/src/lib/components/contact-detail-page.js +6 -1
  7. package/src/lib/components/contact-detail-page.js.map +1 -1
  8. package/src/lib/components/contacts-section.js +37 -8
  9. package/src/lib/components/contacts-section.js.map +1 -1
  10. package/src/lib/components/crm-email-check.d.ts +14 -0
  11. package/src/lib/components/crm-email-check.js +38 -1
  12. package/src/lib/components/crm-email-check.js.map +1 -1
  13. package/src/lib/components/crm-next-activity-column.d.ts +5 -4
  14. package/src/lib/components/crm-next-activity-column.js +5 -4
  15. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  16. package/src/lib/components/deal-edit-drawer.js +3 -1
  17. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  18. package/src/lib/components/deals-section.js +50 -6
  19. package/src/lib/components/deals-section.js.map +1 -1
  20. package/src/lib/components/lead-properties-card.js +9 -3
  21. package/src/lib/components/lead-properties-card.js.map +1 -1
  22. package/src/lib/components/leads-section.js +61 -11
  23. package/src/lib/components/leads-section.js.map +1 -1
  24. package/src/lib/components/tasks-section.js +61 -9
  25. package/src/lib/components/tasks-section.js.map +1 -1
  26. package/src/lib/constants/company-filters.js +40 -2
  27. package/src/lib/constants/company-filters.js.map +1 -1
  28. package/src/lib/constants/contact-filters.d.ts +10 -1
  29. package/src/lib/constants/contact-filters.js +62 -2
  30. package/src/lib/constants/contact-filters.js.map +1 -1
  31. package/src/lib/constants/deal-filters.js +78 -0
  32. package/src/lib/constants/deal-filters.js.map +1 -1
  33. package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
  34. package/src/lib/hooks/use-crm-column-sort.js +156 -0
  35. package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
  36. package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
  37. package/src/lib/hooks/use-crm-tasks.js +10 -8
  38. package/src/lib/hooks/use-crm-tasks.js.map +1 -1
  39. package/src/lib/model/crm-list-query.d.ts +6 -0
  40. package/src/lib/model/crm-list-query.js +13 -0
  41. package/src/lib/model/crm-list-query.js.map +1 -1
  42. package/src/lib/model/lead-filters.d.ts +19 -2
  43. package/src/lib/model/lead-filters.js +112 -2
  44. package/src/lib/model/lead-filters.js.map +1 -1
  45. package/src/lib/model/native-vocabulary.d.ts +41 -0
  46. package/src/lib/model/native-vocabulary.js +59 -0
  47. package/src/lib/model/native-vocabulary.js.map +1 -0
  48. package/src/lib/model/task-views.d.ts +15 -2
  49. package/src/lib/model/task-views.js +50 -4
  50. package/src/lib/model/task-views.js.map +1 -1
  51. package/src/lib/server/email-check.d.ts +4 -1
  52. package/src/lib/server/email-check.js +18 -6
  53. package/src/lib/server/email-check.js.map +1 -1
  54. package/src/lib/transfer/tasks.js +2 -1
  55. package/src/lib/transfer/tasks.js.map +1 -1
@@ -32,6 +32,7 @@ import { customFieldColumns } from "./contact-custom-columns.js";
32
32
  import { useCrmViewGrid } from "../hooks/use-crm-view-grid.js";
33
33
  import { CRM_LIST_SLOTS, CrmColumnOrderProvider } from "./crm-column-menu.js";
34
34
  import { useCrmFoldsScope, useCrmListQuery } from "../hooks/use-crm-list-query.js";
35
+ import { crmViewQuerySort, useCrmColumnSort } from "../hooks/use-crm-column-sort.js";
35
36
  import CrmViewsControl from "./crm-views-control.js";
36
37
  import { CrmListActions, CrmListToolbar } from "./crm-list-toolbar.js";
37
38
  import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
@@ -44,7 +45,7 @@ import { useRouter } from "next/navigation";
44
45
  import { useCallback, useEffect, useMemo, useState } from "react";
45
46
  import { leadPrimaryGroup } from "../model/contact-holder.js";
46
47
  import { crmRoutes } from "../model/crm-routes.js";
47
- import { LEAD_EMAIL_FILTER_OPTIONS, LEAD_FILTER_CODECS, LEAD_LIST_DECLARATION, LEAD_LIST_FILTER_FIELDS, LEAD_LIST_FILTER_HEADERS, LEAD_PICKLIST_FILTERS, LEAD_SOURCE_DIRECTION_FILTER_OPTIONS, LEAD_SOURCE_FILTER_NONE, leadClausesForGrid, leadStatusFilterOptions, LEAD_PREFIX_SEARCH, leadClauseImpliesScope, leadClausesToStore, leadQueryClause } from "../model/lead-filters.js";
48
+ import { LEAD_EMAIL_FILTER_OPTIONS, LEAD_FILTER_CODECS, LEAD_LIST_DECLARATION, LEAD_LIST_FILTER_FIELDS, LEAD_LIST_SORTS, LEAD_LIST_FILTER_HEADERS, LEAD_PICKLIST_FILTERS, LEAD_SOURCE_DIRECTION_FILTER_OPTIONS, LEAD_SOURCE_FILTER_NONE, leadClausesForGrid, leadStatusFilterOptions, LEAD_PREFIX_SEARCH, leadClauseImpliesScope, leadClausesToStore, leadQueryClause, leadStatusBase } from "../model/lead-filters.js";
48
49
  import { crmAskClauses, crmQueryRefusals } from "../model/crm-list-query.js";
49
50
  import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
50
51
  import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
@@ -263,14 +264,31 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
263
264
  foldsScope
264
265
  ]);
265
266
  const searchKey = gridFilter.searchWords.join(' ');
267
+ /*
268
+ * The Status the list always asks is its BASE (AGL-3680; see
269
+ * `leadStatusBase`), so a header order — served only with no filter on —
270
+ * is served on the Open leads the list opens with. Every other clause
271
+ * stays a filter.
272
+ */ const statusBase = useMemo(()=>leadStatusBase(asked.clauses.find((clause)=>clause.field === 'status')), [
273
+ asked.clauses
274
+ ]);
275
+ const filterClauses = useMemo(()=>asked.clauses.filter((clause)=>clause.field !== 'status'), [
276
+ asked.clauses
277
+ ]);
278
+ // The header order the view asks (AGL-3680), read before the query it orders.
279
+ const leadSort = useMemo(()=>crmViewQuerySort(LEAD_LIST_SORTS, views.state.sort, LEAD_LIST_SORTS[0]), [
280
+ views.state.sort
281
+ ]);
266
282
  const paged = useCrmListQuery({
267
283
  scope: dataRoot,
268
284
  collection: 'leads',
269
285
  visibleTo: scopeTokens,
270
286
  foldsScope,
271
287
  declaration: LEAD_LIST_DECLARATION,
272
- clauses: asked.clauses,
288
+ clauses: filterClauses,
273
289
  search: gridFilter.searchWords,
290
+ base: statusBase,
291
+ sort: leadSort.sort,
274
292
  impliesScope: leadClauseImpliesScope,
275
293
  prefixSearch: LEAD_PREFIX_SEARCH
276
294
  });
@@ -303,11 +321,13 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
303
321
  ]);
304
322
  // The list's filter, when one narrows it, for the export to read the same
305
323
  // records the list does (AGL-3528).
306
- const exportFilter = useMemo(()=>paged.plan.served.length || paged.plan.searched ? {
324
+ // The Status rides the plan's base (AGL-3680), so it narrows the export too.
325
+ const exportFilter = useMemo(()=>paged.plan.served.length || paged.plan.searched || statusBase.length ? {
307
326
  label: 'what the list shows',
308
327
  plan: paged.plan
309
328
  } : null, [
310
- paged.plan
329
+ paged.plan,
330
+ statusBase
311
331
  ]);
312
332
  const [assigning, setAssigning] = useState(null);
313
333
  const [unqualifying, setUnqualifying] = useState(null);
@@ -729,6 +749,34 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
729
749
  filterOptions
730
750
  ]);
731
751
  const grid = useCrmViewGrid(views, filterColumns, LEAD_HIDDEN_COLUMNS);
752
+ /*
753
+ * EVERY HEADER SORTS (AGL-3680): the stored ones on the query
754
+ * (`LEAD_LIST_SORTS`), the rest over the page, saying so — Status by its
755
+ * meaning's place, and the picklist columns in the order the org keeps
756
+ * their values, as a picklist sorts.
757
+ */ const pageSorts = useMemo(()=>{
758
+ const rank = (set, label)=>set && String(label != null ? label : '').trim() ? Aglyn.crmPicklistRank(set, label) : null;
759
+ return _extends({
760
+ status: (row)=>Aglyn.CRM_LEAD_STATUSES.indexOf(Aglyn.crmLeadStatus(row)),
761
+ leadSource: (row)=>rank(leadSourceList.picklist, row.leadSource)
762
+ }, Object.fromEntries(LEAD_PICKLIST_FILTERS.map((entry)=>[
763
+ entry.column,
764
+ (row)=>rank(leadPicklists.lists[entry.picklistId], row[entry.column])
765
+ ])));
766
+ }, [
767
+ leadSourceList.picklist,
768
+ leadPicklists.lists
769
+ ]);
770
+ const columnSort = useCrmColumnSort({
771
+ views,
772
+ sorts: LEAD_LIST_SORTS,
773
+ defaultSort: LEAD_LIST_SORTS[0],
774
+ asked: leadSort,
775
+ orderBy: paged.plan.orderBy,
776
+ rows,
777
+ columns: grid.columns,
778
+ pageSorts
779
+ });
732
780
  return /*#__PURE__*/ _jsxs(_Fragment, {
733
781
  children: [
734
782
  /*#__PURE__*/ _jsx(CardDisplay, {
@@ -787,7 +835,10 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
787
835
  }),
788
836
  /*#__PURE__*/ _jsx(ListQueryNotices, {
789
837
  refused: refused,
790
- notices: paged.plan.notices
838
+ notices: [
839
+ ...paged.plan.notices,
840
+ ...columnSort.notices
841
+ ]
791
842
  }),
792
843
  /*#__PURE__*/ _jsx(LeadsBulkBar, {
793
844
  rows: rows,
@@ -801,7 +852,8 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
801
852
  /*#__PURE__*/ _jsx(CrmColumnOrderProvider, {
802
853
  value: grid.columnOrder,
803
854
  children: /*#__PURE__*/ _jsx(ListTable, {
804
- rows: rows,
855
+ rows: columnSort.rows,
856
+ columnSort: columnSort,
805
857
  columns: grid.columns,
806
858
  slots: CRM_LIST_SLOTS,
807
859
  selectable: {
@@ -810,13 +862,11 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
810
862
  },
811
863
  loading: status === 'loading',
812
864
  onOpen: (_id, row)=>router.push(routes.lead(row.leadId)),
813
- // Columns are the view's, controlled (AGL-2617). The query
814
- // orders the list — newest seen first — so the grid sorts
815
- // nothing itself: a sort over one page would reorder that page.
865
+ // Columns are the view's, controlled (AGL-2617). The headers
866
+ // sort through `columnSort` (AGL-3680): a stored column
867
+ // re-asks the query, any other sorts the page and says so.
816
868
  columnVisibilityModel: grid.columnVisibilityModel,
817
869
  onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
818
- sortingMode: "server",
819
- disableColumnSorting: true,
820
870
  // The panel and the search are the grid's; the query answers
821
871
  // both (AGL-3321).
822
872
  filterMode: "server",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/leads-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport type {\n ConsolePluginPageProps,\n CrmLeadFields,\n CrmViewFilterClause,\n} from '@aglyn/aglyn'\nimport {\n mdiAccountArrowRight,\n mdiAccountCancelOutline,\n mdiAccountConvertOutline,\n mdiAccountTieOutline,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n type OrgMemberOptions,\n useOrgMemberOptions,\n} from '../hooks/use-org-member-options'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport CrmViewsControl from './crm-views-control'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport RowActionsMenu from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Select,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n updateDoc,\n} from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { leadPrimaryGroup } from '../model/contact-holder'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LEAD_EMAIL_FILTER_OPTIONS,\n LEAD_FILTER_CODECS,\n LEAD_LIST_DECLARATION,\n LEAD_LIST_FILTER_FIELDS,\n LEAD_LIST_FILTER_HEADERS,\n LEAD_PICKLIST_FILTERS,\n LEAD_SOURCE_DIRECTION_FILTER_OPTIONS,\n LEAD_SOURCE_FILTER_NONE,\n leadClausesForGrid,\n leadStatusFilterOptions,\n LEAD_PREFIX_SEARCH,\n leadClauseImpliesScope,\n leadClausesToStore,\n leadQueryClause,\n} from '../model/lead-filters'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\nimport {\n type ListFilterOption,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { useLeadStatusPicklist } from '../hooks/use-lead-status-picklist'\nimport { useLeadPicklists } from '../hooks/use-lead-picklists'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport {\n leadSourceLabel,\n leadSources,\n leadTimeLabel,\n} from './lead-history-card'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_LEADS_RESOURCE } from '../transfer/fields'\nimport NewLeadDrawer, { type NewLeadValues } from './new-lead-drawer'\nimport { useCrmApi } from './use-crm-api'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { CONVERT_PENDING_ERASURE_REASON } from './lead-properties-card'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { LeadStatusChip } from './lead-status-chip'\nimport { type LeadStatusChoice, leadStatusChoices, leadStatusMenuItems } from './lead-status-options'\nimport { CrmShareChipView } from './record-sharing-card'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\nimport LeadSurfacesNote from './lead-surfaces-note'\nimport { LeadUnqualifyDialog } from './lead-unqualify-dialog'\nimport LeadsBulkBar from './leads-bulk-bar'\nimport OrgLeadSurfacesNote from './org-lead-surfaces-note'\n\n/**\n * One row of the list. `$id` keys the grid and `leadId` names the document,\n * and they are the same value at both levels (AGL-3275).\n *\n * `$id` used to be `{hostId}/{leadId}` at the organization level, because a\n * lead's id is a person key and the same person met by two sites was two\n * documents carrying it — so the id alone could not key a list that spanned\n * sites. One org collection ends that, and the row no longer carries a site\n * at all: which sites hold this person is `capturedByHostIds`, a fact about\n * the person rather than part of their address.\n */\ntype LeadRow = Record<string, unknown> &\n CrmLeadFields & { $id: string; leadId: string }\n\n/** Salesforce's Industry and Rating columns (AGL-3513), off until a reader turns one on. */\nconst LEAD_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = {\n industry: false,\n rating: false,\n}\n\n/**\n * `/crm/leads` — the people a site has met but not yet qualified (AGL-2608).\n *\n * A section of its own, the way Salesforce keeps Leads apart from Contacts:\n * a lead is a capture — a form, a booking, a sign-up — that somebody has\n * still to work, and it converts into a contact, a company and a deal when\n * it is real. Reads `orgs/{orgId}/leads` narrowed by `visibleTo` to the sites\n * this viewer may see (AGL-3275) — the same collection and the same clause at\n * both levels, which is what lets ONE listener serve a section that used to\n * open one per site — and pages it by a query that carries every filter and\n * the search word (AGL-3321; see `leadQueryClause`).\n *\n * Under a site the clause names that site; at the ORGANIZATION level an\n * org-wide member reads without one, since the rules short-circuit on\n * `isOrgWideMember()` and a clause would only narrow what they may already\n * read. The per-site notes — which of a site's forms file a lead — belong to\n * a site's own hub and are not drawn here.\n */\nexport function CrmLeadsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const firestore = useFirestore()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, createHostId, consentGroup } = useCrmScope({ hostId, org })\n // The viewing site's group: a row it sees only through a share is chipped\n // with who shared it (AGL-3336). None at the organization level.\n const viewingHostIds = consentGroup?.hostIds\n const mount = useCrmOrgMount()\n const roster = useOrgMemberOptions(orgId)\n // The org's lead fields, for the optional columns below (AGL-3272).\n const leadFields = useContactFieldDefinitions(orgId, 'lead')\n // The org's lead source values (AGL-3298): the filter's menu and the\n // column's sort order.\n const leadSourceList = useLeadSourcePicklist(orgId)\n // The org's lead status values (AGL-3512): the chips' words, the inline\n // select's choices and the Status filter's names.\n const leadStatusList = useLeadStatusPicklist(orgId)\n // The org's Industry and Rating lists (AGL-3513): their filters' choices.\n const leadPicklists = useLeadPicklists(orgId)\n const routes = crmRoutes(basePath ?? '')\n\n /*\n * Under a site: the ORG collection, narrowed to what this site may see\n * (AGL-3275) by `visibleTo array-contains-any` over the site's own tokens.\n * At the ORGANIZATION level an org-wide member reads with no scope clause,\n * which is what `null` asks for.\n */\n const scopeTokens = useMemo(\n () => (hostId ? scopeTokensForHost(hostId) : null),\n [hostId],\n )\n const dataRoot = useMemo(\n () => (orgId ? (['orgs', orgId] as const) : null),\n [orgId],\n )\n const foldsScope = useCrmFoldsScope(orgId, scopeTokens)\n\n /*\n * What the list is narrowed by is the saved VIEW'S (AGL-2617), and the\n * grid's own Filters panel and quick search edit it (AGL-3313): Status,\n * Email, Owner, Lead source and Campaign are select columns of the grid.\n * The clauses keep the shape the old dropdowns stored — see\n * `leadClausesForGrid` — so no saved view of leads loses its filter.\n */\n const views = useCrmSavedView({\n section: 'leads',\n hostId,\n org: props.org,\n basePath: basePath ?? '',\n })\n const clauses = useMemo(\n () => leadClausesForGrid(views.state.filters),\n [views.state.filters],\n )\n const setClauses = useCallback(\n (next: CrmViewFilterClause[]) => views.setFilters(leadClausesToStore(next)),\n [views.setFilters],\n )\n const gridFilter = useListGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: [\n 'status',\n 'emailState',\n 'ownerUid',\n 'leadSourceDirection',\n ...LEAD_PICKLIST_FILTERS.map((entry) => entry.column),\n ],\n codecs: LEAD_FILTER_CODECS,\n })\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: true })\n const campaignName = useCallback(\n (id: string) =>\n campaigns.options.find((option) => option.value === id)?.label ?? id,\n [campaigns.options],\n )\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed — a retired campaign, a lead source since removed —\n * stays a choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo<Record<string, ListFilterOption[]>>(() => {\n const stale = (field: string, known: readonly { value: string }[]) =>\n clauses\n .filter((clause) => clause.field === field && clause.op !== 'isEmpty')\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n const campaignOptions = campaigns.options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n // Salesforce's Lead Source (AGL-3298): every value the org keeps, inactive ones marked.\n const sourceOptions = [\n ...leadSourceList.picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: LEAD_SOURCE_FILTER_NONE, label: 'No lead source' },\n ]\n /*\n * Industry and Rating (AGL-3513): every value the org keeps, valued by\n * the key the query compares and captioned by the label, inactive ones\n * marked — the Companies list's choices.\n */\n const picklistOptions = Object.fromEntries(\n LEAD_PICKLIST_FILTERS.map((entry) => {\n const known = (leadPicklists.lists[entry.picklistId]?.values ?? []).flatMap((value) => {\n const key = Aglyn.crmPicklistKey(value.label)\n return key\n ? [{ value: key, label: value.active ? value.label : `${value.label} (inactive)` }]\n : []\n })\n return [entry.column, [...known, ...stale(entry.column, known)]]\n }),\n )\n return {\n ...picklistOptions,\n status: leadStatusFilterOptions(leadStatusList.picklist),\n emailState: LEAD_EMAIL_FILTER_OPTIONS,\n ownerUid: roster.options.map((option) => ({\n value: option.uid,\n label: Aglyn.crmMemberPickerLabel(option),\n })),\n campaignIds: [...campaignOptions, ...stale('campaignIds', campaignOptions)],\n leadSource: [...sourceOptions, ...stale('leadSource', sourceOptions)],\n // The picklist's groups (AGL-3511): Inbound and Outbound.\n leadSourceDirection: LEAD_SOURCE_DIRECTION_FILTER_OPTIONS,\n }\n }, [\n clauses,\n campaigns.options,\n leadSourceList.picklist,\n leadStatusList.picklist,\n leadPicklists.lists,\n roster.options,\n ])\n /*\n * Every clause and the search word on ONE query (AGL-3321): each stored\n * clause asked through the field its writer keeps (`leadQueryClause`),\n * paged by that query, and what one query cannot hold refused by name\n * above the list — never matched over the rows a page happened to load.\n */\n const asked = useMemo(\n () =>\n crmAskClauses(clauses, (clause) =>\n leadQueryClause(clause, { scopeTokens, foldsScope }),\n ),\n [clauses, scopeTokens, foldsScope],\n )\n const searchKey = gridFilter.searchWords.join(' ')\n const paged = useCrmListQuery<Record<string, unknown> & CrmLeadFields & { $id: string }>({\n scope: dataRoot,\n collection: 'leads',\n visibleTo: scopeTokens,\n foldsScope,\n declaration: LEAD_LIST_DECLARATION,\n clauses: asked.clauses,\n search: gridFilter.searchWords,\n impliesScope: leadClauseImpliesScope,\n prefixSearch: LEAD_PREFIX_SEARCH,\n })\n const status = paged.status\n const rows = useMemo<LeadRow[]>(\n () => paged.rows.map((row) => ({ ...row, leadId: row.$id })),\n [paged.rows],\n )\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: LEAD_LIST_FILTER_FIELDS,\n headers: LEAD_LIST_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n /** The list as it opens — Open leads, nothing typed — whose emptiness is news. */\n const opening =\n !searchKey.trim() &&\n clauses.length === 1 &&\n clauses[0].field === 'status' &&\n clauses[0].value === 'open'\n\n /*\n * The ticked rows, for the bulk bar (AGL-2662). Cleared when the filter or\n * the search term changes: a selection made on the open leads is not a\n * selection of the unqualified ones, and the bar's count would be over\n * rows no longer listed.\n */\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n useEffect(\n () => setSelectedIds([]),\n [views.state.filters, searchKey, paged.page],\n )\n\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3528).\n const exportFilter = useMemo(\n () => (paged.plan.served.length || paged.plan.searched ? { label: 'what the list shows', plan: paged.plan } : null),\n [paged.plan],\n )\n\n const [assigning, setAssigning] = useState<LeadRow | null>(null)\n const [unqualifying, setUnqualifying] = useState<LeadRow | null>(null)\n // The Unqualified value the row's select picked (AGL-3512).\n const [unqualifyAs, setUnqualifyAs] = useState<string | null>(null)\n // The row whose conversion dialog is open (AGL-2641) — the same dialog\n // the lead's page opens, fed the row so the list is one click shorter.\n const [converting, setConverting] = useState<LeadRow | null>(null)\n\n /*==========================================\n * NEW LEAD (AGL-3231) — Salesforce's New Lead, in a drawer over the\n * list. The route files the lead through the one lead door under the\n * mounted site, or at the organization level under the site the drawer's\n * picker named; the drawer holds its submit until one is known. It makes\n * a lead and nothing else — the conversion is what makes the contact.\n *=========================================*/\n const crmApi = useCrmApi(createHostId)\n const [createOpen, setCreateOpen] = useState(false)\n const [createBusy, setCreateBusy] = useState(false)\n const [createError, setCreateError] = useState<string | null>(null)\n const handleCreate = useCallback(\n async (values: NewLeadValues) => {\n setCreateBusy(true)\n setCreateError(null)\n try {\n const { response, payload } = await crmApi('leads-create', {\n email: values.email,\n ...(values.name ? { name: values.name } : {}),\n ...(values.company ? { company: values.company } : {}),\n ...(values.jobTitle ? { jobTitle: values.jobTitle } : {}),\n ...(values.phone ? { phone: values.phone } : {}),\n ...(values.website ? { website: values.website } : {}),\n ...(values.leadSource ? { leadSource: values.leadSource } : {}),\n ...(values.address ? { address: values.address } : {}),\n ...(values.tags.length ? { tags: values.tags } : {}),\n ...(values.campaignIds.length\n ? { campaignIds: values.campaignIds }\n : {}),\n ...(values.ownerUid ? { ownerUid: values.ownerUid } : {}),\n ...(values.notes ? { notes: values.notes } : {}),\n // The org's own lead fields (AGL-3272), sent only when one was\n // filled — the route reads the definitions to judge the map, and\n // a body without it pays for no read.\n ...(values.custom ? { custom: values.custom } : {}),\n // Salesforce's standard lead fields (AGL-3513), the filled ones.\n ...values.standard,\n status: values.status,\n })\n if (!response.ok) {\n // The route's own sentence, shown above the form unchanged.\n setCreateError(\n String(payload['error'] ?? 'The lead could not be added.'),\n )\n return\n }\n // The activity entry is the route's: it verified the caller and\n // performed the write.\n enqueueSnackbar(\n payload['created']\n ? 'Lead added'\n : 'This site already held a lead for that address — it was updated',\n { variant: 'success', persist: false },\n )\n setCreateOpen(false)\n } catch (error) {\n console.error(error)\n setCreateError('The lead could not be added.')\n } finally {\n setCreateBusy(false)\n }\n },\n [crmApi, enqueueSnackbar],\n )\n\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n const writeLead = useCallback(\n async (lead: LeadRow, fields: Record<string, unknown>, done: string) => {\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n try {\n await updateDoc(\n doc(firestore, 'orgs', orgId, 'leads', lead.leadId),\n {\n ...fields,\n updatedAt: serverTimestamp(),\n },\n )\n followUpSharing('leads', [lead.leadId])\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error\n ? error.message\n : 'The lead could not be updated.',\n { variant: 'error' },\n )\n }\n },\n [firestore, enqueueSnackbar, followUpSharing],\n )\n\n const columns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'name',\n headerName: 'Lead',\n flex: 1.4,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n String(row['name'] || row['email'] || ''),\n renderCell: ({ row }: { row: LeadRow }) => (\n <Stack\n spacing={0}\n sx={{ minWidth: 0, justifyContent: 'center', height: '100%' }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {String(row['name'] || row['email'] || row.$id)}\n </Typography>\n <CrmShareChipView chip={crmShareChipFor(row, viewingHostIds ?? [])} org={org} />\n </Stack>\n {row['name'] ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {String(row['email'] ?? '')}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n // The lead's own profile (AGL-3231): the two facts a work queue is\n // scanned by, beside the person.\n {\n field: 'company',\n headerName: 'Company',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => String(row.company ?? ''),\n },\n {\n field: 'jobTitle',\n headerName: 'Title',\n flex: 0.9,\n minWidth: 130,\n valueGetter: (_value, row: LeadRow) => String(row.jobTitle ?? ''),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.9,\n minWidth: 150,\n // The meaning, which the Status filter asks; the cell shows the org's label.\n valueGetter: (_value, row: LeadRow) => Aglyn.crmLeadStatus(row),\n renderCell: ({ row }: { row: LeadRow }) => (\n <InlineStatus\n lead={row}\n statuses={leadStatusList.picklist}\n onChange={(next) => {\n if (next.status === 'unqualified') {\n setUnqualifying(row)\n setUnqualifyAs(next.label)\n return\n }\n void writeLead(\n row,\n {\n status: next.status,\n statusLabel: next.label,\n ...(Aglyn.crmLeadStatus(row) === 'unqualified'\n ? { unqualifiedReason: deleteField() }\n : {}),\n },\n 'Status updated',\n )\n }}\n />\n ),\n },\n {\n /*\n * The verdict on the address (AGL-3245): the chip the lead's page\n * carries, so a bounced lead is told apart in the queue it is worked\n * from; its label for the sort and the export.\n */\n field: 'emailState',\n headerName: 'Email',\n flex: 0.9,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => {\n const state = Aglyn.readEmailState(row)\n return state ? Aglyn.EMAIL_STATE_LABELS[state.status] : ''\n },\n renderCell: ({ row }: { row: LeadRow }) => {\n const state = Aglyn.readEmailState(row)\n return state ? (\n <CrmEmailStateChip state={state} />\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n row.ownerUid ? roster.labelFor(row.ownerUid) : 'Unassigned',\n },\n // Only at the organization level, where a row can be any site's.\n ...(hostId\n ? []\n : [\n {\n field: 'hostId',\n headerName: 'Site',\n flex: 0.9,\n minWidth: 140,\n /*\n * KNOWN BY, not \"the site\" (AGL-3275). A lead is one org row\n * and several sites in a consent group can hold the same\n * person, so this names every site that captured them — the\n * answer the Contacts list has always given in its own column.\n */\n valueGetter: (_value: unknown, row: LeadRow) => {\n const held = Array.isArray(row['capturedByHostIds'])\n ? (row['capturedByHostIds'] as string[])\n : []\n return held.map((id) => mount?.siteName(id) ?? id).join(', ')\n },\n } satisfies GridColDef,\n ]),\n {\n field: 'sources',\n headerName: 'Source',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n leadSources(row).map(leadSourceLabel).join(', '),\n },\n /*\n * Salesforce's Lead Source (AGL-3298), beside the surfaces that\n * captured the person. Sorted in the order the org keeps its values,\n * as a picklist sorts, with a value the list does not hold after\n * every listed one and a lead with none last.\n */\n {\n field: 'leadSource',\n headerName: 'Lead source',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => String(row.leadSource ?? ''),\n sortComparator: (a: unknown, b: unknown) =>\n Aglyn.crmPicklistRank(leadSourceList.picklist, a) -\n Aglyn.crmPicklistRank(leadSourceList.picklist, b) ||\n String(a ?? '').localeCompare(String(b ?? '')),\n } satisfies GridColDef,\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => (row.tags ?? []).join(', '),\n },\n /*\n * Salesforce's Industry and Rating (AGL-3513), optional: valued by\n * the key their filters compare, drawn as the label the lead holds.\n */\n ...LEAD_PICKLIST_FILTERS.map(\n (entry): GridColDef => ({\n field: entry.column,\n headerName: entry.header,\n flex: 0.8,\n minWidth: 130,\n sortable: false,\n valueGetter: (_value, row: LeadRow) => Aglyn.crmPicklistKey(row[entry.column]) ?? '',\n renderCell: ({ row }: { row: LeadRow }) => String(row[entry.column] ?? '') || '—',\n }),\n ),\n // The campaigns the lead is filed under (AGL-3254), by name — the\n // ids are the storage.\n {\n field: 'campaignIds',\n headerName: 'Campaign',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value: unknown, row: LeadRow) =>\n Aglyn.readContainerIds(row, 'campaign').map(campaignName).join(', '),\n } satisfies GridColDef,\n {\n field: 'lastSeenAtMs',\n headerName: 'Last seen',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n leadTimeLabel(row['lastSeenAtMs'] ?? row['createdAt']),\n },\n // The org's lead fields as optional columns (AGL-3272), read off the\n // row's own `custom` map the way the contacts list reads its own.\n // Ahead of the row menu, so the overflow stays at the right edge\n // however many fields the org has defined.\n ...customFieldColumns(leadFields.active),\n {\n field: 'actions',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n disableColumnMenu: true,\n renderCell: ({ row }: { row: LeadRow }) => {\n /*\n * Why Convert is refused, in the order the lead's page refuses it:\n * a converted lead has its contact already, a closed one was\n * judged not real, and a person with an erasure pending must not\n * be captured again — a conversion is a capture (AGL-2623).\n */\n const converted = Boolean(row.convertedContactId)\n const erasurePending = Aglyn.readErasureRequestedAtMs(row) !== null\n const convertRefusal = converted\n ? 'This lead was converted'\n : !Aglyn.isCrmLeadOpen(row)\n ? 'This lead was unqualified'\n : erasurePending\n ? CONVERT_PENDING_ERASURE_REASON\n : null\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{ display: 'flex', alignItems: 'center', height: '100%' }}\n >\n <RowActionsMenu\n label={String(row['email'] ?? row.$id)}\n items={[\n {\n key: 'open',\n label: 'Open lead',\n icon: (\n <MdiIcon path={mdiAccountArrowRight.path} size={0.8} />\n ),\n href: routes.lead(row.leadId),\n },\n {\n key: 'convert',\n label: 'Convert…',\n icon: (\n <MdiIcon\n path={mdiAccountConvertOutline.path}\n size={0.8}\n />\n ),\n onClick: () => setConverting(row),\n disabled: convertRefusal !== null,\n disabledReason: convertRefusal ?? undefined,\n },\n {\n key: 'assign',\n label: 'Assign owner',\n icon: (\n <MdiIcon path={mdiAccountTieOutline.path} size={0.8} />\n ),\n onClick: () => setAssigning(row),\n },\n {\n key: 'unqualify',\n label: 'Unqualify',\n icon: (\n <MdiIcon path={mdiAccountCancelOutline.path} size={0.8} />\n ),\n onClick: () => {\n setUnqualifyAs(null)\n setUnqualifying(row)\n },\n disabled:\n !Aglyn.isCrmLeadOpen(row) ||\n Boolean(row.convertedContactId),\n disabledReason: row.convertedContactId\n ? 'This lead was converted'\n : 'This lead is already closed',\n },\n ]}\n />\n </Box>\n )\n },\n },\n ],\n [\n roster,\n routes,\n writeLead,\n hostId,\n viewingHostIds,\n org,\n mount,\n campaignName,\n leadFields.active,\n leadSourceList.picklist,\n leadStatusList.picklist,\n ],\n )\n /*\n * The column and sort models are the view's (AGL-2617); the filterable\n * columns are the declared fields, as selects over their choices (AGL-3313).\n */\n const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, LEAD_LIST_FILTER_FIELDS, filterOptions, LEAD_LIST_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, LEAD_HIDDEN_COLUMNS)\n\n return (\n <>\n <CardDisplay\n header={'Leads'}\n help={Aglyn.pluginDocsHelp('crmLeads', {\n anchor: '#filter-the-leads',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_LEADS_RESOURCE} noun=\"leads\" hostId={hostId} mappingZone=\"leads\" />\n <CrmExportButton resource={CRM_LEADS_RESOURCE} hostId={hostId} filter={exportFilter} />\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => setCreateOpen(true)}\n >\n {'New lead'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n {/* Which surfaces file a lead, by name (AGL-2612) — under a site its own, at the org level every site's (AGL-2638). */}\n {hostId ? (\n <LeadSurfacesNote hostId={hostId} />\n ) : (\n <OrgLeadSurfacesNote />\n )}\n {/*\n The saved view, and every clause it is narrowed by as a chip\n (AGL-3313). The clauses are set in the grid's own Filters panel;\n the chips show the whole set, which that panel shows one of.\n */}\n <CrmListToolbar label=\"Lead filters\">\n <CrmViewsControl controller={views} allLabel=\"All leads\" />\n <ListFilterChips\n fields={LEAD_LIST_FILTER_FIELDS}\n headers={LEAD_LIST_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={paged.plan.notices} />\n <LeadsBulkBar\n rows={rows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n roster={roster}\n orgId={orgId}\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={rows}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{\n selected: selectedIds,\n onChange: setSelectedIds,\n }}\n loading={status === 'loading'}\n onOpen={(_id, row: LeadRow) => router.push(routes.lead(row.leadId))}\n // Columns are the view's, controlled (AGL-2617). The query\n // orders the list — newest seen first — so the grid sorts\n // nothing itself: a sort over one page would reorder that page.\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortingMode=\"server\"\n disableColumnSorting\n // The panel and the search are the grid's; the query answers\n // both (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel={opening ? 'No open leads' : 'No leads match these filters'}\n noRowsDescription={\n opening\n ? 'Bookings and lead-routed forms on your site become leads on their own — or add one with New lead, or bring a list in with Import CSV.'\n : undefined\n }\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n </CardDisplay>\n <NewLeadDrawer\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n hostId={hostId ?? null}\n org={org as Record<string, unknown> | undefined}\n busy={createBusy}\n error={createError}\n roster={roster}\n orgId={orgId}\n onSubmit={(values) => void handleCreate(values)}\n />\n <AssignOwnerDialog\n lead={assigning}\n roster={roster}\n onClose={() => setAssigning(null)}\n onAssign={(uid) => {\n if (!assigning) return\n void writeLead(\n assigning,\n { ownerUid: uid || deleteField() },\n uid ? 'Owner assigned' : 'Owner cleared',\n )\n setAssigning(null)\n }}\n />\n <LeadUnqualifyDialog\n open={Boolean(unqualifying)}\n onClose={() => setUnqualifying(null)}\n hostId={hostId ?? null}\n leadId={unqualifying?.leadId ?? ''}\n leadLabel={String(\n unqualifying?.['name'] || unqualifying?.['email'] || '',\n )}\n statusLabel={unqualifyAs}\n />\n {/* The site the conversion is filed as (AGL-2641), which since\n AGL-3275 is the first site that captured this person rather than\n \"the site the row lives under\" — a lead shared by a consent group\n lives under none of them in particular. Under a site the mounted\n one is used, and the two agree for every single-brand org. */}\n <LeadConvertDialog\n open={Boolean(converting)}\n onClose={() => setConverting(null)}\n hostId={\n hostId ??\n (leadPrimaryGroup(converting, org as Record<string, unknown>).hostId || null)\n }\n orgId={orgId}\n org={org as Record<string, unknown> | undefined}\n leadId={converting?.leadId ?? ''}\n lead={converting ?? {}}\n basePath={basePath ?? ''}\n roster={roster}\n />\n </>\n )\n}\nCrmLeadsSection.displayName = 'CrmLeadsSection'\n\n/**\n * The status, editable in place for a lead that is still open.\n *\n * A converted lead shows the chip alone: its status IS the conversion, and\n * the route stamped it. The select stops its click from reaching the row, or\n * every status change would also open the record.\n */\nfunction InlineStatus(props: {\n lead: LeadRow\n statuses: Aglyn.CrmPicklist\n onChange: (next: LeadStatusChoice) => void\n}) {\n const { lead, statuses, onChange } = props\n if (lead.convertedContactId) {\n return (\n <Box sx={{ display: 'flex', alignItems: 'center', height: '100%' }}>\n <LeadStatusChip lead={lead} statuses={statuses} />\n </Box>\n )\n }\n const choices = leadStatusChoices(statuses, lead)\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{\n display: 'flex',\n alignItems: 'center',\n height: '100%',\n width: '100%',\n }}\n >\n <Select\n size=\"small\"\n variant=\"standard\"\n disableUnderline\n value={Aglyn.crmLeadStatusLabel(lead, statuses)}\n onChange={(event) => {\n const choice = choices.find((entry) => entry.label === event.target.value)\n if (choice) onChange(choice)\n }}\n renderValue={() => <LeadStatusChip lead={lead} statuses={statuses} />}\n sx={{ width: '100%' }}\n >\n {leadStatusMenuItems(choices)}\n </Select>\n </Box>\n )\n}\nInlineStatus.displayName = 'InlineStatus'\n\n/** Hand a lead to a team member. */\nfunction AssignOwnerDialog(props: {\n lead: LeadRow | null\n roster: OrgMemberOptions\n onClose: () => void\n onAssign: (uid: string) => void\n}) {\n const { lead, roster, onClose, onAssign } = props\n const [uid, setUid] = useState<string | null>(null)\n const current = uid ?? String(lead?.ownerUid ?? '')\n return (\n <Dialog\n open={Boolean(lead)}\n onClose={onClose}\n maxWidth=\"xs\"\n fullWidth\n slotProps={{ transition: { onExited: () => setUid(null) } }}\n >\n <DialogTitle>{`Assign ${String(lead?.['name'] || lead?.['email'] || 'lead')}`}</DialogTitle>\n <DialogContent>\n <Box sx={{ pt: 1 }}>\n <LeadOwnerSelect\n value={current}\n onChange={setUid}\n roster={roster}\n size=\"medium\"\n />\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={() => onAssign(current)}>\n {'Assign'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nAssignOwnerDialog.displayName = 'AssignOwnerDialog'\n\nexport default CrmLeadsSection\n"],"names":["Aglyn","mdiAccountArrowRight","mdiAccountCancelOutline","mdiAccountConvertOutline","mdiAccountTieOutline","CardDisplay","MdiIcon","ListPagination","ListTable","useOrgMemberOptions","useCrmOrgMount","useCrmSavedView","useCrmScope","useCrmCampaigns","scopeTokensForHost","useContactFieldDefinitions","customFieldColumns","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","useCrmFoldsScope","useCrmListQuery","CrmViewsControl","CrmListActions","CrmListToolbar","RowActionsMenu","ListQueryNotices","useSnackbar","useFirestore","Box","Button","Dialog","DialogActions","DialogContent","DialogTitle","Select","Stack","Typography","deleteField","doc","serverTimestamp","updateDoc","useRouter","useCallback","useEffect","useMemo","useState","leadPrimaryGroup","crmRoutes","LEAD_EMAIL_FILTER_OPTIONS","LEAD_FILTER_CODECS","LEAD_LIST_DECLARATION","LEAD_LIST_FILTER_FIELDS","LEAD_LIST_FILTER_HEADERS","LEAD_PICKLIST_FILTERS","LEAD_SOURCE_DIRECTION_FILTER_OPTIONS","LEAD_SOURCE_FILTER_NONE","leadClausesForGrid","leadStatusFilterOptions","LEAD_PREFIX_SEARCH","leadClauseImpliesScope","leadClausesToStore","leadQueryClause","crmAskClauses","crmQueryRefusals","listFilterGridColumns","useListGridFilter","ListFilterChips","useLeadSourcePicklist","useLeadStatusPicklist","useLeadPicklists","LeadConvertDialog","leadSourceLabel","leadSources","leadTimeLabel","CrmExportButton","CrmImportButton","CRM_LEADS_RESOURCE","NewLeadDrawer","useCrmApi","LeadOwnerSelect","CONVERT_PENDING_ERASURE_REASON","CrmEmailStateChip","LeadStatusChip","leadStatusChoices","leadStatusMenuItems","CrmShareChipView","crmShareChipFor","useCrmSharingFollowUp","LeadSurfacesNote","LeadUnqualifyDialog","LeadsBulkBar","OrgLeadSurfacesNote","LEAD_HIDDEN_COLUMNS","industry","rating","CrmLeadsSection","props","hostId","org","basePath","firestore","router","enqueueSnackbar","orgId","createHostId","consentGroup","viewingHostIds","hostIds","mount","roster","leadFields","leadSourceList","leadStatusList","leadPicklists","routes","scopeTokens","dataRoot","foldsScope","views","section","clauses","state","filters","setClauses","next","setFilters","gridFilter","onChange","selectFields","map","entry","column","codecs","campaigns","enabled","campaignName","id","options","find","option","value","label","filterOptions","stale","field","known","filter","clause","op","flatMap","split","trim","some","campaignOptions","sourceOptions","picklist","values","active","picklistOptions","Object","fromEntries","lists","picklistId","key","crmPicklistKey","status","emailState","ownerUid","uid","crmMemberPickerLabel","campaignIds","leadSource","leadSourceDirection","asked","searchKey","searchWords","join","paged","scope","collection","visibleTo","declaration","search","impliesScope","prefixSearch","rows","row","leadId","$id","refused","plan","fields","headers","opening","length","selectedIds","setSelectedIds","page","exportFilter","served","searched","assigning","setAssigning","unqualifying","setUnqualifying","unqualifyAs","setUnqualifyAs","converting","setConverting","crmApi","createOpen","setCreateOpen","createBusy","setCreateBusy","createError","setCreateError","handleCreate","response","payload","email","name","company","jobTitle","phone","website","address","tags","notes","custom","standard","ok","String","variant","persist","error","console","followUpSharing","writeLead","lead","done","updatedAt","Error","message","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","spacing","sx","justifyContent","height","direction","alignItems","noWrap","chip","color","crmLeadStatus","InlineStatus","statuses","statusLabel","unqualifiedReason","readEmailState","EMAIL_STATE_LABELS","labelFor","held","Array","isArray","siteName","sortComparator","a","b","crmPicklistRank","localeCompare","header","sortable","readContainerIds","width","filterable","disableColumnMenu","converted","Boolean","convertedContactId","erasurePending","readErasureRequestedAtMs","convertRefusal","isCrmLeadOpen","onClick","event","stopPropagation","display","items","icon","path","size","href","disabled","disabledReason","undefined","filterColumns","grid","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","mappingZone","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","slots","selectable","loading","onOpen","_id","push","columnVisibilityModel","onColumnVisibilityModelChange","sortingMode","disableColumnSorting","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","noRowsDescription","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","open","onClose","busy","onSubmit","AssignOwnerDialog","onAssign","leadLabel","displayName","choices","disableUnderline","crmLeadStatusLabel","choice","target","renderValue","setUid","current","maxWidth","fullWidth","slotProps","transition","onExited","pt"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/leads-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport type {\n ConsolePluginPageProps,\n CrmLeadFields,\n CrmViewFilterClause,\n} from '@aglyn/aglyn'\nimport {\n mdiAccountArrowRight,\n mdiAccountCancelOutline,\n mdiAccountConvertOutline,\n mdiAccountTieOutline,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n type OrgMemberOptions,\n useOrgMemberOptions,\n} from '../hooks/use-org-member-options'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport { crmViewQuerySort, useCrmColumnSort } from '../hooks/use-crm-column-sort'\nimport CrmViewsControl from './crm-views-control'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport RowActionsMenu from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Select,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n updateDoc,\n} from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { leadPrimaryGroup } from '../model/contact-holder'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LEAD_EMAIL_FILTER_OPTIONS,\n LEAD_FILTER_CODECS,\n LEAD_LIST_DECLARATION,\n LEAD_LIST_FILTER_FIELDS,\n LEAD_LIST_SORTS,\n LEAD_LIST_FILTER_HEADERS,\n LEAD_PICKLIST_FILTERS,\n LEAD_SOURCE_DIRECTION_FILTER_OPTIONS,\n LEAD_SOURCE_FILTER_NONE,\n leadClausesForGrid,\n leadStatusFilterOptions,\n LEAD_PREFIX_SEARCH,\n leadClauseImpliesScope,\n leadClausesToStore,\n leadQueryClause,\n leadStatusBase,\n} from '../model/lead-filters'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\nimport {\n type ListFilterOption,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { useLeadStatusPicklist } from '../hooks/use-lead-status-picklist'\nimport { useLeadPicklists } from '../hooks/use-lead-picklists'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport {\n leadSourceLabel,\n leadSources,\n leadTimeLabel,\n} from './lead-history-card'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_LEADS_RESOURCE } from '../transfer/fields'\nimport NewLeadDrawer, { type NewLeadValues } from './new-lead-drawer'\nimport { useCrmApi } from './use-crm-api'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { CONVERT_PENDING_ERASURE_REASON } from './lead-properties-card'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { LeadStatusChip } from './lead-status-chip'\nimport { type LeadStatusChoice, leadStatusChoices, leadStatusMenuItems } from './lead-status-options'\nimport { CrmShareChipView } from './record-sharing-card'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\nimport LeadSurfacesNote from './lead-surfaces-note'\nimport { LeadUnqualifyDialog } from './lead-unqualify-dialog'\nimport LeadsBulkBar from './leads-bulk-bar'\nimport OrgLeadSurfacesNote from './org-lead-surfaces-note'\n\n/**\n * One row of the list. `$id` keys the grid and `leadId` names the document,\n * and they are the same value at both levels (AGL-3275).\n *\n * `$id` used to be `{hostId}/{leadId}` at the organization level, because a\n * lead's id is a person key and the same person met by two sites was two\n * documents carrying it — so the id alone could not key a list that spanned\n * sites. One org collection ends that, and the row no longer carries a site\n * at all: which sites hold this person is `capturedByHostIds`, a fact about\n * the person rather than part of their address.\n */\ntype LeadRow = Record<string, unknown> &\n CrmLeadFields & { $id: string; leadId: string }\n\n/** Salesforce's Industry and Rating columns (AGL-3513), off until a reader turns one on. */\nconst LEAD_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = {\n industry: false,\n rating: false,\n}\n\n/**\n * `/crm/leads` — the people a site has met but not yet qualified (AGL-2608).\n *\n * A section of its own, the way Salesforce keeps Leads apart from Contacts:\n * a lead is a capture — a form, a booking, a sign-up — that somebody has\n * still to work, and it converts into a contact, a company and a deal when\n * it is real. Reads `orgs/{orgId}/leads` narrowed by `visibleTo` to the sites\n * this viewer may see (AGL-3275) — the same collection and the same clause at\n * both levels, which is what lets ONE listener serve a section that used to\n * open one per site — and pages it by a query that carries every filter and\n * the search word (AGL-3321; see `leadQueryClause`).\n *\n * Under a site the clause names that site; at the ORGANIZATION level an\n * org-wide member reads without one, since the rules short-circuit on\n * `isOrgWideMember()` and a clause would only narrow what they may already\n * read. The per-site notes — which of a site's forms file a lead — belong to\n * a site's own hub and are not drawn here.\n */\nexport function CrmLeadsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const firestore = useFirestore()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, createHostId, consentGroup } = useCrmScope({ hostId, org })\n // The viewing site's group: a row it sees only through a share is chipped\n // with who shared it (AGL-3336). None at the organization level.\n const viewingHostIds = consentGroup?.hostIds\n const mount = useCrmOrgMount()\n const roster = useOrgMemberOptions(orgId)\n // The org's lead fields, for the optional columns below (AGL-3272).\n const leadFields = useContactFieldDefinitions(orgId, 'lead')\n // The org's lead source values (AGL-3298): the filter's menu and the\n // column's sort order.\n const leadSourceList = useLeadSourcePicklist(orgId)\n // The org's lead status values (AGL-3512): the chips' words, the inline\n // select's choices and the Status filter's names.\n const leadStatusList = useLeadStatusPicklist(orgId)\n // The org's Industry and Rating lists (AGL-3513): their filters' choices.\n const leadPicklists = useLeadPicklists(orgId)\n const routes = crmRoutes(basePath ?? '')\n\n /*\n * Under a site: the ORG collection, narrowed to what this site may see\n * (AGL-3275) by `visibleTo array-contains-any` over the site's own tokens.\n * At the ORGANIZATION level an org-wide member reads with no scope clause,\n * which is what `null` asks for.\n */\n const scopeTokens = useMemo(\n () => (hostId ? scopeTokensForHost(hostId) : null),\n [hostId],\n )\n const dataRoot = useMemo(\n () => (orgId ? (['orgs', orgId] as const) : null),\n [orgId],\n )\n const foldsScope = useCrmFoldsScope(orgId, scopeTokens)\n\n /*\n * What the list is narrowed by is the saved VIEW'S (AGL-2617), and the\n * grid's own Filters panel and quick search edit it (AGL-3313): Status,\n * Email, Owner, Lead source and Campaign are select columns of the grid.\n * The clauses keep the shape the old dropdowns stored — see\n * `leadClausesForGrid` — so no saved view of leads loses its filter.\n */\n const views = useCrmSavedView({\n section: 'leads',\n hostId,\n org: props.org,\n basePath: basePath ?? '',\n })\n const clauses = useMemo(\n () => leadClausesForGrid(views.state.filters),\n [views.state.filters],\n )\n const setClauses = useCallback(\n (next: CrmViewFilterClause[]) => views.setFilters(leadClausesToStore(next)),\n [views.setFilters],\n )\n const gridFilter = useListGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: [\n 'status',\n 'emailState',\n 'ownerUid',\n 'leadSourceDirection',\n ...LEAD_PICKLIST_FILTERS.map((entry) => entry.column),\n ],\n codecs: LEAD_FILTER_CODECS,\n })\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: true })\n const campaignName = useCallback(\n (id: string) =>\n campaigns.options.find((option) => option.value === id)?.label ?? id,\n [campaigns.options],\n )\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed — a retired campaign, a lead source since removed —\n * stays a choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo<Record<string, ListFilterOption[]>>(() => {\n const stale = (field: string, known: readonly { value: string }[]) =>\n clauses\n .filter((clause) => clause.field === field && clause.op !== 'isEmpty')\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n const campaignOptions = campaigns.options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n // Salesforce's Lead Source (AGL-3298): every value the org keeps, inactive ones marked.\n const sourceOptions = [\n ...leadSourceList.picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: LEAD_SOURCE_FILTER_NONE, label: 'No lead source' },\n ]\n /*\n * Industry and Rating (AGL-3513): every value the org keeps, valued by\n * the key the query compares and captioned by the label, inactive ones\n * marked — the Companies list's choices.\n */\n const picklistOptions = Object.fromEntries(\n LEAD_PICKLIST_FILTERS.map((entry) => {\n const known = (leadPicklists.lists[entry.picklistId]?.values ?? []).flatMap((value) => {\n const key = Aglyn.crmPicklistKey(value.label)\n return key\n ? [{ value: key, label: value.active ? value.label : `${value.label} (inactive)` }]\n : []\n })\n return [entry.column, [...known, ...stale(entry.column, known)]]\n }),\n )\n return {\n ...picklistOptions,\n status: leadStatusFilterOptions(leadStatusList.picklist),\n emailState: LEAD_EMAIL_FILTER_OPTIONS,\n ownerUid: roster.options.map((option) => ({\n value: option.uid,\n label: Aglyn.crmMemberPickerLabel(option),\n })),\n campaignIds: [...campaignOptions, ...stale('campaignIds', campaignOptions)],\n leadSource: [...sourceOptions, ...stale('leadSource', sourceOptions)],\n // The picklist's groups (AGL-3511): Inbound and Outbound.\n leadSourceDirection: LEAD_SOURCE_DIRECTION_FILTER_OPTIONS,\n }\n }, [\n clauses,\n campaigns.options,\n leadSourceList.picklist,\n leadStatusList.picklist,\n leadPicklists.lists,\n roster.options,\n ])\n /*\n * Every clause and the search word on ONE query (AGL-3321): each stored\n * clause asked through the field its writer keeps (`leadQueryClause`),\n * paged by that query, and what one query cannot hold refused by name\n * above the list — never matched over the rows a page happened to load.\n */\n const asked = useMemo(\n () =>\n crmAskClauses(clauses, (clause) =>\n leadQueryClause(clause, { scopeTokens, foldsScope }),\n ),\n [clauses, scopeTokens, foldsScope],\n )\n const searchKey = gridFilter.searchWords.join(' ')\n /*\n * The Status the list always asks is its BASE (AGL-3680; see\n * `leadStatusBase`), so a header order — served only with no filter on —\n * is served on the Open leads the list opens with. Every other clause\n * stays a filter.\n */\n const statusBase = useMemo(\n () => leadStatusBase(asked.clauses.find((clause) => clause.field === 'status')),\n [asked.clauses],\n )\n const filterClauses = useMemo(\n () => asked.clauses.filter((clause) => clause.field !== 'status'),\n [asked.clauses],\n )\n // The header order the view asks (AGL-3680), read before the query it orders.\n const leadSort = useMemo(\n () => crmViewQuerySort(LEAD_LIST_SORTS, views.state.sort, LEAD_LIST_SORTS[0]),\n [views.state.sort],\n )\n const paged = useCrmListQuery<Record<string, unknown> & CrmLeadFields & { $id: string }>({\n scope: dataRoot,\n collection: 'leads',\n visibleTo: scopeTokens,\n foldsScope,\n declaration: LEAD_LIST_DECLARATION,\n clauses: filterClauses,\n search: gridFilter.searchWords,\n base: statusBase,\n sort: leadSort.sort,\n impliesScope: leadClauseImpliesScope,\n prefixSearch: LEAD_PREFIX_SEARCH,\n })\n const status = paged.status\n const rows = useMemo<LeadRow[]>(\n () => paged.rows.map((row) => ({ ...row, leadId: row.$id })),\n [paged.rows],\n )\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: LEAD_LIST_FILTER_FIELDS,\n headers: LEAD_LIST_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n /** The list as it opens — Open leads, nothing typed — whose emptiness is news. */\n const opening =\n !searchKey.trim() &&\n clauses.length === 1 &&\n clauses[0].field === 'status' &&\n clauses[0].value === 'open'\n\n /*\n * The ticked rows, for the bulk bar (AGL-2662). Cleared when the filter or\n * the search term changes: a selection made on the open leads is not a\n * selection of the unqualified ones, and the bar's count would be over\n * rows no longer listed.\n */\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n useEffect(\n () => setSelectedIds([]),\n [views.state.filters, searchKey, paged.page],\n )\n\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3528).\n // The Status rides the plan's base (AGL-3680), so it narrows the export too.\n const exportFilter = useMemo(\n () =>\n paged.plan.served.length || paged.plan.searched || statusBase.length\n ? { label: 'what the list shows', plan: paged.plan }\n : null,\n [paged.plan, statusBase],\n )\n\n const [assigning, setAssigning] = useState<LeadRow | null>(null)\n const [unqualifying, setUnqualifying] = useState<LeadRow | null>(null)\n // The Unqualified value the row's select picked (AGL-3512).\n const [unqualifyAs, setUnqualifyAs] = useState<string | null>(null)\n // The row whose conversion dialog is open (AGL-2641) — the same dialog\n // the lead's page opens, fed the row so the list is one click shorter.\n const [converting, setConverting] = useState<LeadRow | null>(null)\n\n /*==========================================\n * NEW LEAD (AGL-3231) — Salesforce's New Lead, in a drawer over the\n * list. The route files the lead through the one lead door under the\n * mounted site, or at the organization level under the site the drawer's\n * picker named; the drawer holds its submit until one is known. It makes\n * a lead and nothing else — the conversion is what makes the contact.\n *=========================================*/\n const crmApi = useCrmApi(createHostId)\n const [createOpen, setCreateOpen] = useState(false)\n const [createBusy, setCreateBusy] = useState(false)\n const [createError, setCreateError] = useState<string | null>(null)\n const handleCreate = useCallback(\n async (values: NewLeadValues) => {\n setCreateBusy(true)\n setCreateError(null)\n try {\n const { response, payload } = await crmApi('leads-create', {\n email: values.email,\n ...(values.name ? { name: values.name } : {}),\n ...(values.company ? { company: values.company } : {}),\n ...(values.jobTitle ? { jobTitle: values.jobTitle } : {}),\n ...(values.phone ? { phone: values.phone } : {}),\n ...(values.website ? { website: values.website } : {}),\n ...(values.leadSource ? { leadSource: values.leadSource } : {}),\n ...(values.address ? { address: values.address } : {}),\n ...(values.tags.length ? { tags: values.tags } : {}),\n ...(values.campaignIds.length\n ? { campaignIds: values.campaignIds }\n : {}),\n ...(values.ownerUid ? { ownerUid: values.ownerUid } : {}),\n ...(values.notes ? { notes: values.notes } : {}),\n // The org's own lead fields (AGL-3272), sent only when one was\n // filled — the route reads the definitions to judge the map, and\n // a body without it pays for no read.\n ...(values.custom ? { custom: values.custom } : {}),\n // Salesforce's standard lead fields (AGL-3513), the filled ones.\n ...values.standard,\n status: values.status,\n })\n if (!response.ok) {\n // The route's own sentence, shown above the form unchanged.\n setCreateError(\n String(payload['error'] ?? 'The lead could not be added.'),\n )\n return\n }\n // The activity entry is the route's: it verified the caller and\n // performed the write.\n enqueueSnackbar(\n payload['created']\n ? 'Lead added'\n : 'This site already held a lead for that address — it was updated',\n { variant: 'success', persist: false },\n )\n setCreateOpen(false)\n } catch (error) {\n console.error(error)\n setCreateError('The lead could not be added.')\n } finally {\n setCreateBusy(false)\n }\n },\n [crmApi, enqueueSnackbar],\n )\n\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n const writeLead = useCallback(\n async (lead: LeadRow, fields: Record<string, unknown>, done: string) => {\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n try {\n await updateDoc(\n doc(firestore, 'orgs', orgId, 'leads', lead.leadId),\n {\n ...fields,\n updatedAt: serverTimestamp(),\n },\n )\n followUpSharing('leads', [lead.leadId])\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error\n ? error.message\n : 'The lead could not be updated.',\n { variant: 'error' },\n )\n }\n },\n [firestore, enqueueSnackbar, followUpSharing],\n )\n\n const columns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'name',\n headerName: 'Lead',\n flex: 1.4,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n String(row['name'] || row['email'] || ''),\n renderCell: ({ row }: { row: LeadRow }) => (\n <Stack\n spacing={0}\n sx={{ minWidth: 0, justifyContent: 'center', height: '100%' }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {String(row['name'] || row['email'] || row.$id)}\n </Typography>\n <CrmShareChipView chip={crmShareChipFor(row, viewingHostIds ?? [])} org={org} />\n </Stack>\n {row['name'] ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {String(row['email'] ?? '')}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n // The lead's own profile (AGL-3231): the two facts a work queue is\n // scanned by, beside the person.\n {\n field: 'company',\n headerName: 'Company',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => String(row.company ?? ''),\n },\n {\n field: 'jobTitle',\n headerName: 'Title',\n flex: 0.9,\n minWidth: 130,\n valueGetter: (_value, row: LeadRow) => String(row.jobTitle ?? ''),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.9,\n minWidth: 150,\n // The meaning, which the Status filter asks; the cell shows the org's label.\n valueGetter: (_value, row: LeadRow) => Aglyn.crmLeadStatus(row),\n renderCell: ({ row }: { row: LeadRow }) => (\n <InlineStatus\n lead={row}\n statuses={leadStatusList.picklist}\n onChange={(next) => {\n if (next.status === 'unqualified') {\n setUnqualifying(row)\n setUnqualifyAs(next.label)\n return\n }\n void writeLead(\n row,\n {\n status: next.status,\n statusLabel: next.label,\n ...(Aglyn.crmLeadStatus(row) === 'unqualified'\n ? { unqualifiedReason: deleteField() }\n : {}),\n },\n 'Status updated',\n )\n }}\n />\n ),\n },\n {\n /*\n * The verdict on the address (AGL-3245): the chip the lead's page\n * carries, so a bounced lead is told apart in the queue it is worked\n * from; its label for the sort and the export.\n */\n field: 'emailState',\n headerName: 'Email',\n flex: 0.9,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => {\n const state = Aglyn.readEmailState(row)\n return state ? Aglyn.EMAIL_STATE_LABELS[state.status] : ''\n },\n renderCell: ({ row }: { row: LeadRow }) => {\n const state = Aglyn.readEmailState(row)\n return state ? (\n <CrmEmailStateChip state={state} />\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n row.ownerUid ? roster.labelFor(row.ownerUid) : 'Unassigned',\n },\n // Only at the organization level, where a row can be any site's.\n ...(hostId\n ? []\n : [\n {\n field: 'hostId',\n headerName: 'Site',\n flex: 0.9,\n minWidth: 140,\n /*\n * KNOWN BY, not \"the site\" (AGL-3275). A lead is one org row\n * and several sites in a consent group can hold the same\n * person, so this names every site that captured them — the\n * answer the Contacts list has always given in its own column.\n */\n valueGetter: (_value: unknown, row: LeadRow) => {\n const held = Array.isArray(row['capturedByHostIds'])\n ? (row['capturedByHostIds'] as string[])\n : []\n return held.map((id) => mount?.siteName(id) ?? id).join(', ')\n },\n } satisfies GridColDef,\n ]),\n {\n field: 'sources',\n headerName: 'Source',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n leadSources(row).map(leadSourceLabel).join(', '),\n },\n /*\n * Salesforce's Lead Source (AGL-3298), beside the surfaces that\n * captured the person. Sorted in the order the org keeps its values,\n * as a picklist sorts, with a value the list does not hold after\n * every listed one and a lead with none last.\n */\n {\n field: 'leadSource',\n headerName: 'Lead source',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => String(row.leadSource ?? ''),\n sortComparator: (a: unknown, b: unknown) =>\n Aglyn.crmPicklistRank(leadSourceList.picklist, a) -\n Aglyn.crmPicklistRank(leadSourceList.picklist, b) ||\n String(a ?? '').localeCompare(String(b ?? '')),\n } satisfies GridColDef,\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => (row.tags ?? []).join(', '),\n },\n /*\n * Salesforce's Industry and Rating (AGL-3513), optional: valued by\n * the key their filters compare, drawn as the label the lead holds.\n */\n ...LEAD_PICKLIST_FILTERS.map(\n (entry): GridColDef => ({\n field: entry.column,\n headerName: entry.header,\n flex: 0.8,\n minWidth: 130,\n sortable: false,\n valueGetter: (_value, row: LeadRow) => Aglyn.crmPicklistKey(row[entry.column]) ?? '',\n renderCell: ({ row }: { row: LeadRow }) => String(row[entry.column] ?? '') || '—',\n }),\n ),\n // The campaigns the lead is filed under (AGL-3254), by name — the\n // ids are the storage.\n {\n field: 'campaignIds',\n headerName: 'Campaign',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value: unknown, row: LeadRow) =>\n Aglyn.readContainerIds(row, 'campaign').map(campaignName).join(', '),\n } satisfies GridColDef,\n {\n field: 'lastSeenAtMs',\n headerName: 'Last seen',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n leadTimeLabel(row['lastSeenAtMs'] ?? row['createdAt']),\n },\n // The org's lead fields as optional columns (AGL-3272), read off the\n // row's own `custom` map the way the contacts list reads its own.\n // Ahead of the row menu, so the overflow stays at the right edge\n // however many fields the org has defined.\n ...customFieldColumns(leadFields.active),\n {\n field: 'actions',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n disableColumnMenu: true,\n renderCell: ({ row }: { row: LeadRow }) => {\n /*\n * Why Convert is refused, in the order the lead's page refuses it:\n * a converted lead has its contact already, a closed one was\n * judged not real, and a person with an erasure pending must not\n * be captured again — a conversion is a capture (AGL-2623).\n */\n const converted = Boolean(row.convertedContactId)\n const erasurePending = Aglyn.readErasureRequestedAtMs(row) !== null\n const convertRefusal = converted\n ? 'This lead was converted'\n : !Aglyn.isCrmLeadOpen(row)\n ? 'This lead was unqualified'\n : erasurePending\n ? CONVERT_PENDING_ERASURE_REASON\n : null\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{ display: 'flex', alignItems: 'center', height: '100%' }}\n >\n <RowActionsMenu\n label={String(row['email'] ?? row.$id)}\n items={[\n {\n key: 'open',\n label: 'Open lead',\n icon: (\n <MdiIcon path={mdiAccountArrowRight.path} size={0.8} />\n ),\n href: routes.lead(row.leadId),\n },\n {\n key: 'convert',\n label: 'Convert…',\n icon: (\n <MdiIcon\n path={mdiAccountConvertOutline.path}\n size={0.8}\n />\n ),\n onClick: () => setConverting(row),\n disabled: convertRefusal !== null,\n disabledReason: convertRefusal ?? undefined,\n },\n {\n key: 'assign',\n label: 'Assign owner',\n icon: (\n <MdiIcon path={mdiAccountTieOutline.path} size={0.8} />\n ),\n onClick: () => setAssigning(row),\n },\n {\n key: 'unqualify',\n label: 'Unqualify',\n icon: (\n <MdiIcon path={mdiAccountCancelOutline.path} size={0.8} />\n ),\n onClick: () => {\n setUnqualifyAs(null)\n setUnqualifying(row)\n },\n disabled:\n !Aglyn.isCrmLeadOpen(row) ||\n Boolean(row.convertedContactId),\n disabledReason: row.convertedContactId\n ? 'This lead was converted'\n : 'This lead is already closed',\n },\n ]}\n />\n </Box>\n )\n },\n },\n ],\n [\n roster,\n routes,\n writeLead,\n hostId,\n viewingHostIds,\n org,\n mount,\n campaignName,\n leadFields.active,\n leadSourceList.picklist,\n leadStatusList.picklist,\n ],\n )\n /*\n * The column and sort models are the view's (AGL-2617); the filterable\n * columns are the declared fields, as selects over their choices (AGL-3313).\n */\n const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, LEAD_LIST_FILTER_FIELDS, filterOptions, LEAD_LIST_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, LEAD_HIDDEN_COLUMNS)\n /*\n * EVERY HEADER SORTS (AGL-3680): the stored ones on the query\n * (`LEAD_LIST_SORTS`), the rest over the page, saying so — Status by its\n * meaning's place, and the picklist columns in the order the org keeps\n * their values, as a picklist sorts.\n */\n const pageSorts = useMemo(() => {\n const rank = (set: Parameters<typeof Aglyn.crmPicklistRank>[0] | undefined, label: unknown) =>\n set && String(label ?? '').trim() ? Aglyn.crmPicklistRank(set, label) : null\n return {\n status: (row: LeadRow) => Aglyn.CRM_LEAD_STATUSES.indexOf(Aglyn.crmLeadStatus(row)),\n leadSource: (row: LeadRow) => rank(leadSourceList.picklist, row.leadSource),\n ...Object.fromEntries(\n LEAD_PICKLIST_FILTERS.map((entry) => [\n entry.column,\n (row: LeadRow) => rank(leadPicklists.lists[entry.picklistId], row[entry.column]),\n ]),\n ),\n }\n }, [leadSourceList.picklist, leadPicklists.lists])\n const columnSort = useCrmColumnSort<LeadRow>({\n views,\n sorts: LEAD_LIST_SORTS,\n defaultSort: LEAD_LIST_SORTS[0],\n asked: leadSort,\n orderBy: paged.plan.orderBy,\n rows,\n columns: grid.columns,\n pageSorts,\n })\n\n return (\n <>\n <CardDisplay\n header={'Leads'}\n help={Aglyn.pluginDocsHelp('crmLeads', {\n anchor: '#filter-the-leads',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_LEADS_RESOURCE} noun=\"leads\" hostId={hostId} mappingZone=\"leads\" />\n <CrmExportButton resource={CRM_LEADS_RESOURCE} hostId={hostId} filter={exportFilter} />\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => setCreateOpen(true)}\n >\n {'New lead'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n {/* Which surfaces file a lead, by name (AGL-2612) — under a site its own, at the org level every site's (AGL-2638). */}\n {hostId ? (\n <LeadSurfacesNote hostId={hostId} />\n ) : (\n <OrgLeadSurfacesNote />\n )}\n {/*\n The saved view, and every clause it is narrowed by as a chip\n (AGL-3313). The clauses are set in the grid's own Filters panel;\n the chips show the whole set, which that panel shows one of.\n */}\n <CrmListToolbar label=\"Lead filters\">\n <CrmViewsControl controller={views} allLabel=\"All leads\" />\n <ListFilterChips\n fields={LEAD_LIST_FILTER_FIELDS}\n headers={LEAD_LIST_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices\n refused={refused}\n notices={[...paged.plan.notices, ...columnSort.notices]}\n />\n <LeadsBulkBar\n rows={rows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n roster={roster}\n orgId={orgId}\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={columnSort.rows}\n columnSort={columnSort}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{\n selected: selectedIds,\n onChange: setSelectedIds,\n }}\n loading={status === 'loading'}\n onOpen={(_id, row: LeadRow) => router.push(routes.lead(row.leadId))}\n // Columns are the view's, controlled (AGL-2617). The headers\n // sort through `columnSort` (AGL-3680): a stored column\n // re-asks the query, any other sorts the page and says so.\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n // The panel and the search are the grid's; the query answers\n // both (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel={opening ? 'No open leads' : 'No leads match these filters'}\n noRowsDescription={\n opening\n ? 'Bookings and lead-routed forms on your site become leads on their own — or add one with New lead, or bring a list in with Import CSV.'\n : undefined\n }\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n </CardDisplay>\n <NewLeadDrawer\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n hostId={hostId ?? null}\n org={org as Record<string, unknown> | undefined}\n busy={createBusy}\n error={createError}\n roster={roster}\n orgId={orgId}\n onSubmit={(values) => void handleCreate(values)}\n />\n <AssignOwnerDialog\n lead={assigning}\n roster={roster}\n onClose={() => setAssigning(null)}\n onAssign={(uid) => {\n if (!assigning) return\n void writeLead(\n assigning,\n { ownerUid: uid || deleteField() },\n uid ? 'Owner assigned' : 'Owner cleared',\n )\n setAssigning(null)\n }}\n />\n <LeadUnqualifyDialog\n open={Boolean(unqualifying)}\n onClose={() => setUnqualifying(null)}\n hostId={hostId ?? null}\n leadId={unqualifying?.leadId ?? ''}\n leadLabel={String(\n unqualifying?.['name'] || unqualifying?.['email'] || '',\n )}\n statusLabel={unqualifyAs}\n />\n {/* The site the conversion is filed as (AGL-2641), which since\n AGL-3275 is the first site that captured this person rather than\n \"the site the row lives under\" — a lead shared by a consent group\n lives under none of them in particular. Under a site the mounted\n one is used, and the two agree for every single-brand org. */}\n <LeadConvertDialog\n open={Boolean(converting)}\n onClose={() => setConverting(null)}\n hostId={\n hostId ??\n (leadPrimaryGroup(converting, org as Record<string, unknown>).hostId || null)\n }\n orgId={orgId}\n org={org as Record<string, unknown> | undefined}\n leadId={converting?.leadId ?? ''}\n lead={converting ?? {}}\n basePath={basePath ?? ''}\n roster={roster}\n />\n </>\n )\n}\nCrmLeadsSection.displayName = 'CrmLeadsSection'\n\n/**\n * The status, editable in place for a lead that is still open.\n *\n * A converted lead shows the chip alone: its status IS the conversion, and\n * the route stamped it. The select stops its click from reaching the row, or\n * every status change would also open the record.\n */\nfunction InlineStatus(props: {\n lead: LeadRow\n statuses: Aglyn.CrmPicklist\n onChange: (next: LeadStatusChoice) => void\n}) {\n const { lead, statuses, onChange } = props\n if (lead.convertedContactId) {\n return (\n <Box sx={{ display: 'flex', alignItems: 'center', height: '100%' }}>\n <LeadStatusChip lead={lead} statuses={statuses} />\n </Box>\n )\n }\n const choices = leadStatusChoices(statuses, lead)\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{\n display: 'flex',\n alignItems: 'center',\n height: '100%',\n width: '100%',\n }}\n >\n <Select\n size=\"small\"\n variant=\"standard\"\n disableUnderline\n value={Aglyn.crmLeadStatusLabel(lead, statuses)}\n onChange={(event) => {\n const choice = choices.find((entry) => entry.label === event.target.value)\n if (choice) onChange(choice)\n }}\n renderValue={() => <LeadStatusChip lead={lead} statuses={statuses} />}\n sx={{ width: '100%' }}\n >\n {leadStatusMenuItems(choices)}\n </Select>\n </Box>\n )\n}\nInlineStatus.displayName = 'InlineStatus'\n\n/** Hand a lead to a team member. */\nfunction AssignOwnerDialog(props: {\n lead: LeadRow | null\n roster: OrgMemberOptions\n onClose: () => void\n onAssign: (uid: string) => void\n}) {\n const { lead, roster, onClose, onAssign } = props\n const [uid, setUid] = useState<string | null>(null)\n const current = uid ?? String(lead?.ownerUid ?? '')\n return (\n <Dialog\n open={Boolean(lead)}\n onClose={onClose}\n maxWidth=\"xs\"\n fullWidth\n slotProps={{ transition: { onExited: () => setUid(null) } }}\n >\n <DialogTitle>{`Assign ${String(lead?.['name'] || lead?.['email'] || 'lead')}`}</DialogTitle>\n <DialogContent>\n <Box sx={{ pt: 1 }}>\n <LeadOwnerSelect\n value={current}\n onChange={setUid}\n roster={roster}\n size=\"medium\"\n />\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={() => onAssign(current)}>\n {'Assign'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nAssignOwnerDialog.displayName = 'AssignOwnerDialog'\n\nexport default CrmLeadsSection\n"],"names":["Aglyn","mdiAccountArrowRight","mdiAccountCancelOutline","mdiAccountConvertOutline","mdiAccountTieOutline","CardDisplay","MdiIcon","ListPagination","ListTable","useOrgMemberOptions","useCrmOrgMount","useCrmSavedView","useCrmScope","useCrmCampaigns","scopeTokensForHost","useContactFieldDefinitions","customFieldColumns","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","useCrmFoldsScope","useCrmListQuery","crmViewQuerySort","useCrmColumnSort","CrmViewsControl","CrmListActions","CrmListToolbar","RowActionsMenu","ListQueryNotices","useSnackbar","useFirestore","Box","Button","Dialog","DialogActions","DialogContent","DialogTitle","Select","Stack","Typography","deleteField","doc","serverTimestamp","updateDoc","useRouter","useCallback","useEffect","useMemo","useState","leadPrimaryGroup","crmRoutes","LEAD_EMAIL_FILTER_OPTIONS","LEAD_FILTER_CODECS","LEAD_LIST_DECLARATION","LEAD_LIST_FILTER_FIELDS","LEAD_LIST_SORTS","LEAD_LIST_FILTER_HEADERS","LEAD_PICKLIST_FILTERS","LEAD_SOURCE_DIRECTION_FILTER_OPTIONS","LEAD_SOURCE_FILTER_NONE","leadClausesForGrid","leadStatusFilterOptions","LEAD_PREFIX_SEARCH","leadClauseImpliesScope","leadClausesToStore","leadQueryClause","leadStatusBase","crmAskClauses","crmQueryRefusals","listFilterGridColumns","useListGridFilter","ListFilterChips","useLeadSourcePicklist","useLeadStatusPicklist","useLeadPicklists","LeadConvertDialog","leadSourceLabel","leadSources","leadTimeLabel","CrmExportButton","CrmImportButton","CRM_LEADS_RESOURCE","NewLeadDrawer","useCrmApi","LeadOwnerSelect","CONVERT_PENDING_ERASURE_REASON","CrmEmailStateChip","LeadStatusChip","leadStatusChoices","leadStatusMenuItems","CrmShareChipView","crmShareChipFor","useCrmSharingFollowUp","LeadSurfacesNote","LeadUnqualifyDialog","LeadsBulkBar","OrgLeadSurfacesNote","LEAD_HIDDEN_COLUMNS","industry","rating","CrmLeadsSection","props","hostId","org","basePath","firestore","router","enqueueSnackbar","orgId","createHostId","consentGroup","viewingHostIds","hostIds","mount","roster","leadFields","leadSourceList","leadStatusList","leadPicklists","routes","scopeTokens","dataRoot","foldsScope","views","section","clauses","state","filters","setClauses","next","setFilters","gridFilter","onChange","selectFields","map","entry","column","codecs","campaigns","enabled","campaignName","id","options","find","option","value","label","filterOptions","stale","field","known","filter","clause","op","flatMap","split","trim","some","campaignOptions","sourceOptions","picklist","values","active","picklistOptions","Object","fromEntries","lists","picklistId","key","crmPicklistKey","status","emailState","ownerUid","uid","crmMemberPickerLabel","campaignIds","leadSource","leadSourceDirection","asked","searchKey","searchWords","join","statusBase","filterClauses","leadSort","sort","paged","scope","collection","visibleTo","declaration","search","base","impliesScope","prefixSearch","rows","row","leadId","$id","refused","plan","fields","headers","opening","length","selectedIds","setSelectedIds","page","exportFilter","served","searched","assigning","setAssigning","unqualifying","setUnqualifying","unqualifyAs","setUnqualifyAs","converting","setConverting","crmApi","createOpen","setCreateOpen","createBusy","setCreateBusy","createError","setCreateError","handleCreate","response","payload","email","name","company","jobTitle","phone","website","address","tags","notes","custom","standard","ok","String","variant","persist","error","console","followUpSharing","writeLead","lead","done","updatedAt","Error","message","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","spacing","sx","justifyContent","height","direction","alignItems","noWrap","chip","color","crmLeadStatus","InlineStatus","statuses","statusLabel","unqualifiedReason","readEmailState","EMAIL_STATE_LABELS","labelFor","held","Array","isArray","siteName","sortComparator","a","b","crmPicklistRank","localeCompare","header","sortable","readContainerIds","width","filterable","disableColumnMenu","converted","Boolean","convertedContactId","erasurePending","readErasureRequestedAtMs","convertRefusal","isCrmLeadOpen","onClick","event","stopPropagation","display","items","icon","path","size","href","disabled","disabledReason","undefined","filterColumns","grid","pageSorts","rank","set","CRM_LEAD_STATUSES","indexOf","columnSort","sorts","defaultSort","orderBy","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","mappingZone","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","slots","selectable","loading","onOpen","_id","push","columnVisibilityModel","onColumnVisibilityModelChange","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","noRowsDescription","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","open","onClose","busy","onSubmit","AssignOwnerDialog","onAssign","leadLabel","displayName","choices","disableUnderline","crmLeadStatusLabel","choice","target","renderValue","setUid","current","maxWidth","fullWidth","slotProps","transition","onExited","pt"],"mappings":"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"}
@@ -16,7 +16,7 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
- import { CRM_COLLECTIONS, crmPicklistShownLabel, crmTaskPicklistLabels, crmTaskStatusWrite, pluginDocsHelp } from "@aglyn/aglyn";
19
+ import { CRM_COLLECTIONS, crmPicklistRank, crmPicklistShownLabel, crmTaskPicklistLabels, crmTaskStatusWrite, pluginDocsHelp } from "@aglyn/aglyn";
20
20
  import { CardDisplay } from "@aglyn/shared-ui-jsx";
21
21
  import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
22
22
  import { useCrmSavedView } from "../hooks/use-crm-saved-view.js";
@@ -37,7 +37,8 @@ import { crmRoutes } from "../model/crm-routes.js";
37
37
  import { refreshCrmNextActivity } from "../model/next-activity-api.js";
38
38
  import { completeCrmTask } from "../model/task-api.js";
39
39
  import { crmTaskCallScope } from "../model/task-routes.js";
40
- import { CRM_TASK_KINDS, CRM_TASK_PRIORITIES, CRM_TASK_VIEWS, TASK_LIST_QUERY_FIELDS } from "../model/task-views.js";
40
+ import { CRM_TASK_KINDS, CRM_TASK_PRIORITIES, CRM_TASK_VIEWS, crmTaskViewPlan, crmTaskViewSort, TASK_LIST_QUERY_FIELDS, TASK_LIST_SORTS, taskRecordLink } from "../model/task-views.js";
41
+ import { crmViewQuerySort, useCrmColumnSort } from "../hooks/use-crm-column-sort.js";
41
42
  import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
42
43
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
43
44
  import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
@@ -83,6 +84,10 @@ const TASK_FILTER_HEADERS = {
83
84
  op: 'equals',
84
85
  value: 'mine'
85
86
  };
87
+ /** The row controls no header sorts (AGL-3680): the done box, an action like Complete. */ const TASK_ROW_CONTROLS = [
88
+ 'status'
89
+ ];
90
+ /** A label's place in its org list, for a page sort; `null` for none (sorted last). */ const picklistRank = (picklist, label)=>label ? crmPicklistRank(picklist, label) : null;
86
91
  const EMPTY_LABEL = {
87
92
  mine: 'Nothing is assigned to you',
88
93
  overdue: 'Nothing is overdue',
@@ -183,6 +188,19 @@ const EMPTY_LABEL = {
183
188
  const queryClauses = useMemo(()=>clauses.filter((clause)=>clause.field !== 'view'), [
184
189
  clauses
185
190
  ]);
191
+ /*
192
+ * The header order the view asks (AGL-3680), read before the query it
193
+ * orders; with none, the task view's own Due order.
194
+ */ const viewDirection = crmTaskViewPlan(view, {
195
+ nowMs,
196
+ uid: user == null ? void 0 : user.uid
197
+ }).direction;
198
+ const taskSort = useMemo(()=>crmViewQuerySort(TASK_LIST_SORTS, views.state.sort, crmTaskViewSort({
199
+ direction: viewDirection
200
+ })), [
201
+ views.state.sort,
202
+ viewDirection
203
+ ]);
186
204
  const list = useCrmTaskList({
187
205
  hostId,
188
206
  org: orgRecord,
@@ -190,7 +208,8 @@ const EMPTY_LABEL = {
190
208
  uid: user == null ? void 0 : user.uid,
191
209
  nowMs,
192
210
  clauses: queryClauses,
193
- search: gridFilter.searchWords
211
+ search: gridFilter.searchWords,
212
+ sort: taskSort.sort
194
213
  });
195
214
  const { tasks, status, fromCache, scope, orgId, readTokens } = list;
196
215
  const directory = useOrgMemberDirectory(orgId);
@@ -513,6 +532,37 @@ const EMPTY_LABEL = {
513
532
  filterOptions
514
533
  ]);
515
534
  const grid = useCrmViewGrid(views, filterColumns, TASK_HIDDEN_COLUMNS);
535
+ /*
536
+ * EVERY HEADER SORTS (AGL-3680): Due, Task and Priority on the query
537
+ * (`TASK_LIST_SORTS`), the rest over the page, saying so — Type and Status
538
+ * in the order the org keeps their values, For by the record's name. The
539
+ * done box is a row control, like an actions column, and sorts nothing.
540
+ */ const pageSorts = useMemo(()=>({
541
+ kind: (row)=>picklistRank(picklists.type, crmTaskPicklistLabels(row, picklists).type),
542
+ statusLabel: (row)=>picklistRank(picklists.status, crmTaskPicklistLabels(row, picklists).status),
543
+ record: (row)=>{
544
+ var _nameOf;
545
+ const link = taskRecordLink(row, routes);
546
+ return link ? (_nameOf = nameOf(link.kind, link.id)) != null ? _nameOf : null : null;
547
+ }
548
+ }), [
549
+ picklists,
550
+ routes,
551
+ nameOf
552
+ ]);
553
+ const columnSort = useCrmColumnSort({
554
+ views,
555
+ sorts: TASK_LIST_SORTS,
556
+ defaultSort: crmTaskViewSort({
557
+ direction: viewDirection
558
+ }),
559
+ asked: taskSort,
560
+ orderBy: list.plan.orderBy,
561
+ rows: tasks,
562
+ columns: grid.columns,
563
+ pageSorts,
564
+ unsortable: TASK_ROW_CONTROLS
565
+ });
516
566
  return /*#__PURE__*/ _jsxs(_Fragment, {
517
567
  children: [
518
568
  /*#__PURE__*/ _jsx(CardDisplay, {
@@ -592,7 +642,10 @@ const EMPTY_LABEL = {
592
642
  }),
593
643
  /*#__PURE__*/ _jsx(ListQueryNotices, {
594
644
  refused: refused,
595
- notices: list.plan.notices
645
+ notices: [
646
+ ...list.plan.notices,
647
+ ...columnSort.notices
648
+ ]
596
649
  }),
597
650
  status === 'error' ? /*#__PURE__*/ _jsx(Typography, {
598
651
  variant: "body2",
@@ -620,7 +673,8 @@ const EMPTY_LABEL = {
620
673
  /*#__PURE__*/ _jsx(CrmColumnOrderProvider, {
621
674
  value: grid.columnOrder,
622
675
  children: /*#__PURE__*/ _jsx(ListTable, {
623
- rows: tasks,
676
+ rows: columnSort.rows,
677
+ columnSort: columnSort,
624
678
  columns: grid.columns,
625
679
  slots: CRM_LIST_SLOTS,
626
680
  selectable: {
@@ -657,10 +711,8 @@ const EMPTY_LABEL = {
657
711
  onFilterModelChange: gridFilter.onFilterModelChange,
658
712
  quickFilter: true,
659
713
  // The view orders the query (due soonest, or Done most
660
- // recently due), so the grid sorts nothing itself: a
661
- // sort over one page would reorder ten rows.
662
- sortingMode: "server",
663
- disableColumnSorting: true,
714
+ // recently due) until a header asks otherwise: the headers
715
+ // sort through `columnSort` (AGL-3680).
664
716
  // Columns are the view's, controlled (AGL-2617).
665
717
  columnVisibilityModel: grid.columnVisibilityModel,
666
718
  onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,