@aglyn/plugins-crm 1.0.0-beta.183 → 1.0.0-beta.184

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 (28) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/companies-section.js +97 -54
  3. package/src/lib/components/companies-section.js.map +1 -1
  4. package/src/lib/components/contacts-section.js +51 -19
  5. package/src/lib/components/contacts-section.js.map +1 -1
  6. package/src/lib/components/crm-filter-bar.d.ts +34 -32
  7. package/src/lib/components/crm-filter-bar.js +34 -235
  8. package/src/lib/components/crm-filter-bar.js.map +1 -1
  9. package/src/lib/components/crm-next-activity-column.d.ts +13 -21
  10. package/src/lib/components/crm-next-activity-column.js +21 -32
  11. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  12. package/src/lib/components/deals-section.js +65 -54
  13. package/src/lib/components/deals-section.js.map +1 -1
  14. package/src/lib/components/leads-section.js +106 -286
  15. package/src/lib/components/leads-section.js.map +1 -1
  16. package/src/lib/components/tasks-section.js +139 -34
  17. package/src/lib/components/tasks-section.js.map +1 -1
  18. package/src/lib/hooks/use-crm-grid-filter.d.ts +77 -0
  19. package/src/lib/hooks/use-crm-grid-filter.js +143 -0
  20. package/src/lib/hooks/use-crm-grid-filter.js.map +1 -0
  21. package/src/lib/model/crm-grid-filter.d.ts +64 -0
  22. package/src/lib/model/crm-grid-filter.js +144 -0
  23. package/src/lib/model/crm-grid-filter.js.map +1 -0
  24. package/src/lib/model/lead-filters.d.ts +42 -0
  25. package/src/lib/model/lead-filters.js +182 -9
  26. package/src/lib/model/lead-filters.js.map +1 -1
  27. package/src/lib/server/email-send.js +4 -2
  28. package/src/lib/server/email-send.js.map +1 -1
@@ -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 CrmLeadStatus,\n} from '@aglyn/aglyn'\nimport {\n mdiAccountArrowRight,\n mdiAccountCancelOutline,\n mdiAccountConvertOutline,\n mdiAccountTieOutline,\n mdiMagnify,\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 { useOrgLeads } from '../hooks/use-org-leads'\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 { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControl,\n InputAdornment,\n InputLabel,\n MenuItem,\n Select,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n deleteField,\n doc,\n limit,\n orderBy,\n query,\n serverTimestamp,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useId, useMemo, useState } from 'react'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LEAD_FILTER_LABELS,\n LEAD_EMAIL_FILTER_LABELS,\n LEAD_EMAIL_FILTERS,\n LEAD_FILTERS,\n type LeadEmailFilter,\n type LeadFilter,\n leadMatchesCampaignFilter,\n leadMatchesEmailFilter,\n leadMatchesFilter,\n leadMatchesLeadSourceFilter,\n leadMatchesSearch,\n LEAD_SOURCE_FILTER_NONE,\n} from '../model/lead-filters'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { type LeadCsvOptions, leadsCsv } from '../model/leads-csv'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport {\n leadSourceLabel,\n leadSources,\n leadTimeLabel,\n} from './lead-history-card'\nimport { LeadImportButton } from './lead-import-drawer'\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 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 * How many leads the section reads: the newest by last seen, plus one probe\n * row so \"there are more\" is a fact rather than a guess at the boundary.\n *\n * A CEILING and a client-side filter rather than a paged status query, and\n * the reason is the lead documents themselves. Every lead the capture door\n * writes carries NO `status` — the field exists only once somebody in the\n * CRM has touched the lead — and Firestore cannot select documents by a\n * field's absence: `where('status','in',[…])`, `!=` and `not-in` all skip a\n * document without the field. A server-side \"open leads\" query would\n * therefore hide every lead nobody has worked yet, which is the entire\n * population the section exists to show on the day it ships. So the query\n * is the one order every lead can satisfy (`lastSeenAtMs`, stamped on every\n * capture), and the status filter narrows the loaded window — said out loud\n * beneath the table whenever the window is not the whole collection.\n *\n * The window is then PAGED in memory under the shared footer, the way the\n * workspace pickers page a slice of a window they cannot re-key: the rows\n * are already in the snapshot, so turning a page costs nothing, and the\n * footer's count is exact because it counts the filtered window rather than\n * a collection nobody has measured.\n */\nconst LEADS_WINDOW = 200\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/**\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.\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 } = useCrmScope({ hostId, org })\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 const routes = crmRoutes(basePath ?? '')\n\n /*\n * Under a site: the ORG collection, narrowed to what this site may see\n * (AGL-3275). It read `hosts/{hostId}/leads` until the silo moved, and\n * leaving it there would have shown a site its pre-migration rows and\n * nothing captured since — then nothing at all, once AGL-3276 emptied the\n * path it was reading.\n */\n const site = useFirestoreCollection<\n Record<string, unknown> & CrmLeadFields & { $id: string }\n >(\n () =>\n hostId && orgId\n ? query(\n collection(firestore, 'orgs', orgId, 'leads'),\n where('visibleTo', 'array-contains-any', scopeTokensForHost(hostId)),\n orderBy('lastSeenAtMs', 'desc'),\n limit(LEADS_WINDOW + 1),\n )\n : null,\n [firestore, hostId, orgId],\n { idField: '$id' },\n )\n // At the organization level: every site's window, merged.\n const orgHostIds = useMemo(\n () => (hostId ? [] : (mount?.hosts ?? []).map((host) => host.id)),\n [hostId, mount?.hosts],\n )\n /*\n * ONE LISTENER (AGL-3275), where this used to fan out across the org's\n * sites and merge. At the organization level an org-wide member reads with\n * no scope clause, which is what `visibleTo: null` asks for.\n */\n const orgLeads = useOrgLeads({\n orgId,\n visibleTo: null,\n windowSize: LEADS_WINDOW,\n })\n const leadDocs = useMemo<LeadRow[]>(\n () =>\n hostId\n ? site.data.map((row) => ({ ...row, leadId: row.$id }))\n : orgLeads.data,\n [hostId, site.data, orgLeads.data],\n )\n const status = hostId\n ? site.status\n : mount?.hostsReady && !orgHostIds.length\n ? 'success'\n : orgLeads.status\n const truncated = hostId ? leadDocs.length > LEADS_WINDOW : orgLeads.truncated\n const window = useMemo(() => leadDocs.slice(0, LEADS_WINDOW), [leadDocs])\n\n /*\n * The `Show` filter is the saved VIEW'S (AGL-2617): a saved view of leads\n * holds the status beside the columns and the sort, and the select below\n * writes into it. Unset reads as `open`, which is what the section opened\n * on before views existed and the one reading a query cannot express.\n */\n const views = useCrmSavedView({\n section: 'leads',\n hostId,\n org: props.org,\n basePath: basePath ?? '',\n })\n const filter: LeadFilter = useMemo(() => {\n const value = views.state.filters.find(\n (clause) => clause.field === 'status',\n )?.value\n return (LEAD_FILTERS as readonly string[]).includes(value ?? '')\n ? (value as LeadFilter)\n : 'open'\n }, [views.state.filters])\n /*\n * The `Email` filter (AGL-3245) is the view's too, as an `emailState`\n * clause beside the status one. Each setter keeps the other's clause:\n * narrowing to bounced leads does not reopen the unqualified ones.\n */\n const emailFilter: LeadEmailFilter = useMemo(() => {\n const value = views.state.filters.find(\n (clause) => clause.field === 'emailState',\n )?.value\n return (LEAD_EMAIL_FILTERS as readonly string[]).includes(value ?? '')\n ? (value as LeadEmailFilter)\n : 'any'\n }, [views.state.filters])\n const setFilter = useCallback(\n (next: LeadFilter) =>\n views.setFilters([\n ...views.state.filters.filter((clause) => clause.field !== 'status'),\n ...(next === 'open'\n ? []\n : [{ field: 'status', op: 'equals', value: next }]),\n ]),\n [views.setFilters, views.state.filters],\n )\n const setEmailFilter = useCallback(\n (next: LeadEmailFilter) =>\n views.setFilters([\n ...views.state.filters.filter(\n (clause) => clause.field !== 'emailState',\n ),\n ...(next === 'any'\n ? []\n : [{ field: 'emailState', op: 'equals', value: next }]),\n ]),\n [views.setFilters, views.state.filters],\n )\n /*\n * The `Campaign` filter (AGL-3254) is the view's too, as a `campaignIds`\n * clause: the id of one of the org's campaign containers, resolved to its\n * name from the containers themselves — ids only in storage, so a renamed\n * campaign keeps its leads. Under a site the choice is the campaigns\n * placed on it; at the organization level, every campaign in the org.\n */\n const campaignFilter = useMemo(\n () =>\n views.state.filters.find((clause) => clause.field === 'campaignIds')\n ?.value ?? '',\n [views.state.filters],\n )\n const setCampaignFilter = useCallback(\n (next: string) =>\n views.setFilters([\n ...views.state.filters.filter(\n (clause) => clause.field !== 'campaignIds',\n ),\n ...(next\n ? [{ field: 'campaignIds', op: 'contains', value: next }]\n : []),\n ]),\n [views.setFilters, views.state.filters],\n )\n /*\n * The `Lead source` filter (AGL-3298) is the view's too: an `equals`\n * clause naming the label records store, or an `isEmpty` one for the\n * leads that hold none.\n */\n const leadSourceFilter = useMemo(() => {\n const clause = views.state.filters.find((entry) => entry.field === 'leadSource')\n if (!clause) return ''\n return clause.op === 'isEmpty' ? LEAD_SOURCE_FILTER_NONE : String(clause.value ?? '')\n }, [views.state.filters])\n const setLeadSourceFilter = useCallback(\n (next: string) =>\n views.setFilters([\n ...views.state.filters.filter((clause) => clause.field !== 'leadSource'),\n ...(next === LEAD_SOURCE_FILTER_NONE\n ? [{ field: 'leadSource', op: 'isEmpty', value: '' }]\n : next\n ? [{ field: 'leadSource', op: 'equals', value: next }]\n : []),\n ]),\n [views.setFilters, views.state.filters],\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 // The label's id, so the filter's combobox is named \"Show\" rather than\n // after the option it shows — see `LeadOwnerSelect`.\n const filterLabelId = useId()\n const emailFilterLabelId = useId()\n const campaignFilterLabelId = useId()\n const leadSourceFilterLabelId = useId()\n /*\n * The search box is the SECTION'S, not the grid's (AGL-3246). The grid's\n * quick filter runs over the rows the grid holds, and the grid holds one\n * PAGE of the window — so a lead on page three answered \"no match\" while\n * the footer below went on counting the unfiltered window. The term\n * narrows the whole loaded window here, beside the status filter and\n * before the footer's count and the page slice, over the fields a person\n * types to find a lead: name, email, company, title and tags.\n */\n const [search, setSearch] = useState('')\n const rows = useMemo(\n () =>\n window.filter(\n (lead) =>\n leadMatchesFilter(lead, filter) &&\n leadMatchesEmailFilter(lead, emailFilter) &&\n leadMatchesCampaignFilter(lead, campaignFilter) &&\n leadMatchesLeadSourceFilter(lead, leadSourceFilter) &&\n leadMatchesSearch(lead, search),\n ),\n [window, filter, emailFilter, campaignFilter, leadSourceFilter, search],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n // A new filter or search term starts on page one: page three of the open\n // leads is not a page of the unqualified ones, and an out-of-range page\n // renders empty.\n useEffect(() => {\n setPage(0)\n }, [filter, emailFilter, campaignFilter, leadSourceFilter, search])\n const pageRows = useMemo(\n () => rows.slice(page * pageSize, (page + 1) * pageSize),\n [rows, page, pageSize],\n )\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 [filter, emailFilter, campaignFilter, leadSourceFilter, search],\n )\n // How the file names the owner and, at the org level, the site.\n const csvOptions: LeadCsvOptions = useMemo(\n () => ({\n ownerEmail: roster.emailFor,\n ...(hostId ? {} : { siteName: (id: string) => mount?.siteName(id) }),\n }),\n [roster.emailFor, hostId, mount],\n )\n // The listed window — every row the filter and the search admit, not just\n // the page.\n const handleExport = useCallback(() => {\n downloadTextFile('leads.csv', 'text/csv', leadsCsv(rows, csvOptions))\n }, [rows, csvOptions])\n\n const [assigning, setAssigning] = useState<LeadRow | null>(null)\n const [unqualifying, setUnqualifying] = useState<LeadRow | 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 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 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 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],\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 <Typography variant=\"body2\" noWrap>\n {String(row['name'] || row['email'] || row.$id)}\n </Typography>\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 valueGetter: (_value, row: LeadRow) => Aglyn.crmLeadStatus(row),\n renderCell: ({ row }: { row: LeadRow }) => (\n <InlineStatus\n lead={row}\n onChange={(next) => {\n if (next === 'unqualified') {\n setUnqualifying(row)\n return\n }\n void writeLead(\n row,\n {\n status: next,\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 // 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.readCampaignIds(row).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: () => setUnqualifying(row),\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 mount,\n campaignName,\n leadFields.active,\n leadSourceList.picklist,\n ],\n )\n /* The column and sort models are the view's (AGL-2617). */\n const grid = useCrmViewGrid(views, columns)\n\n return (\n <>\n <CardDisplay\n header={'Leads'}\n help={Aglyn.pluginDocsHelp('contacts', {\n anchor: '#whats-in-the-crm-area',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <LeadImportButton hostId={hostId} orgId={orgId} />\n <Button size=\"small\" onClick={handleExport} disabled={!rows.length}>\n {'Export CSV'}\n </Button>\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 {/* The filters sit above the grid they narrow, wrapping when narrow (AGL-3311). */}\n <CrmListToolbar label=\"Lead filters\">\n {/* The saved view this list is showing, beside the status it narrows to (AGL-2617). */}\n <CrmViewsControl controller={views} allLabel=\"All leads\" />\n <FormControl size=\"small\" sx={{ minWidth: 160 }}>\n <InputLabel id={filterLabelId}>{'Show'}</InputLabel>\n <Select\n labelId={filterLabelId}\n label=\"Show\"\n value={filter}\n onChange={(event) =>\n setFilter(event.target.value as LeadFilter)\n }\n >\n {LEAD_FILTERS.map((option) => (\n <MenuItem key={option} value={option}>\n {LEAD_FILTER_LABELS[option]}\n </MenuItem>\n ))}\n </Select>\n </FormControl>\n {/* The verdict on the address (AGL-3245): the bounced, the blocked, the ones who left. */}\n <FormControl size=\"small\" sx={{ minWidth: 160 }}>\n <InputLabel id={emailFilterLabelId}>{'Email'}</InputLabel>\n <Select\n labelId={emailFilterLabelId}\n label=\"Email\"\n value={emailFilter}\n onChange={(event) =>\n setEmailFilter(event.target.value as LeadEmailFilter)\n }\n >\n {LEAD_EMAIL_FILTERS.map((option) => (\n <MenuItem key={option} value={option}>\n {LEAD_EMAIL_FILTER_LABELS[option]}\n </MenuItem>\n ))}\n </Select>\n </FormControl>\n {/* The campaign the lead is filed under (AGL-3254), by name. */}\n <FormControl size=\"small\" sx={{ minWidth: 180 }}>\n <InputLabel id={campaignFilterLabelId} shrink>\n {'Campaign'}\n </InputLabel>\n <Select\n labelId={campaignFilterLabelId}\n label=\"Campaign\"\n notched\n value={campaignFilter}\n onChange={(event) =>\n setCampaignFilter(String(event.target.value))\n }\n displayEmpty\n >\n <MenuItem value=\"\">{'Any campaign'}</MenuItem>\n {campaigns.options.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n {/* A stored filter naming a campaign no longer listed stays selectable, by id, so it can be cleared. */}\n {campaignFilter &&\n !campaigns.options.some(\n (option) => option.value === campaignFilter,\n ) ? (\n <MenuItem value={campaignFilter}>{campaignFilter}</MenuItem>\n ) : null}\n </Select>\n </FormControl>\n {/* Salesforce's Lead Source (AGL-3298): every value the org keeps, inactive ones marked. */}\n <FormControl size=\"small\" sx={{ minWidth: 180 }}>\n <InputLabel id={leadSourceFilterLabelId} shrink>\n {'Lead source'}\n </InputLabel>\n <Select\n labelId={leadSourceFilterLabelId}\n label=\"Lead source\"\n notched\n value={leadSourceFilter}\n onChange={(event) => setLeadSourceFilter(String(event.target.value))}\n displayEmpty\n >\n <MenuItem value=\"\">{'Any lead source'}</MenuItem>\n {leadSourceList.picklist.values.map((value) => (\n <MenuItem key={value.id} value={value.label}>\n {value.active ? value.label : `${value.label} (inactive)`}\n </MenuItem>\n ))}\n <MenuItem value={LEAD_SOURCE_FILTER_NONE}>{'No lead source'}</MenuItem>\n {/* A stored filter naming a value no longer listed stays selectable, so it can be cleared. */}\n {leadSourceFilter &&\n leadSourceFilter !== LEAD_SOURCE_FILTER_NONE &&\n !Aglyn.crmPicklistValueByLabel(leadSourceList.picklist, leadSourceFilter) ? (\n <MenuItem value={leadSourceFilter}>{leadSourceFilter}</MenuItem>\n ) : null}\n </Select>\n </FormControl>\n <TextField\n size=\"small\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder=\"Search leads\"\n slotProps={{\n input: {\n startAdornment: (\n <InputAdornment position=\"start\">\n <MdiIcon path={mdiMagnify.path} size={0.8} />\n </InputAdornment>\n ),\n },\n htmlInput: { 'aria-label': 'Search leads', type: 'search' },\n }}\n sx={{ minWidth: 200 }}\n />\n </CrmListToolbar>\n {status === 'success' && window.length === 0 ? (\n <EmptyStateComponent\n label={'No leads yet'}\n description={\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 }\n />\n ) : status === 'success' && rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`No ${LEAD_FILTER_LABELS[filter].toLowerCase()} leads` +\n (search.trim() ? ` match “${search.trim()}”` : '') +\n ` among the ${window.length.toLocaleString()} most recently seen.`}\n </Typography>\n ) : (\n <>\n <LeadsBulkBar\n rows={rows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n roster={roster}\n csv={csvOptions}\n orgId={orgId}\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={pageRows}\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) =>\n router.push(\n routes.lead(row.leadId),\n )\n }\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={\n grid.onColumnVisibilityModelChange\n }\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n // The search is the section's, above: the grid's own box\n // would search this page alone.\n quickFilter={false}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={rows.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </>\n )}\n {truncated ? (\n <Alert severity=\"info\">\n {`Showing the ${LEADS_WINDOW.toLocaleString()} most recently seen ` +\n 'leads. The search box and the status filter narrow these ' +\n `${LEADS_WINDOW.toLocaleString()} only; older leads are still ` +\n 'listed in the Inbox and reached by campaign audiences.'}\n </Alert>\n ) : null}\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 />\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 (Aglyn.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 onChange: (next: CrmLeadStatus) => void\n}) {\n const { lead, onChange } = props\n if (lead.convertedContactId) {\n return (\n <Box sx={{ display: 'flex', alignItems: 'center', height: '100%' }}>\n <LeadStatusChip lead={lead} />\n </Box>\n )\n }\n const status = Aglyn.crmLeadStatus(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={status}\n onChange={(event) => onChange(event.target.value as CrmLeadStatus)}\n renderValue={() => <LeadStatusChip lead={lead} />}\n sx={{ width: '100%' }}\n >\n <MenuItem value=\"new\">{Aglyn.CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">\n {Aglyn.CRM_LEAD_STATUS_LABELS.working}\n </MenuItem>\n <MenuItem value=\"unqualified\">{`${Aglyn.CRM_LEAD_STATUS_LABELS.unqualified}…`}</MenuItem>\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","mdiMagnify","CardDisplay","MdiIcon","ListPagination","ListTable","useOrgMemberOptions","useCrmOrgMount","useCrmSavedView","useCrmScope","useCrmCampaigns","scopeTokensForHost","useContactFieldDefinitions","customFieldColumns","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","useOrgLeads","CrmViewsControl","CrmListActions","CrmListToolbar","RowActionsMenu","TABLE_PAGE_SIZE_DEFAULT","EmptyStateComponent","useSnackbar","useFirestore","useFirestoreCollection","Alert","Box","Button","Dialog","DialogActions","DialogContent","DialogTitle","FormControl","InputAdornment","InputLabel","MenuItem","Select","Stack","TextField","Typography","collection","deleteField","doc","limit","orderBy","query","serverTimestamp","updateDoc","where","useRouter","useCallback","useEffect","useId","useMemo","useState","downloadTextFile","crmRoutes","LEAD_FILTER_LABELS","LEAD_EMAIL_FILTER_LABELS","LEAD_EMAIL_FILTERS","LEAD_FILTERS","leadMatchesCampaignFilter","leadMatchesEmailFilter","leadMatchesFilter","leadMatchesLeadSourceFilter","leadMatchesSearch","LEAD_SOURCE_FILTER_NONE","useLeadSourcePicklist","leadsCsv","LeadConvertDialog","leadSourceLabel","leadSources","leadTimeLabel","LeadImportButton","NewLeadDrawer","useCrmApi","LeadOwnerSelect","CONVERT_PENDING_ERASURE_REASON","CrmEmailStateChip","LeadStatusChip","LeadSurfacesNote","LeadUnqualifyDialog","LeadsBulkBar","OrgLeadSurfacesNote","LEADS_WINDOW","CrmLeadsSection","props","hostId","org","basePath","firestore","router","enqueueSnackbar","orgId","createHostId","mount","roster","leadFields","leadSourceList","routes","site","idField","orgHostIds","hosts","map","host","id","orgLeads","visibleTo","windowSize","leadDocs","data","row","leadId","$id","status","hostsReady","length","truncated","window","slice","views","section","filter","value","state","filters","find","clause","field","includes","emailFilter","setFilter","next","setFilters","op","setEmailFilter","campaignFilter","setCampaignFilter","leadSourceFilter","entry","String","setLeadSourceFilter","campaigns","enabled","campaignName","options","option","label","filterLabelId","emailFilterLabelId","campaignFilterLabelId","leadSourceFilterLabelId","search","setSearch","rows","lead","page","setPage","pageSize","setPageSize","pageRows","selectedIds","setSelectedIds","csvOptions","ownerEmail","emailFor","siteName","handleExport","assigning","setAssigning","unqualifying","setUnqualifying","converting","setConverting","crmApi","createOpen","setCreateOpen","createBusy","setCreateBusy","createError","setCreateError","handleCreate","values","response","payload","email","name","company","jobTitle","phone","website","leadSource","address","tags","campaignIds","ownerUid","notes","custom","ok","variant","persist","error","console","writeLead","fields","done","updatedAt","Error","message","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","spacing","sx","justifyContent","height","noWrap","color","crmLeadStatus","InlineStatus","onChange","unqualifiedReason","readEmailState","EMAIL_STATE_LABELS","labelFor","held","Array","isArray","join","sortComparator","a","b","crmPicklistRank","picklist","localeCompare","readCampaignIds","active","width","sortable","filterable","disableColumnMenu","converted","Boolean","convertedContactId","erasurePending","readErasureRequestedAtMs","convertRefusal","isCrmLeadOpen","onClick","event","stopPropagation","display","alignItems","items","key","icon","path","size","href","disabled","disabledReason","undefined","grid","header","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","labelId","target","shrink","notched","displayEmpty","some","crmPicklistValueByLabel","placeholder","slotProps","input","startAdornment","position","htmlInput","type","description","toLowerCase","trim","toLocaleString","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","onOpen","_id","push","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","quickFilter","hideFooter","rowCount","count","onPageChange","onPageSizeChange","severity","open","onClose","busy","onSubmit","AssignOwnerDialog","onAssign","uid","leadLabel","leadPrimaryGroup","displayName","disableUnderline","renderValue","CRM_LEAD_STATUS_LABELS","new","working","unqualified","setUid","current","maxWidth","fullWidth","transition","onExited","pt"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,YAAYA,WAAW,eAAc;AAMrC,SACEC,oBAAoB,EACpBC,uBAAuB,EACvBC,wBAAwB,EACxBC,oBAAoB,EACpBC,UAAU,QACL,yBAAwB;AAC/B,SAASC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AAC3D,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,SAAS,QAAQ,uDAAsD;AAChF,SAEEC,mBAAmB,QACd,qCAAiC;AACxC,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,eAAe,QAAQ,iCAA6B;AAC7D,SAASC,WAAW,QAAQ,4BAAwB;AACpD,SAASC,eAAe,QAAQ,gCAA4B;AAC5D,SAASC,kBAAkB,QAAQ,sCAAqC;AACxE,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,SAASC,kBAAkB,QAAQ,8BAA0B;AAC7D,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,cAAc,EAAEC,sBAAsB,QAAQ,uBAAmB;AAC1E,SAASC,WAAW,QAAQ,4BAAwB;AACpD,OAAOC,qBAAqB,yBAAqB;AACjD,SAASC,cAAc,EAAEC,cAAc,QAAQ,wBAAoB;AACnE,OAAOC,oBAAoB,6DAA4D;AACvF,SAASC,uBAAuB,QAAQ,8CAA6C;AACrF,OAAOC,yBAAyB,wDAAuD;AACvF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,YAAY,EACZC,sBAAsB,QACjB,iCAAgC;AACvC,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,WAAW,EACXC,cAAc,EACdC,UAAU,EACVC,QAAQ,EACRC,MAAM,EACNC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AAEtB,SACEC,UAAU,EACVC,WAAW,EACXC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,eAAe,EACfC,SAAS,EACTC,KAAK,QACA,qBAAoB;AAC3B,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,WAAW,EAAEC,SAAS,EAAEC,KAAK,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACxE,SAASC,gBAAgB,QAAQ,2BAAuB;AACxD,SAASC,SAAS,QAAQ,yBAAqB;AAC/C,SACEC,kBAAkB,EAClBC,wBAAwB,EACxBC,kBAAkB,EAClBC,YAAY,EAGZC,yBAAyB,EACzBC,sBAAsB,EACtBC,iBAAiB,EACjBC,2BAA2B,EAC3BC,iBAAiB,EACjBC,uBAAuB,QAClB,2BAAuB;AAC9B,SAASC,qBAAqB,QAAQ,uCAAmC;AACzE,SAA8BC,QAAQ,QAAQ,wBAAoB;AAClE,SAASC,iBAAiB,QAAQ,2BAAuB;AACzD,SACEC,eAAe,EACfC,WAAW,EACXC,aAAa,QACR,yBAAqB;AAC5B,SAASC,gBAAgB,QAAQ,0BAAsB;AACvD,OAAOC,mBAA2C,uBAAmB;AACrE,SAASC,SAAS,QAAQ,mBAAe;AACzC,SAASC,eAAe,QAAQ,yBAAqB;AACrD,SAASC,8BAA8B,QAAQ,4BAAwB;AACvE,SAASC,iBAAiB,QAAQ,4BAAwB;AAC1D,SAASC,cAAc,QAAQ,wBAAoB;AACnD,OAAOC,sBAAsB,0BAAsB;AACnD,SAASC,mBAAmB,QAAQ,6BAAyB;AAC7D,OAAOC,kBAAkB,sBAAkB;AAC3C,OAAOC,yBAAyB,8BAA0B;AAE1D;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,MAAMC,eAAe;AAgBrB;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,gBAAgBC,KAA6B;;IAC3D,MAAM,EAAEC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAE,GAAGH;IAClC,MAAMI,YAAYnE;IAClB,MAAMoE,SAAS1C;IACf,MAAM,EAAE2C,eAAe,EAAE,GAAGtE;IAC5B,MAAM,EAAEuE,KAAK,EAAEC,YAAY,EAAE,GAAGvF,YAAY;QAAEgF;QAAQC;IAAI;IAC1D,MAAMO,QAAQ1F;IACd,MAAM2F,SAAS5F,oBAAoByF;IACnC,oEAAoE;IACpE,MAAMI,aAAavF,2BAA2BmF,OAAO;IACrD,qEAAqE;IACrE,uBAAuB;IACvB,MAAMK,iBAAiB/B,sBAAsB0B;IAC7C,MAAMM,SAAS3C,UAAUiC,mBAAAA,WAAY;IAErC;;;;;;GAMC,GACD,MAAMW,OAAO5E,uBAGX,IACE+D,UAAUM,QACNhD,MACEL,WAAWkD,WAAW,QAAQG,OAAO,UACrC7C,MAAM,aAAa,sBAAsBvC,mBAAmB8E,UAC5D3C,QAAQ,gBAAgB,SACxBD,MAAMyC,eAAe,MAEvB,MACN;QAACM;QAAWH;QAAQM;KAAM,EAC1B;QAAEQ,SAAS;IAAM;IAEnB,0DAA0D;IAC1D,MAAMC,aAAajD,QACjB;;eAAOkC,SAAS,EAAE,GAAG,SAACQ,yBAAAA,MAAOQ,KAAK,mBAAI,EAAE,EAAEC,GAAG,CAAC,CAACC,OAASA,KAAKC,EAAE;OAC/D;QAACnB;QAAQQ,yBAAAA,MAAOQ,KAAK;KAAC;IAExB;;;;GAIC,GACD,MAAMI,WAAW5F,YAAY;QAC3B8E;QACAe,WAAW;QACXC,YAAYzB;IACd;IACA,MAAM0B,WAAWzD,QACf,IACEkC,SACIa,KAAKW,IAAI,CAACP,GAAG,CAAC,CAACQ,MAAS,aAAKA;gBAAKC,QAAQD,IAAIE,GAAG;kBACjDP,SAASI,IAAI,EACnB;QAACxB;QAAQa,KAAKW,IAAI;QAAEJ,SAASI,IAAI;KAAC;IAEpC,MAAMI,SAAS5B,SACXa,KAAKe,MAAM,GACXpB,CAAAA,yBAAAA,MAAOqB,UAAU,KAAI,CAACd,WAAWe,MAAM,GACrC,YACAV,SAASQ,MAAM;IACrB,MAAMG,YAAY/B,SAASuB,SAASO,MAAM,GAAGjC,eAAeuB,SAASW,SAAS;IAC9E,MAAMC,SAASlE,QAAQ,IAAMyD,SAASU,KAAK,CAAC,GAAGpC,eAAe;QAAC0B;KAAS;IAExE;;;;;GAKC,GACD,MAAMW,QAAQnH,gBAAgB;QAC5BoH,SAAS;QACTnC;QACAC,KAAKF,MAAME,GAAG;QACdC,QAAQ,EAAEA,mBAAAA,WAAY;IACxB;IACA,MAAMkC,SAAqBtE,QAAQ;YACnBoE;QAAd,MAAMG,SAAQH,4BAAAA,MAAMI,KAAK,CAACC,OAAO,CAACC,IAAI,CACpC,CAACC,SAAWA,OAAOC,KAAK,KAAK,8BADjBR,0BAEXG,KAAK;QACR,OAAO,AAAChE,aAAmCsE,QAAQ,CAACN,gBAAAA,QAAS,MACxDA,QACD;IACN,GAAG;QAACH,MAAMI,KAAK,CAACC,OAAO;KAAC;IACxB;;;;GAIC,GACD,MAAMK,cAA+B9E,QAAQ;YAC7BoE;QAAd,MAAMG,SAAQH,4BAAAA,MAAMI,KAAK,CAACC,OAAO,CAACC,IAAI,CACpC,CAACC,SAAWA,OAAOC,KAAK,KAAK,kCADjBR,0BAEXG,KAAK;QACR,OAAO,AAACjE,mBAAyCuE,QAAQ,CAACN,gBAAAA,QAAS,MAC9DA,QACD;IACN,GAAG;QAACH,MAAMI,KAAK,CAACC,OAAO;KAAC;IACxB,MAAMM,YAAYlF,YAChB,CAACmF,OACCZ,MAAMa,UAAU,CAAC;eACZb,MAAMI,KAAK,CAACC,OAAO,CAACH,MAAM,CAAC,CAACK,SAAWA,OAAOC,KAAK,KAAK;eACvDI,SAAS,SACT,EAAE,GACF;gBAAC;oBAAEJ,OAAO;oBAAUM,IAAI;oBAAUX,OAAOS;gBAAK;aAAE;SACrD,GACH;QAACZ,MAAMa,UAAU;QAAEb,MAAMI,KAAK,CAACC,OAAO;KAAC;IAEzC,MAAMU,iBAAiBtF,YACrB,CAACmF,OACCZ,MAAMa,UAAU,CAAC;eACZb,MAAMI,KAAK,CAACC,OAAO,CAACH,MAAM,CAC3B,CAACK,SAAWA,OAAOC,KAAK,KAAK;eAE3BI,SAAS,QACT,EAAE,GACF;gBAAC;oBAAEJ,OAAO;oBAAcM,IAAI;oBAAUX,OAAOS;gBAAK;aAAE;SACzD,GACH;QAACZ,MAAMa,UAAU;QAAEb,MAAMI,KAAK,CAACC,OAAO;KAAC;IAEzC;;;;;;GAMC,GACD,MAAMW,iBAAiBpF,QACrB;;YACEoE;wBAAAA,4BAAAA,MAAMI,KAAK,CAACC,OAAO,CAACC,IAAI,CAAC,CAACC,SAAWA,OAAOC,KAAK,KAAK,mCAAtDR,0BACIG,KAAK,mBAAI;OACf;QAACH,MAAMI,KAAK,CAACC,OAAO;KAAC;IAEvB,MAAMY,oBAAoBxF,YACxB,CAACmF,OACCZ,MAAMa,UAAU,CAAC;eACZb,MAAMI,KAAK,CAACC,OAAO,CAACH,MAAM,CAC3B,CAACK,SAAWA,OAAOC,KAAK,KAAK;eAE3BI,OACA;gBAAC;oBAAEJ,OAAO;oBAAeM,IAAI;oBAAYX,OAAOS;gBAAK;aAAE,GACvD,EAAE;SACP,GACH;QAACZ,MAAMa,UAAU;QAAEb,MAAMI,KAAK,CAACC,OAAO;KAAC;IAEzC;;;;GAIC,GACD,MAAMa,mBAAmBtF,QAAQ;YAGmC2E;QAFlE,MAAMA,SAASP,MAAMI,KAAK,CAACC,OAAO,CAACC,IAAI,CAAC,CAACa,QAAUA,MAAMX,KAAK,KAAK;QACnE,IAAI,CAACD,QAAQ,OAAO;QACpB,OAAOA,OAAOO,EAAE,KAAK,YAAYrE,0BAA0B2E,QAAOb,gBAAAA,OAAOJ,KAAK,YAAZI,gBAAgB;IACpF,GAAG;QAACP,MAAMI,KAAK,CAACC,OAAO;KAAC;IACxB,MAAMgB,sBAAsB5F,YAC1B,CAACmF,OACCZ,MAAMa,UAAU,CAAC;eACZb,MAAMI,KAAK,CAACC,OAAO,CAACH,MAAM,CAAC,CAACK,SAAWA,OAAOC,KAAK,KAAK;eACvDI,SAASnE,0BACT;gBAAC;oBAAE+D,OAAO;oBAAcM,IAAI;oBAAWX,OAAO;gBAAG;aAAE,GACnDS,OACE;gBAAC;oBAAEJ,OAAO;oBAAcM,IAAI;oBAAUX,OAAOS;gBAAK;aAAE,GACpD,EAAE;SACT,GACH;QAACZ,MAAMa,UAAU;QAAEb,MAAMI,KAAK,CAACC,OAAO;KAAC;IAEzC,MAAMiB,YAAYvI,gBAAgB;QAAE+E;QAAQM;IAAM,GAAG;QAAEmD,SAAS;IAAK;IACrE,MAAMC,eAAe/F,YACnB,CAACwD;;YACCqC;wBAAAA,0BAAAA,UAAUG,OAAO,CAACnB,IAAI,CAAC,CAACoB,SAAWA,OAAOvB,KAAK,KAAKlB,wBAApDqC,wBAAyDK,KAAK,mBAAI1C;OACpE;QAACqC,UAAUG,OAAO;KAAC;IAErB,uEAAuE;IACvE,qDAAqD;IACrD,MAAMG,gBAAgBjG;IACtB,MAAMkG,qBAAqBlG;IAC3B,MAAMmG,wBAAwBnG;IAC9B,MAAMoG,0BAA0BpG;IAChC;;;;;;;;GAQC,GACD,MAAM,CAACqG,QAAQC,UAAU,GAAGpG,SAAS;IACrC,MAAMqG,OAAOtG,QACX,IACEkE,OAAOI,MAAM,CACX,CAACiC,OACC7F,kBAAkB6F,MAAMjC,WACxB7D,uBAAuB8F,MAAMzB,gBAC7BtE,0BAA0B+F,MAAMnB,mBAChCzE,4BAA4B4F,MAAMjB,qBAClC1E,kBAAkB2F,MAAMH,UAE9B;QAAClC;QAAQI;QAAQQ;QAAaM;QAAgBE;QAAkBc;KAAO;IAEzE,MAAM,CAACI,MAAMC,QAAQ,GAAGxG,SAAS;IACjC,MAAM,CAACyG,UAAUC,YAAY,GAAG1G,SAASlC;IACzC,yEAAyE;IACzE,wEAAwE;IACxE,iBAAiB;IACjB+B,UAAU;QACR2G,QAAQ;IACV,GAAG;QAACnC;QAAQQ;QAAaM;QAAgBE;QAAkBc;KAAO;IAClE,MAAMQ,WAAW5G,QACf,IAAMsG,KAAKnC,KAAK,CAACqC,OAAOE,UAAU,AAACF,CAAAA,OAAO,CAAA,IAAKE,WAC/C;QAACJ;QAAME;QAAME;KAAS;IAGxB;;;;;GAKC,GACD,MAAM,CAACG,aAAaC,eAAe,GAAG7G,SAAmB,EAAE;IAC3DH,UACE,IAAMgH,eAAe,EAAE,GACvB;QAACxC;QAAQQ;QAAaM;QAAgBE;QAAkBc;KAAO;IAEjE,gEAAgE;IAChE,MAAMW,aAA6B/G,QACjC,IAAO;YACLgH,YAAYrE,OAAOsE,QAAQ;WACvB/E,SAAS,CAAC,IAAI;YAAEgF,UAAU,CAAC7D,KAAeX,yBAAAA,MAAOwE,QAAQ,CAAC7D;QAAI,IAEpE;QAACV,OAAOsE,QAAQ;QAAE/E;QAAQQ;KAAM;IAElC,0EAA0E;IAC1E,YAAY;IACZ,MAAMyE,eAAetH,YAAY;QAC/BK,iBAAiB,aAAa,YAAYa,SAASuF,MAAMS;IAC3D,GAAG;QAACT;QAAMS;KAAW;IAErB,MAAM,CAACK,WAAWC,aAAa,GAAGpH,SAAyB;IAC3D,MAAM,CAACqH,cAAcC,gBAAgB,GAAGtH,SAAyB;IACjE,uEAAuE;IACvE,uEAAuE;IACvE,MAAM,CAACuH,YAAYC,cAAc,GAAGxH,SAAyB;IAE7D;;;;;;6CAM2C,GAC3C,MAAMyH,SAASpG,UAAUmB;IACzB,MAAM,CAACkF,YAAYC,cAAc,GAAG3H,SAAS;IAC7C,MAAM,CAAC4H,YAAYC,cAAc,GAAG7H,SAAS;IAC7C,MAAM,CAAC8H,aAAaC,eAAe,GAAG/H,SAAwB;IAC9D,MAAMgI,eAAepI,YACnB,OAAOqI;QACLJ,cAAc;QACdE,eAAe;QACf,IAAI;YACF,MAAM,EAAEG,QAAQ,EAAEC,OAAO,EAAE,GAAG,MAAMV,OAAO,gBAAgB;gBACzDW,OAAOH,OAAOG,KAAK;eACfH,OAAOI,IAAI,GAAG;gBAAEA,MAAMJ,OAAOI,IAAI;YAAC,IAAI,CAAC,GACvCJ,OAAOK,OAAO,GAAG;gBAAEA,SAASL,OAAOK,OAAO;YAAC,IAAI,CAAC,GAChDL,OAAOM,QAAQ,GAAG;gBAAEA,UAAUN,OAAOM,QAAQ;YAAC,IAAI,CAAC,GACnDN,OAAOO,KAAK,GAAG;gBAAEA,OAAOP,OAAOO,KAAK;YAAC,IAAI,CAAC,GAC1CP,OAAOQ,OAAO,GAAG;gBAAEA,SAASR,OAAOQ,OAAO;YAAC,IAAI,CAAC,GAChDR,OAAOS,UAAU,GAAG;gBAAEA,YAAYT,OAAOS,UAAU;YAAC,IAAI,CAAC,GACzDT,OAAOU,OAAO,GAAG;gBAAEA,SAASV,OAAOU,OAAO;YAAC,IAAI,CAAC,GAChDV,OAAOW,IAAI,CAAC7E,MAAM,GAAG;gBAAE6E,MAAMX,OAAOW,IAAI;YAAC,IAAI,CAAC,GAC9CX,OAAOY,WAAW,CAAC9E,MAAM,GACzB;gBAAE8E,aAAaZ,OAAOY,WAAW;YAAC,IAClC,CAAC,GACDZ,OAAOa,QAAQ,GAAG;gBAAEA,UAAUb,OAAOa,QAAQ;YAAC,IAAI,CAAC,GACnDb,OAAOc,KAAK,GAAG;gBAAEA,OAAOd,OAAOc,KAAK;YAAC,IAAI,CAAC,GAI1Cd,OAAOe,MAAM,GAAG;gBAAEA,QAAQf,OAAOe,MAAM;YAAC,IAAI,CAAC;gBACjDnF,QAAQoE,OAAOpE,MAAM;;YAEvB,IAAI,CAACqE,SAASe,EAAE,EAAE;oBAGPd;gBAFT,4DAA4D;gBAC5DJ,eACExC,QAAO4C,iBAAAA,OAAO,CAAC,QAAQ,YAAhBA,iBAAoB;gBAE7B;YACF;YACA,gEAAgE;YAChE,uBAAuB;YACvB7F,gBACE6F,OAAO,CAAC,UAAU,GACd,eACA,mEACJ;gBAAEe,SAAS;gBAAWC,SAAS;YAAM;YAEvCxB,cAAc;QAChB,EAAE,OAAOyB,OAAO;YACdC,QAAQD,KAAK,CAACA;YACdrB,eAAe;QACjB,SAAU;YACRF,cAAc;QAChB;IACF,GACA;QAACJ;QAAQnF;KAAgB;IAG3B,MAAMgH,YAAY1J,YAChB,OAAO0G,MAAeiD,QAAiCC;QACrD,IAAI,CAACjH,OAAO;YACVD,gBAAgB,yDAAyD;gBACvE4G,SAAS;gBACTC,SAAS;YACX;YACA;QACF;QACA,IAAI;YACF,MAAM1J,UACJL,IAAIgD,WAAW,QAAQG,OAAO,SAAS+D,KAAK3C,MAAM,GAClD,aACK4F;gBACHE,WAAWjK;;YAGf8C,gBAAgBkH,MAAM;gBAAEN,SAAS;gBAAWC,SAAS;YAAM;QAC7D,EAAE,OAAOC,OAAO;YACd9G,gBACE8G,iBAAiBM,QACbN,MAAMO,OAAO,GACb,kCACJ;gBAAET,SAAS;YAAQ;QAEvB;IACF,GACA;QAAC9G;QAAWE;KAAgB;IAG9B,MAAMsH,UAAU7J,QACd,IAAM;YACJ;gBACE4E,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG,MACpB6B,OAAO7B,GAAG,CAAC,OAAO,IAAIA,GAAG,CAAC,QAAQ,IAAI;gBACxCwG,YAAY,CAAC,EAAExG,GAAG,EAAoB;wBAUtBA;yCATd,MAAC3E;wBACCoL,SAAS;wBACTC,IAAI;4BAAEL,UAAU;4BAAGM,gBAAgB;4BAAUC,QAAQ;wBAAO;;0CAE5D,KAACrL;gCAAWiK,SAAQ;gCAAQqB,MAAM;0CAC/BhF,OAAO7B,GAAG,CAAC,OAAO,IAAIA,GAAG,CAAC,QAAQ,IAAIA,IAAIE,GAAG;;4BAE/CF,GAAG,CAAC,OAAO,iBACV,KAACzE;gCAAWiK,SAAQ;gCAAUsB,OAAM;gCAAiBD,MAAM;0CACxDhF,QAAO7B,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgB;iCAExB;;;;YAGV;YACA,mEAAmE;YACnE,iCAAiC;YACjC;gBACEiB,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG;wBAAwBA;2BAAP6B,QAAO7B,eAAAA,IAAI4E,OAAO,YAAX5E,eAAe;;YAC/D;YACA;gBACEiB,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG;wBAAwBA;2BAAP6B,QAAO7B,gBAAAA,IAAI6E,QAAQ,YAAZ7E,gBAAgB;;YAChE;YACA;gBACEiB,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG,MAAiBtH,MAAMqO,aAAa,CAAC/G;gBAC3DwG,YAAY,CAAC,EAAExG,GAAG,EAAoB,iBACpC,KAACgH;wBACCpE,MAAM5C;wBACNiH,UAAU,CAAC5F;4BACT,IAAIA,SAAS,eAAe;gCAC1BuC,gBAAgB5D;gCAChB;4BACF;4BACA,KAAK4F,UACH5F,KACA;gCACEG,QAAQkB;+BACJ3I,MAAMqO,aAAa,CAAC/G,SAAS,gBAC7B;gCAAEkH,mBAAmBzL;4BAAc,IACnC,CAAC,IAEP;wBAEJ;;YAGN;YACA;gBACE;;;;SAIC,GACDwF,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG;oBACpB,MAAMa,QAAQnI,MAAMyO,cAAc,CAACnH;oBACnC,OAAOa,QAAQnI,MAAM0O,kBAAkB,CAACvG,MAAMV,MAAM,CAAC,GAAG;gBAC1D;gBACAqG,YAAY,CAAC,EAAExG,GAAG,EAAoB;oBACpC,MAAMa,QAAQnI,MAAMyO,cAAc,CAACnH;oBACnC,OAAOa,sBACL,KAAC/C;wBAAkB+C,OAAOA;uCAE1B,KAACtF;wBAAWiK,SAAQ;wBAAUsB,OAAM;kCACjC;;gBAGP;YACF;YACA;gBACE7F,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG,MACpBA,IAAIoF,QAAQ,GAAGpG,OAAOqI,QAAQ,CAACrH,IAAIoF,QAAQ,IAAI;YACnD;YACA,iEAAiE;eAC7D7G,SACA,EAAE,GACF;gBACE;oBACE0C,OAAO;oBACPkF,YAAY;oBACZC,MAAM;oBACNC,UAAU;oBACV;;;;;eAKC,GACDC,aAAa,CAACC,QAAiBvG;wBAC7B,MAAMsH,OAAOC,MAAMC,OAAO,CAACxH,GAAG,CAAC,oBAAoB,IAC9CA,GAAG,CAAC,oBAAoB,GACzB,EAAE;wBACN,OAAOsH,KAAK9H,GAAG,CAAC,CAACE;;2CAAOX,yBAAAA,MAAOwE,QAAQ,CAAC7D,sBAAOA;2BAAI+H,IAAI,CAAC;oBAC1D;gBACF;aACD;YACL;gBACExG,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG,MACpBzC,YAAYyC,KAAKR,GAAG,CAAClC,iBAAiBmK,IAAI,CAAC;YAC/C;YACA;;;;;OAKC,GACD;gBACExG,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG;wBAAwBA;2BAAP6B,QAAO7B,kBAAAA,IAAIgF,UAAU,YAAdhF,kBAAkB;;gBAChE0H,gBAAgB,CAACC,GAAYC,IAC3BlP,MAAMmP,eAAe,CAAC3I,eAAe4I,QAAQ,EAAEH,KAC7CjP,MAAMmP,eAAe,CAAC3I,eAAe4I,QAAQ,EAAEF,MACjD/F,OAAO8F,YAAAA,IAAK,IAAII,aAAa,CAAClG,OAAO+F,YAAAA,IAAK;YAC9C;YACA;gBACE3G,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG;wBAAkBA;2BAAD,EAACA,YAAAA,IAAIkF,IAAI,YAARlF,YAAY,EAAE,EAAEyH,IAAI,CAAC;;YAC/D;YACA,kEAAkE;YAClE,uBAAuB;YACvB;gBACExG,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAiBvG,MAC7BtH,MAAMsP,eAAe,CAAChI,KAAKR,GAAG,CAACyC,cAAcwF,IAAI,CAAC;YACtD;YACA;gBACExG,OAAO;gBACPkF,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQvG;wBACNA;2BAAdxC,eAAcwC,oBAAAA,GAAG,CAAC,eAAe,YAAnBA,oBAAuBA,GAAG,CAAC,YAAY;;YACzD;YACA,qEAAqE;YACrE,kEAAkE;YAClE,iEAAiE;YACjE,2CAA2C;eACxCrG,mBAAmBsF,WAAWgJ,MAAM;YACvC;gBACEhH,OAAO;gBACPkF,YAAY;gBACZ+B,OAAO;gBACPC,UAAU;gBACVC,YAAY;gBACZC,mBAAmB;gBACnB7B,YAAY,CAAC,EAAExG,GAAG,EAAoB;wBAsBhBA;oBArBpB;;;;;WAKC,GACD,MAAMsI,YAAYC,QAAQvI,IAAIwI,kBAAkB;oBAChD,MAAMC,iBAAiB/P,MAAMgQ,wBAAwB,CAAC1I,SAAS;oBAC/D,MAAM2I,iBAAiBL,YACnB,4BACA,CAAC5P,MAAMkQ,aAAa,CAAC5I,OACnB,8BACAyI,iBACE5K,iCACA;oBACR,qBACE,KAACnD;wBACCmO,SAAS,CAACC,QAAUA,MAAMC,eAAe;wBACzCrC,IAAI;4BAAEsC,SAAS;4BAAQC,YAAY;4BAAUrC,QAAQ;wBAAO;kCAE5D,cAAA,KAACzM;4BACCiI,OAAOP,QAAO7B,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgBA,IAAIE,GAAG;4BACrCgJ,OAAO;gCACL;oCACEC,KAAK;oCACL/G,OAAO;oCACPgH,oBACE,KAACnQ;wCAAQoQ,MAAM1Q,qBAAqB0Q,IAAI;wCAAEC,MAAM;;oCAElDC,MAAMpK,OAAOyD,IAAI,CAAC5C,IAAIC,MAAM;gCAC9B;gCACA;oCACEkJ,KAAK;oCACL/G,OAAO;oCACPgH,oBACE,KAACnQ;wCACCoQ,MAAMxQ,yBAAyBwQ,IAAI;wCACnCC,MAAM;;oCAGVT,SAAS,IAAM/E,cAAc9D;oCAC7BwJ,UAAUb,mBAAmB;oCAC7Bc,cAAc,EAAEd,yBAAAA,iBAAkBe;gCACpC;gCACA;oCACEP,KAAK;oCACL/G,OAAO;oCACPgH,oBACE,KAACnQ;wCAAQoQ,MAAMvQ,qBAAqBuQ,IAAI;wCAAEC,MAAM;;oCAElDT,SAAS,IAAMnF,aAAa1D;gCAC9B;gCACA;oCACEmJ,KAAK;oCACL/G,OAAO;oCACPgH,oBACE,KAACnQ;wCAAQoQ,MAAMzQ,wBAAwByQ,IAAI;wCAAEC,MAAM;;oCAErDT,SAAS,IAAMjF,gBAAgB5D;oCAC/BwJ,UACE,CAAC9Q,MAAMkQ,aAAa,CAAC5I,QACrBuI,QAAQvI,IAAIwI,kBAAkB;oCAChCiB,gBAAgBzJ,IAAIwI,kBAAkB,GAClC,4BACA;gCACN;6BACD;;;gBAIT;YACF;SACD,EACD;QACExJ;QACAG;QACAyG;QACArH;QACAQ;QACAkD;QACAhD,WAAWgJ,MAAM;QACjB/I,eAAe4I,QAAQ;KACxB;IAEH,yDAAyD,GACzD,MAAM6B,OAAO/P,eAAe6G,OAAOyF;IAEnC,qBACE;;0BACE,KAAClN;gBACC4Q,QAAQ;gBACRC,MAAMnR,MAAMoR,cAAc,CAAC,YAAY;oBACrCC,QAAQ;gBACV;gBACAC,cAAc;gBACdC,cAAc;gBACdC,aAAa;oBACX,8DAA8D;oBAC9DC,sBACE,MAAClQ;;0CACC,KAACwD;gCAAiBc,QAAQA;gCAAQM,OAAOA;;0CACzC,KAAClE;gCAAO2O,MAAK;gCAAQT,SAASrF;gCAAcgG,UAAU,CAAC7G,KAAKtC,MAAM;0CAC/D;;0CAEH,KAAC1F;gCACC2O,MAAK;gCACL9D,SAAQ;gCACRqD,SAAS,IAAM5E,cAAc;0CAE5B;;;;gBAIT;0BAEA,cAAA,MAAC5I;oBAAMoL,SAAS;;wBAEblI,uBACC,KAACP;4BAAiBO,QAAQA;2CAE1B,KAACJ;sCAGH,MAACjE;4BAAekI,OAAM;;8CAEpB,KAACpI;oCAAgBoQ,YAAY3J;oCAAO4J,UAAS;;8CAC7C,MAACrP;oCAAYsO,MAAK;oCAAQ5C,IAAI;wCAAEL,UAAU;oCAAI;;sDAC5C,KAACnL;4CAAWwE,IAAI2C;sDAAgB;;sDAChC,KAACjH;4CACCkP,SAASjI;4CACTD,OAAM;4CACNxB,OAAOD;4CACPsG,UAAU,CAAC6B,QACT1H,UAAU0H,MAAMyB,MAAM,CAAC3J,KAAK;sDAG7BhE,aAAa4C,GAAG,CAAC,CAAC2C,uBACjB,KAAChH;oDAAsByF,OAAOuB;8DAC3B1F,kBAAkB,CAAC0F,OAAO;mDADdA;;;;8CAOrB,MAACnH;oCAAYsO,MAAK;oCAAQ5C,IAAI;wCAAEL,UAAU;oCAAI;;sDAC5C,KAACnL;4CAAWwE,IAAI4C;sDAAqB;;sDACrC,KAAClH;4CACCkP,SAAShI;4CACTF,OAAM;4CACNxB,OAAOO;4CACP8F,UAAU,CAAC6B,QACTtH,eAAesH,MAAMyB,MAAM,CAAC3J,KAAK;sDAGlCjE,mBAAmB6C,GAAG,CAAC,CAAC2C,uBACvB,KAAChH;oDAAsByF,OAAOuB;8DAC3BzF,wBAAwB,CAACyF,OAAO;mDADpBA;;;;8CAOrB,MAACnH;oCAAYsO,MAAK;oCAAQ5C,IAAI;wCAAEL,UAAU;oCAAI;;sDAC5C,KAACnL;4CAAWwE,IAAI6C;4CAAuBiI,MAAM;sDAC1C;;sDAEH,MAACpP;4CACCkP,SAAS/H;4CACTH,OAAM;4CACNqI,OAAO;4CACP7J,OAAOa;4CACPwF,UAAU,CAAC6B,QACTpH,kBAAkBG,OAAOiH,MAAMyB,MAAM,CAAC3J,KAAK;4CAE7C8J,YAAY;;8DAEZ,KAACvP;oDAASyF,OAAM;8DAAI;;gDACnBmB,UAAUG,OAAO,CAAC1C,GAAG,CAAC,CAAC2C,uBACtB,KAAChH;wDAA4ByF,OAAOuB,OAAOvB,KAAK;kEAC7CuB,OAAOC,KAAK;uDADAD,OAAOvB,KAAK;gDAK5Ba,kBACD,CAACM,UAAUG,OAAO,CAACyI,IAAI,CACrB,CAACxI,SAAWA,OAAOvB,KAAK,KAAKa,gCAE7B,KAACtG;oDAASyF,OAAOa;8DAAiBA;qDAChC;;;;;8CAIR,MAACzG;oCAAYsO,MAAK;oCAAQ5C,IAAI;wCAAEL,UAAU;oCAAI;;sDAC5C,KAACnL;4CAAWwE,IAAI8C;4CAAyBgI,MAAM;sDAC5C;;sDAEH,MAACpP;4CACCkP,SAAS9H;4CACTJ,OAAM;4CACNqI,OAAO;4CACP7J,OAAOe;4CACPsF,UAAU,CAAC6B,QAAUhH,oBAAoBD,OAAOiH,MAAMyB,MAAM,CAAC3J,KAAK;4CAClE8J,YAAY;;8DAEZ,KAACvP;oDAASyF,OAAM;8DAAI;;gDACnB1B,eAAe4I,QAAQ,CAACvD,MAAM,CAAC/E,GAAG,CAAC,CAACoB,sBACnC,KAACzF;wDAAwByF,OAAOA,MAAMwB,KAAK;kEACxCxB,MAAMqH,MAAM,GAAGrH,MAAMwB,KAAK,GAAG,GAAGxB,MAAMwB,KAAK,CAAC,WAAW,CAAC;uDAD5CxB,MAAMlB,EAAE;8DAIzB,KAACvE;oDAASyF,OAAO1D;8DAA0B;;gDAE1CyE,oBACDA,qBAAqBzE,2BACrB,CAACxE,MAAMkS,uBAAuB,CAAC1L,eAAe4I,QAAQ,EAAEnG,kCACtD,KAACxG;oDAASyF,OAAOe;8DAAmBA;qDAClC;;;;;8CAGR,KAACrG;oCACCgO,MAAK;oCACL1I,OAAO6B;oCACPwE,UAAU,CAAC6B,QAAUpG,UAAUoG,MAAMyB,MAAM,CAAC3J,KAAK;oCACjDiK,aAAY;oCACZC,WAAW;wCACTC,OAAO;4CACLC,8BACE,KAAC/P;gDAAegQ,UAAS;0DACvB,cAAA,KAAChS;oDAAQoQ,MAAMtQ,WAAWsQ,IAAI;oDAAEC,MAAM;;;wCAG5C;wCACA4B,WAAW;4CAAE,cAAc;4CAAgBC,MAAM;wCAAS;oCAC5D;oCACAzE,IAAI;wCAAEL,UAAU;oCAAI;;;;wBAGvBlG,WAAW,aAAaI,OAAOF,MAAM,KAAK,kBACzC,KAAChG;4BACC+H,OAAO;4BACPgJ,aACE;6BAGFjL,WAAW,aAAawC,KAAKtC,MAAM,KAAK,kBAC1C,KAAC9E;4BAAWiK,SAAQ;4BAAQsB,OAAM;sCAC/B,CAAC,GAAG,EAAErK,kBAAkB,CAACkE,OAAO,CAAC0K,WAAW,GAAG,MAAM,CAAC,GACpD5I,CAAAA,OAAO6I,IAAI,KAAK,CAAC,QAAQ,EAAE7I,OAAO6I,IAAI,GAAG,CAAC,CAAC,GAAG,EAAC,IAChD,CAAC,WAAW,EAAE/K,OAAOF,MAAM,CAACkL,cAAc,GAAG,oBAAoB,CAAC;2CAGtE;;8CACE,KAACrN;oCACCyE,MAAMA;oCACN6I,UAAUtI;oCACVuI,kBAAkBtI;oCAClBnE,QAAQA;oCACR0M,KAAKtI;oCACLvE,OAAOA;oCACPN,QAAQA;oCACRC,KAAKA;;8CAEP,KAAC1E;oCAAuB8G,OAAO+I,KAAKgC,WAAW;8CAC7C,cAAA,KAACxS;wCACCwJ,MAAMM;wCACNiD,SAASyD,KAAKzD,OAAO;wCACrB0F,OAAO/R;wCACPgS,YAAY;4CACVL,UAAUtI;4CACV+D,UAAU9D;wCACZ;wCACA2I,SAAS3L,WAAW;wCACpB4L,QAAQ,CAACC,KAAKhM,MACZrB,OAAOsN,IAAI,CACT9M,OAAOyD,IAAI,CAAC5C,IAAIC,MAAM;wCAG1B,0DAA0D;wCAC1DiM,uBAAuBvC,KAAKuC,qBAAqB;wCACjDC,+BACExC,KAAKwC,6BAA6B;wCAEpCC,WAAWzC,KAAKyC,SAAS;wCACzBC,mBAAmB1C,KAAK0C,iBAAiB;wCACzC,yDAAyD;wCACzD,gCAAgC;wCAChCC,aAAa;wCACb,8DAA8D;wCAC9DC,UAAU;;;8CAGd,KAACrT;oCACC2J,MAAMA;oCACNE,UAAUA;oCACVyJ,UAAUvJ,SAAS5C,MAAM;oCACzBoM,OAAO9J,KAAKtC,MAAM;oCAClBqM,cAAc5J;oCACd6J,kBAAkB3J;;;;wBAIvB1C,0BACC,KAAC7F;4BAAMmS,UAAS;sCACb,CAAC,YAAY,EAAExO,aAAamN,cAAc,GAAG,oBAAoB,CAAC,GACjE,8DACA,GAAGnN,aAAamN,cAAc,GAAG,6BAA6B,CAAC,GAC/D;6BAEF;;;;0BAGR,KAAC7N;gBACCmP,MAAM7I;gBACN8I,SAAS,IAAM7I,cAAc;gBAC7B1F,MAAM,EAAEA,iBAAAA,SAAU;gBAClBC,KAAKA;gBACLuO,MAAM7I;gBACNwB,OAAOtB;gBACPpF,QAAQA;gBACRH,OAAOA;gBACPmO,UAAU,CAACzI,SAAW,KAAKD,aAAaC;;0BAE1C,KAAC0I;gBACCrK,MAAMa;gBACNzE,QAAQA;gBACR8N,SAAS,IAAMpJ,aAAa;gBAC5BwJ,UAAU,CAACC;oBACT,IAAI,CAAC1J,WAAW;oBAChB,KAAKmC,UACHnC,WACA;wBAAE2B,UAAU+H,OAAO1R;oBAAc,GACjC0R,MAAM,mBAAmB;oBAE3BzJ,aAAa;gBACf;;0BAEF,KAACzF;gBACC4O,MAAMtE,QAAQ5E;gBACdmJ,SAAS,IAAMlJ,gBAAgB;gBAC/BrF,MAAM,EAAEA,iBAAAA,SAAU;gBAClB0B,MAAM,UAAE0D,gCAAAA,aAAc1D,MAAM,mBAAI;gBAChCmN,WAAWvL,OACT8B,CAAAA,gCAAAA,YAAc,CAAC,OAAO,MAAIA,gCAAAA,YAAc,CAAC,QAAQ,KAAI;;0BAQzD,KAACtG;gBACCwP,MAAMtE,QAAQ1E;gBACdiJ,SAAS,IAAMhJ,cAAc;gBAC7BvF,MAAM,EACJA,iBAAAA,SACC7F,MAAM2U,gBAAgB,CAACxJ,YAAYrF,KAAgCD,MAAM,IAAI;gBAEhFM,OAAOA;gBACPL,KAAKA;gBACLyB,MAAM,WAAE4D,8BAAAA,WAAY5D,MAAM,oBAAI;gBAC9B2C,IAAI,EAAEiB,qBAAAA,aAAc,CAAC;gBACrBpF,QAAQ,EAAEA,mBAAAA,WAAY;gBACtBO,QAAQA;;;;AAIhB;AACAX,gBAAgBiP,WAAW,GAAG;AAE9B;;;;;;CAMC,GACD,SAAStG,aAAa1I,KAGrB;IACC,MAAM,EAAEsE,IAAI,EAAEqE,QAAQ,EAAE,GAAG3I;IAC3B,IAAIsE,KAAK4F,kBAAkB,EAAE;QAC3B,qBACE,KAAC9N;YAAIgM,IAAI;gBAAEsC,SAAS;gBAAQC,YAAY;gBAAUrC,QAAQ;YAAO;sBAC/D,cAAA,KAAC7I;gBAAe6E,MAAMA;;;IAG5B;IACA,MAAMzC,SAASzH,MAAMqO,aAAa,CAACnE;IACnC,qBACE,KAAClI;QACCmO,SAAS,CAACC,QAAUA,MAAMC,eAAe;QACzCrC,IAAI;YACFsC,SAAS;YACTC,YAAY;YACZrC,QAAQ;YACRsB,OAAO;QACT;kBAEA,cAAA,MAAC9M;YACCkO,MAAK;YACL9D,SAAQ;YACR+H,gBAAgB;YAChB3M,OAAOT;YACP8G,UAAU,CAAC6B,QAAU7B,SAAS6B,MAAMyB,MAAM,CAAC3J,KAAK;YAChD4M,aAAa,kBAAM,KAACzP;oBAAe6E,MAAMA;;YACzC8D,IAAI;gBAAEwB,OAAO;YAAO;;8BAEpB,KAAC/M;oBAASyF,OAAM;8BAAOlI,MAAM+U,sBAAsB,CAACC,GAAG;;8BACvD,KAACvS;oBAASyF,OAAM;8BACblI,MAAM+U,sBAAsB,CAACE,OAAO;;8BAEvC,KAACxS;oBAASyF,OAAM;8BAAe,GAAGlI,MAAM+U,sBAAsB,CAACG,WAAW,CAAC,CAAC,CAAC;;;;;AAIrF;AACA5G,aAAasG,WAAW,GAAG;AAE3B,kCAAkC,GAClC,SAASL,kBAAkB3O,KAK1B;;IACC,MAAM,EAAEsE,IAAI,EAAE5D,MAAM,EAAE8N,OAAO,EAAEI,QAAQ,EAAE,GAAG5O;IAC5C,MAAM,CAAC6O,KAAKU,OAAO,GAAGvR,SAAwB;IAC9C,MAAMwR,UAAUX,cAAAA,MAAOtL,eAAOe,wBAAAA,KAAMwC,QAAQ,mBAAI;IAChD,qBACE,MAACxK;QACCiS,MAAMtE,QAAQ3F;QACdkK,SAASA;QACTiB,UAAS;QACTC,SAAS;QACTlD,WAAW;YAAEmD,YAAY;gBAAEC,UAAU,IAAML,OAAO;YAAM;QAAE;;0BAE1D,KAAC9S;0BAAa,CAAC,OAAO,EAAE8G,OAAOe,CAAAA,wBAAAA,IAAM,CAAC,OAAO,MAAIA,wBAAAA,IAAM,CAAC,QAAQ,KAAI,SAAS;;0BAC7E,KAAC9H;0BACC,cAAA,KAACJ;oBAAIgM,IAAI;wBAAEyH,IAAI;oBAAE;8BACf,cAAA,KAACvQ;wBACCgD,OAAOkN;wBACP7G,UAAU4G;wBACV7O,QAAQA;wBACRsK,MAAK;;;;0BAIX,MAACzO;;kCACC,KAACF;wBAAOkO,SAASiE;kCAAU;;kCAC3B,KAACnS;wBAAO6K,SAAQ;wBAAYqD,SAAS,IAAMqE,SAASY;kCACjD;;;;;;AAKX;AACAb,kBAAkBK,WAAW,GAAG;AAEhC,eAAejP,gBAAe"}
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 CrmLeadStatus,\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 { useOrgLeads } from '../hooks/use-org-leads'\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 { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Select,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n deleteField,\n doc,\n limit,\n orderBy,\n query,\n serverTimestamp,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LEAD_EMAIL_FILTER_OPTIONS,\n LEAD_FILTER_CODECS,\n LEAD_LIST_FILTER_FIELDS,\n LEAD_LIST_FILTER_HEADERS,\n LEAD_SOURCE_FILTER_NONE,\n LEAD_STATUS_FILTER_OPTIONS,\n leadClausesForGrid,\n leadClausesToStore,\n leadMatchesClauses,\n leadMatchesSearch,\n} from '../model/lead-filters'\nimport {\n type CrmFilterOption,\n crmFilterColumns,\n} from '../model/crm-grid-filter'\nimport { useCrmGridFilter } from '../hooks/use-crm-grid-filter'\nimport CrmFilterBar from './crm-filter-bar'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { type LeadCsvOptions, leadsCsv } from '../model/leads-csv'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport {\n leadSourceLabel,\n leadSources,\n leadTimeLabel,\n} from './lead-history-card'\nimport { LeadImportButton } from './lead-import-drawer'\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 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 * How many leads the section reads: the newest by last seen, plus one probe\n * row so \"there are more\" is a fact rather than a guess at the boundary.\n *\n * A CEILING and a client-side filter rather than a paged status query, and\n * the reason is the lead documents themselves. Every lead the capture door\n * writes carries NO `status` — the field exists only once somebody in the\n * CRM has touched the lead — and Firestore cannot select documents by a\n * field's absence: `where('status','in',[…])`, `!=` and `not-in` all skip a\n * document without the field. A server-side \"open leads\" query would\n * therefore hide every lead nobody has worked yet, which is the entire\n * population the section exists to show on the day it ships. So the query\n * is the one order every lead can satisfy (`lastSeenAtMs`, stamped on every\n * capture), and the status filter narrows the loaded window — said out loud\n * beneath the table whenever the window is not the whole collection.\n *\n * The window is then PAGED in memory under the shared footer, the way the\n * workspace pickers page a slice of a window they cannot re-key: the rows\n * are already in the snapshot, so turning a page costs nothing, and the\n * footer's count is exact because it counts the filtered window rather than\n * a collection nobody has measured.\n */\nconst LEADS_WINDOW = 200\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/**\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.\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 } = useCrmScope({ hostId, org })\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 const routes = crmRoutes(basePath ?? '')\n\n /*\n * Under a site: the ORG collection, narrowed to what this site may see\n * (AGL-3275). It read `hosts/{hostId}/leads` until the silo moved, and\n * leaving it there would have shown a site its pre-migration rows and\n * nothing captured since — then nothing at all, once AGL-3276 emptied the\n * path it was reading.\n */\n const site = useFirestoreCollection<\n Record<string, unknown> & CrmLeadFields & { $id: string }\n >(\n () =>\n hostId && orgId\n ? query(\n collection(firestore, 'orgs', orgId, 'leads'),\n where('visibleTo', 'array-contains-any', scopeTokensForHost(hostId)),\n orderBy('lastSeenAtMs', 'desc'),\n limit(LEADS_WINDOW + 1),\n )\n : null,\n [firestore, hostId, orgId],\n { idField: '$id' },\n )\n // At the organization level: every site's window, merged.\n const orgHostIds = useMemo(\n () => (hostId ? [] : (mount?.hosts ?? []).map((host) => host.id)),\n [hostId, mount?.hosts],\n )\n /*\n * ONE LISTENER (AGL-3275), where this used to fan out across the org's\n * sites and merge. At the organization level an org-wide member reads with\n * no scope clause, which is what `visibleTo: null` asks for.\n */\n const orgLeads = useOrgLeads({\n orgId,\n visibleTo: null,\n windowSize: LEADS_WINDOW,\n })\n const leadDocs = useMemo<LeadRow[]>(\n () =>\n hostId\n ? site.data.map((row) => ({ ...row, leadId: row.$id }))\n : orgLeads.data,\n [hostId, site.data, orgLeads.data],\n )\n const status = hostId\n ? site.status\n : mount?.hostsReady && !orgHostIds.length\n ? 'success'\n : orgLeads.status\n const truncated = hostId ? leadDocs.length > LEADS_WINDOW : orgLeads.truncated\n const window = useMemo(() => leadDocs.slice(0, LEADS_WINDOW), [leadDocs])\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 = useCrmGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: ['status', 'emailState', 'ownerUid'],\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, CrmFilterOption[]>>(() => {\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 return {\n status: LEAD_STATUS_FILTER_OPTIONS,\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 }\n }, [clauses, campaigns.options, leadSourceList.picklist, roster.options])\n /*\n * Every clause and the search narrow the whole loaded WINDOW here, before\n * the footer's count and the page slice (AGL-3246). The grid holds one\n * page of it, so a filter or a quick search the grid ran itself would\n * answer \"no match\" for a lead on page three.\n */\n const searchKey = gridFilter.searchWords.join(' ')\n const rows = useMemo(\n () =>\n window.filter(\n (lead) =>\n leadMatchesClauses(lead, clauses) &&\n leadMatchesSearch(lead, searchKey),\n ),\n [window, clauses, searchKey],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n // A new filter or search term starts on page one: page three of the open\n // leads is not a page of the unqualified ones, and an out-of-range page\n // renders empty.\n useEffect(() => {\n setPage(0)\n }, [views.state.filters, searchKey])\n const pageRows = useMemo(\n () => rows.slice(page * pageSize, (page + 1) * pageSize),\n [rows, page, pageSize],\n )\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],\n )\n // How the file names the owner and, at the org level, the site.\n const csvOptions: LeadCsvOptions = useMemo(\n () => ({\n ownerEmail: roster.emailFor,\n ...(hostId ? {} : { siteName: (id: string) => mount?.siteName(id) }),\n }),\n [roster.emailFor, hostId, mount],\n )\n // The listed window — every row the filter and the search admit, not just\n // the page.\n const handleExport = useCallback(() => {\n downloadTextFile('leads.csv', 'text/csv', leadsCsv(rows, csvOptions))\n }, [rows, csvOptions])\n\n const [assigning, setAssigning] = useState<LeadRow | null>(null)\n const [unqualifying, setUnqualifying] = useState<LeadRow | 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 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 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 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],\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 <Typography variant=\"body2\" noWrap>\n {String(row['name'] || row['email'] || row.$id)}\n </Typography>\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 valueGetter: (_value, row: LeadRow) => Aglyn.crmLeadStatus(row),\n renderCell: ({ row }: { row: LeadRow }) => (\n <InlineStatus\n lead={row}\n onChange={(next) => {\n if (next === 'unqualified') {\n setUnqualifying(row)\n return\n }\n void writeLead(\n row,\n {\n status: next,\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 // 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.readCampaignIds(row).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: () => setUnqualifying(row),\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 mount,\n campaignName,\n leadFields.active,\n leadSourceList.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 crmFilterColumns(columns, LEAD_LIST_FILTER_FIELDS, filterOptions, LEAD_LIST_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns)\n\n return (\n <>\n <CardDisplay\n header={'Leads'}\n help={Aglyn.pluginDocsHelp('contacts', {\n anchor: '#whats-in-the-crm-area',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <LeadImportButton hostId={hostId} orgId={orgId} />\n <Button size=\"small\" onClick={handleExport} disabled={!rows.length}>\n {'Export CSV'}\n </Button>\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 <CrmFilterBar\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 {status === 'success' && window.length === 0 ? (\n <EmptyStateComponent\n label={'No leads yet'}\n description={\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 }\n />\n ) : (\n <>\n <LeadsBulkBar\n rows={rows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n roster={roster}\n csv={csvOptions}\n orgId={orgId}\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={pageRows}\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) =>\n router.push(\n routes.lead(row.leadId),\n )\n }\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={\n grid.onColumnVisibilityModelChange\n }\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n // The panel and the search are the grid's; the section\n // answers both over the whole window (AGL-3313).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel={`No leads match among the ${window.length.toLocaleString()} most recently seen`}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={rows.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </>\n )}\n {truncated ? (\n <Alert severity=\"info\">\n {`Showing the ${LEADS_WINDOW.toLocaleString()} most recently seen ` +\n 'leads. The filters and the search narrow these ' +\n `${LEADS_WINDOW.toLocaleString()} only; older leads are still ` +\n 'listed in the Inbox and reached by campaign audiences.'}\n </Alert>\n ) : null}\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 />\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 (Aglyn.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 onChange: (next: CrmLeadStatus) => void\n}) {\n const { lead, onChange } = props\n if (lead.convertedContactId) {\n return (\n <Box sx={{ display: 'flex', alignItems: 'center', height: '100%' }}>\n <LeadStatusChip lead={lead} />\n </Box>\n )\n }\n const status = Aglyn.crmLeadStatus(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={status}\n onChange={(event) => onChange(event.target.value as CrmLeadStatus)}\n renderValue={() => <LeadStatusChip lead={lead} />}\n sx={{ width: '100%' }}\n >\n <MenuItem value=\"new\">{Aglyn.CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">\n {Aglyn.CRM_LEAD_STATUS_LABELS.working}\n </MenuItem>\n <MenuItem value=\"unqualified\">{`${Aglyn.CRM_LEAD_STATUS_LABELS.unqualified}…`}</MenuItem>\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","useOrgLeads","CrmViewsControl","CrmListActions","CrmListToolbar","RowActionsMenu","TABLE_PAGE_SIZE_DEFAULT","EmptyStateComponent","useSnackbar","useFirestore","useFirestoreCollection","Alert","Box","Button","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Select","Stack","Typography","collection","deleteField","doc","limit","orderBy","query","serverTimestamp","updateDoc","where","useRouter","useCallback","useEffect","useMemo","useState","downloadTextFile","crmRoutes","LEAD_EMAIL_FILTER_OPTIONS","LEAD_FILTER_CODECS","LEAD_LIST_FILTER_FIELDS","LEAD_LIST_FILTER_HEADERS","LEAD_SOURCE_FILTER_NONE","LEAD_STATUS_FILTER_OPTIONS","leadClausesForGrid","leadClausesToStore","leadMatchesClauses","leadMatchesSearch","crmFilterColumns","useCrmGridFilter","CrmFilterBar","useLeadSourcePicklist","leadsCsv","LeadConvertDialog","leadSourceLabel","leadSources","leadTimeLabel","LeadImportButton","NewLeadDrawer","useCrmApi","LeadOwnerSelect","CONVERT_PENDING_ERASURE_REASON","CrmEmailStateChip","LeadStatusChip","LeadSurfacesNote","LeadUnqualifyDialog","LeadsBulkBar","OrgLeadSurfacesNote","LEADS_WINDOW","CrmLeadsSection","props","hostId","org","basePath","firestore","router","enqueueSnackbar","orgId","createHostId","mount","roster","leadFields","leadSourceList","routes","site","idField","orgHostIds","hosts","map","host","id","orgLeads","visibleTo","windowSize","leadDocs","data","row","leadId","$id","status","hostsReady","length","truncated","window","slice","views","section","clauses","state","filters","setClauses","next","setFilters","gridFilter","onChange","selectFields","codecs","campaigns","enabled","campaignName","options","find","option","value","label","filterOptions","stale","field","known","filter","clause","op","flatMap","split","trim","some","campaignOptions","sourceOptions","picklist","values","active","emailState","ownerUid","uid","crmMemberPickerLabel","campaignIds","leadSource","searchKey","searchWords","join","rows","lead","page","setPage","pageSize","setPageSize","pageRows","selectedIds","setSelectedIds","csvOptions","ownerEmail","emailFor","siteName","handleExport","assigning","setAssigning","unqualifying","setUnqualifying","converting","setConverting","crmApi","createOpen","setCreateOpen","createBusy","setCreateBusy","createError","setCreateError","handleCreate","response","payload","email","name","company","jobTitle","phone","website","address","tags","notes","custom","ok","String","variant","persist","error","console","writeLead","fields","done","updatedAt","Error","message","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","spacing","sx","justifyContent","height","noWrap","color","crmLeadStatus","InlineStatus","unqualifiedReason","readEmailState","EMAIL_STATE_LABELS","labelFor","held","Array","isArray","sortComparator","a","b","crmPicklistRank","localeCompare","readCampaignIds","width","sortable","filterable","disableColumnMenu","converted","Boolean","convertedContactId","erasurePending","readErasureRequestedAtMs","convertRefusal","isCrmLeadOpen","onClick","event","stopPropagation","display","alignItems","items","key","icon","path","size","href","disabled","disabledReason","undefined","filterColumns","grid","header","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","headers","marksServed","description","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","onOpen","_id","push","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","toLocaleString","hideFooter","rowCount","count","onPageChange","onPageSizeChange","severity","open","onClose","busy","onSubmit","AssignOwnerDialog","onAssign","leadLabel","leadPrimaryGroup","displayName","disableUnderline","target","renderValue","CRM_LEAD_STATUS_LABELS","new","working","unqualified","setUid","current","maxWidth","fullWidth","slotProps","transition","onExited","pt"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,YAAYA,WAAW,eAAc;AAOrC,SACEC,oBAAoB,EACpBC,uBAAuB,EACvBC,wBAAwB,EACxBC,oBAAoB,QACf,yBAAwB;AAC/B,SAASC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AAC3D,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,SAAS,QAAQ,uDAAsD;AAChF,SAEEC,mBAAmB,QACd,qCAAiC;AACxC,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,eAAe,QAAQ,iCAA6B;AAC7D,SAASC,WAAW,QAAQ,4BAAwB;AACpD,SAASC,eAAe,QAAQ,gCAA4B;AAC5D,SAASC,kBAAkB,QAAQ,sCAAqC;AACxE,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,SAASC,kBAAkB,QAAQ,8BAA0B;AAC7D,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,cAAc,EAAEC,sBAAsB,QAAQ,uBAAmB;AAC1E,SAASC,WAAW,QAAQ,4BAAwB;AACpD,OAAOC,qBAAqB,yBAAqB;AACjD,SAASC,cAAc,EAAEC,cAAc,QAAQ,wBAAoB;AACnE,OAAOC,oBAAoB,6DAA4D;AACvF,SAASC,uBAAuB,QAAQ,8CAA6C;AACrF,OAAOC,yBAAyB,wDAAuD;AACvF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,YAAY,EACZC,sBAAsB,QACjB,iCAAgC;AACvC,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,QAAQ,EACRC,MAAM,EACNC,KAAK,EACLC,UAAU,QACL,gBAAe;AAEtB,SACEC,UAAU,EACVC,WAAW,EACXC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,eAAe,EACfC,SAAS,EACTC,KAAK,QACA,qBAAoB;AAC3B,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,SAASC,gBAAgB,QAAQ,2BAAuB;AACxD,SAASC,SAAS,QAAQ,yBAAqB;AAC/C,SACEC,yBAAyB,EACzBC,kBAAkB,EAClBC,uBAAuB,EACvBC,wBAAwB,EACxBC,uBAAuB,EACvBC,0BAA0B,EAC1BC,kBAAkB,EAClBC,kBAAkB,EAClBC,kBAAkB,EAClBC,iBAAiB,QACZ,2BAAuB;AAC9B,SAEEC,gBAAgB,QACX,8BAA0B;AACjC,SAASC,gBAAgB,QAAQ,kCAA8B;AAC/D,OAAOC,kBAAkB,sBAAkB;AAC3C,SAASC,qBAAqB,QAAQ,uCAAmC;AACzE,SAA8BC,QAAQ,QAAQ,wBAAoB;AAClE,SAASC,iBAAiB,QAAQ,2BAAuB;AACzD,SACEC,eAAe,EACfC,WAAW,EACXC,aAAa,QACR,yBAAqB;AAC5B,SAASC,gBAAgB,QAAQ,0BAAsB;AACvD,OAAOC,mBAA2C,uBAAmB;AACrE,SAASC,SAAS,QAAQ,mBAAe;AACzC,SAASC,eAAe,QAAQ,yBAAqB;AACrD,SAASC,8BAA8B,QAAQ,4BAAwB;AACvE,SAASC,iBAAiB,QAAQ,4BAAwB;AAC1D,SAASC,cAAc,QAAQ,wBAAoB;AACnD,OAAOC,sBAAsB,0BAAsB;AACnD,SAASC,mBAAmB,QAAQ,6BAAyB;AAC7D,OAAOC,kBAAkB,sBAAkB;AAC3C,OAAOC,yBAAyB,8BAA0B;AAE1D;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,MAAMC,eAAe;AAgBrB;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,gBAAgBC,KAA6B;;IAC3D,MAAM,EAAEC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAE,GAAGH;IAClC,MAAMI,YAAYjE;IAClB,MAAMkE,SAAS5C;IACf,MAAM,EAAE6C,eAAe,EAAE,GAAGpE;IAC5B,MAAM,EAAEqE,KAAK,EAAEC,YAAY,EAAE,GAAGrF,YAAY;QAAE8E;QAAQC;IAAI;IAC1D,MAAMO,QAAQxF;IACd,MAAMyF,SAAS1F,oBAAoBuF;IACnC,oEAAoE;IACpE,MAAMI,aAAarF,2BAA2BiF,OAAO;IACrD,qEAAqE;IACrE,uBAAuB;IACvB,MAAMK,iBAAiB/B,sBAAsB0B;IAC7C,MAAMM,SAAS9C,UAAUoC,mBAAAA,WAAY;IAErC;;;;;;GAMC,GACD,MAAMW,OAAO1E,uBAGX,IACE6D,UAAUM,QACNlD,MACEL,WAAWoD,WAAW,QAAQG,OAAO,UACrC/C,MAAM,aAAa,sBAAsBnC,mBAAmB4E,UAC5D7C,QAAQ,gBAAgB,SACxBD,MAAM2C,eAAe,MAEvB,MACN;QAACM;QAAWH;QAAQM;KAAM,EAC1B;QAAEQ,SAAS;IAAM;IAEnB,0DAA0D;IAC1D,MAAMC,aAAapD,QACjB;;eAAOqC,SAAS,EAAE,GAAG,SAACQ,yBAAAA,MAAOQ,KAAK,mBAAI,EAAE,EAAEC,GAAG,CAAC,CAACC,OAASA,KAAKC,EAAE;OAC/D;QAACnB;QAAQQ,yBAAAA,MAAOQ,KAAK;KAAC;IAExB;;;;GAIC,GACD,MAAMI,WAAW1F,YAAY;QAC3B4E;QACAe,WAAW;QACXC,YAAYzB;IACd;IACA,MAAM0B,WAAW5D,QACf,IACEqC,SACIa,KAAKW,IAAI,CAACP,GAAG,CAAC,CAACQ,MAAS,aAAKA;gBAAKC,QAAQD,IAAIE,GAAG;kBACjDP,SAASI,IAAI,EACnB;QAACxB;QAAQa,KAAKW,IAAI;QAAEJ,SAASI,IAAI;KAAC;IAEpC,MAAMI,SAAS5B,SACXa,KAAKe,MAAM,GACXpB,CAAAA,yBAAAA,MAAOqB,UAAU,KAAI,CAACd,WAAWe,MAAM,GACrC,YACAV,SAASQ,MAAM;IACrB,MAAMG,YAAY/B,SAASuB,SAASO,MAAM,GAAGjC,eAAeuB,SAASW,SAAS;IAC9E,MAAMC,SAASrE,QAAQ,IAAM4D,SAASU,KAAK,CAAC,GAAGpC,eAAe;QAAC0B;KAAS;IAExE;;;;;;GAMC,GACD,MAAMW,QAAQjH,gBAAgB;QAC5BkH,SAAS;QACTnC;QACAC,KAAKF,MAAME,GAAG;QACdC,QAAQ,EAAEA,mBAAAA,WAAY;IACxB;IACA,MAAMkC,UAAUzE,QACd,IAAMU,mBAAmB6D,MAAMG,KAAK,CAACC,OAAO,GAC5C;QAACJ,MAAMG,KAAK,CAACC,OAAO;KAAC;IAEvB,MAAMC,aAAa9E,YACjB,CAAC+E,OAAgCN,MAAMO,UAAU,CAACnE,mBAAmBkE,QACrE;QAACN,MAAMO,UAAU;KAAC;IAEpB,MAAMC,aAAahE,iBAAiB;QAClC0D;QACAO,UAAUJ;QACVK,cAAc;YAAC;YAAU;YAAc;SAAW;QAClDC,QAAQ7E;IACV;IACA,MAAM8E,YAAY3H,gBAAgB;QAAE6E;QAAQM;IAAM,GAAG;QAAEyC,SAAS;IAAK;IACrE,MAAMC,eAAevF,YACnB,CAAC0D;;YACC2B;wBAAAA,0BAAAA,UAAUG,OAAO,CAACC,IAAI,CAAC,CAACC,SAAWA,OAAOC,KAAK,KAAKjC,wBAApD2B,wBAAyDO,KAAK,mBAAIlC;OACpE;QAAC2B,UAAUG,OAAO;KAAC;IAErB;;;;GAIC,GACD,MAAMK,gBAAgB3F,QAA2C;QAC/D,MAAM4F,QAAQ,CAACC,OAAeC,QAC5BrB,QACGsB,MAAM,CAAC,CAACC,SAAWA,OAAOH,KAAK,KAAKA,SAASG,OAAOC,EAAE,KAAK,WAC3DC,OAAO,CAAC,CAACF,SAAWA,OAAOP,KAAK,CAACU,KAAK,CAAC,MACvC7C,GAAG,CAAC,CAACmC,QAAUA,MAAMW,IAAI,IACzBL,MAAM,CAAC,CAACN,QAAUA,SAAS,CAACK,MAAMO,IAAI,CAAC,CAACb,SAAWA,OAAOC,KAAK,KAAKA,QACpEnC,GAAG,CAAC,CAACmC,QAAW,CAAA;oBAAEA;oBAAOC,OAAOD;gBAAM,CAAA;QAC3C,MAAMa,kBAAkBnB,UAAUG,OAAO,CAAChC,GAAG,CAAC,CAACkC,SAAY,CAAA;gBACzDC,OAAOD,OAAOC,KAAK;gBACnBC,OAAOF,OAAOE,KAAK;YACrB,CAAA;QACA,wFAAwF;QACxF,MAAMa,gBAAgB;eACjBvD,eAAewD,QAAQ,CAACC,MAAM,CAACnD,GAAG,CAAC,CAACmC,QAAW,CAAA;oBAChDA,OAAOA,MAAMC,KAAK;oBAClBA,OAAOD,MAAMiB,MAAM,GAAGjB,MAAMC,KAAK,GAAG,GAAGD,MAAMC,KAAK,CAAC,WAAW,CAAC;gBACjE,CAAA;YACA;gBAAED,OAAOjF;gBAAyBkF,OAAO;YAAiB;SAC3D;QACD,OAAO;YACLzB,QAAQxD;YACRkG,YAAYvG;YACZwG,UAAU9D,OAAOwC,OAAO,CAAChC,GAAG,CAAC,CAACkC,SAAY,CAAA;oBACxCC,OAAOD,OAAOqB,GAAG;oBACjBnB,OAAO/I,MAAMmK,oBAAoB,CAACtB;gBACpC,CAAA;YACAuB,aAAa;mBAAIT;mBAAoBV,MAAM,eAAeU;aAAiB;YAC3EU,YAAY;mBAAIT;mBAAkBX,MAAM,cAAcW;aAAe;QACvE;IACF,GAAG;QAAC9B;QAASU,UAAUG,OAAO;QAAEtC,eAAewD,QAAQ;QAAE1D,OAAOwC,OAAO;KAAC;IACxE;;;;;GAKC,GACD,MAAM2B,YAAYlC,WAAWmC,WAAW,CAACC,IAAI,CAAC;IAC9C,MAAMC,OAAOpH,QACX,IACEqE,OAAO0B,MAAM,CACX,CAACsB,OACCzG,mBAAmByG,MAAM5C,YACzB5D,kBAAkBwG,MAAMJ,aAE9B;QAAC5C;QAAQI;QAASwC;KAAU;IAE9B,MAAM,CAACK,MAAMC,QAAQ,GAAGtH,SAAS;IACjC,MAAM,CAACuH,UAAUC,YAAY,GAAGxH,SAAS7B;IACzC,yEAAyE;IACzE,wEAAwE;IACxE,iBAAiB;IACjB2B,UAAU;QACRwH,QAAQ;IACV,GAAG;QAAChD,MAAMG,KAAK,CAACC,OAAO;QAAEsC;KAAU;IACnC,MAAMS,WAAW1H,QACf,IAAMoH,KAAK9C,KAAK,CAACgD,OAAOE,UAAU,AAACF,CAAAA,OAAO,CAAA,IAAKE,WAC/C;QAACJ;QAAME;QAAME;KAAS;IAGxB;;;;;GAKC,GACD,MAAM,CAACG,aAAaC,eAAe,GAAG3H,SAAmB,EAAE;IAC3DF,UACE,IAAM6H,eAAe,EAAE,GACvB;QAACrD,MAAMG,KAAK,CAACC,OAAO;QAAEsC;KAAU;IAElC,gEAAgE;IAChE,MAAMY,aAA6B7H,QACjC,IAAO;YACL8H,YAAYhF,OAAOiF,QAAQ;WACvB1F,SAAS,CAAC,IAAI;YAAE2F,UAAU,CAACxE,KAAeX,yBAAAA,MAAOmF,QAAQ,CAACxE;QAAI,IAEpE;QAACV,OAAOiF,QAAQ;QAAE1F;QAAQQ;KAAM;IAElC,0EAA0E;IAC1E,YAAY;IACZ,MAAMoF,eAAenI,YAAY;QAC/BI,iBAAiB,aAAa,YAAYgB,SAASkG,MAAMS;IAC3D,GAAG;QAACT;QAAMS;KAAW;IAErB,MAAM,CAACK,WAAWC,aAAa,GAAGlI,SAAyB;IAC3D,MAAM,CAACmI,cAAcC,gBAAgB,GAAGpI,SAAyB;IACjE,uEAAuE;IACvE,uEAAuE;IACvE,MAAM,CAACqI,YAAYC,cAAc,GAAGtI,SAAyB;IAE7D;;;;;;6CAM2C,GAC3C,MAAMuI,SAAS/G,UAAUmB;IACzB,MAAM,CAAC6F,YAAYC,cAAc,GAAGzI,SAAS;IAC7C,MAAM,CAAC0I,YAAYC,cAAc,GAAG3I,SAAS;IAC7C,MAAM,CAAC4I,aAAaC,eAAe,GAAG7I,SAAwB;IAC9D,MAAM8I,eAAejJ,YACnB,OAAO2G;QACLmC,cAAc;QACdE,eAAe;QACf,IAAI;YACF,MAAM,EAAEE,QAAQ,EAAEC,OAAO,EAAE,GAAG,MAAMT,OAAO,gBAAgB;gBACzDU,OAAOzC,OAAOyC,KAAK;eACfzC,OAAO0C,IAAI,GAAG;gBAAEA,MAAM1C,OAAO0C,IAAI;YAAC,IAAI,CAAC,GACvC1C,OAAO2C,OAAO,GAAG;gBAAEA,SAAS3C,OAAO2C,OAAO;YAAC,IAAI,CAAC,GAChD3C,OAAO4C,QAAQ,GAAG;gBAAEA,UAAU5C,OAAO4C,QAAQ;YAAC,IAAI,CAAC,GACnD5C,OAAO6C,KAAK,GAAG;gBAAEA,OAAO7C,OAAO6C,KAAK;YAAC,IAAI,CAAC,GAC1C7C,OAAO8C,OAAO,GAAG;gBAAEA,SAAS9C,OAAO8C,OAAO;YAAC,IAAI,CAAC,GAChD9C,OAAOO,UAAU,GAAG;gBAAEA,YAAYP,OAAOO,UAAU;YAAC,IAAI,CAAC,GACzDP,OAAO+C,OAAO,GAAG;gBAAEA,SAAS/C,OAAO+C,OAAO;YAAC,IAAI,CAAC,GAChD/C,OAAOgD,IAAI,CAACtF,MAAM,GAAG;gBAAEsF,MAAMhD,OAAOgD,IAAI;YAAC,IAAI,CAAC,GAC9ChD,OAAOM,WAAW,CAAC5C,MAAM,GACzB;gBAAE4C,aAAaN,OAAOM,WAAW;YAAC,IAClC,CAAC,GACDN,OAAOG,QAAQ,GAAG;gBAAEA,UAAUH,OAAOG,QAAQ;YAAC,IAAI,CAAC,GACnDH,OAAOiD,KAAK,GAAG;gBAAEA,OAAOjD,OAAOiD,KAAK;YAAC,IAAI,CAAC,GAI1CjD,OAAOkD,MAAM,GAAG;gBAAEA,QAAQlD,OAAOkD,MAAM;YAAC,IAAI,CAAC;gBACjD1F,QAAQwC,OAAOxC,MAAM;;YAEvB,IAAI,CAAC+E,SAASY,EAAE,EAAE;oBAGPX;gBAFT,4DAA4D;gBAC5DH,eACEe,QAAOZ,iBAAAA,OAAO,CAAC,QAAQ,YAAhBA,iBAAoB;gBAE7B;YACF;YACA,gEAAgE;YAChE,uBAAuB;YACvBvG,gBACEuG,OAAO,CAAC,UAAU,GACd,eACA,mEACJ;gBAAEa,SAAS;gBAAWC,SAAS;YAAM;YAEvCrB,cAAc;QAChB,EAAE,OAAOsB,OAAO;YACdC,QAAQD,KAAK,CAACA;YACdlB,eAAe;QACjB,SAAU;YACRF,cAAc;QAChB;IACF,GACA;QAACJ;QAAQ9F;KAAgB;IAG3B,MAAMwH,YAAYpK,YAChB,OAAOuH,MAAe8C,QAAiCC;QACrD,IAAI,CAACzH,OAAO;YACVD,gBAAgB,yDAAyD;gBACvEoH,SAAS;gBACTC,SAAS;YACX;YACA;QACF;QACA,IAAI;YACF,MAAMpK,UACJL,IAAIkD,WAAW,QAAQG,OAAO,SAAS0E,KAAKtD,MAAM,GAClD,aACKoG;gBACHE,WAAW3K;;YAGfgD,gBAAgB0H,MAAM;gBAAEN,SAAS;gBAAWC,SAAS;YAAM;QAC7D,EAAE,OAAOC,OAAO;YACdtH,gBACEsH,iBAAiBM,QACbN,MAAMO,OAAO,GACb,kCACJ;gBAAET,SAAS;YAAQ;QAEvB;IACF,GACA;QAACtH;QAAWE;KAAgB;IAG9B,MAAM8H,UAAUxK,QACd,IAAM;YACJ;gBACE6F,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G,MACpB+F,OAAO/F,GAAG,CAAC,OAAO,IAAIA,GAAG,CAAC,QAAQ,IAAI;gBACxCgH,YAAY,CAAC,EAAEhH,GAAG,EAAoB;wBAUtBA;yCATd,MAAC5E;wBACC6L,SAAS;wBACTC,IAAI;4BAAEL,UAAU;4BAAGM,gBAAgB;4BAAUC,QAAQ;wBAAO;;0CAE5D,KAAC/L;gCAAW2K,SAAQ;gCAAQqB,MAAM;0CAC/BtB,OAAO/F,GAAG,CAAC,OAAO,IAAIA,GAAG,CAAC,QAAQ,IAAIA,IAAIE,GAAG;;4BAE/CF,GAAG,CAAC,OAAO,iBACV,KAAC3E;gCAAW2K,SAAQ;gCAAUsB,OAAM;gCAAiBD,MAAM;0CACxDtB,QAAO/F,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgB;iCAExB;;;;YAGV;YACA,mEAAmE;YACnE,iCAAiC;YACjC;gBACE+B,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G;wBAAwBA;2BAAP+F,QAAO/F,eAAAA,IAAIsF,OAAO,YAAXtF,eAAe;;YAC/D;YACA;gBACE+B,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G;wBAAwBA;2BAAP+F,QAAO/F,gBAAAA,IAAIuF,QAAQ,YAAZvF,gBAAgB;;YAChE;YACA;gBACE+B,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G,MAAiBnH,MAAM0O,aAAa,CAACvH;gBAC3DgH,YAAY,CAAC,EAAEhH,GAAG,EAAoB,iBACpC,KAACwH;wBACCjE,MAAMvD;wBACNkB,UAAU,CAACH;4BACT,IAAIA,SAAS,eAAe;gCAC1BwD,gBAAgBvE;gCAChB;4BACF;4BACA,KAAKoG,UACHpG,KACA;gCACEG,QAAQY;+BACJlI,MAAM0O,aAAa,CAACvH,SAAS,gBAC7B;gCAAEyH,mBAAmBlM;4BAAc,IACnC,CAAC,IAEP;wBAEJ;;YAGN;YACA;gBACE;;;;SAIC,GACDwG,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G;oBACpB,MAAMY,QAAQ/H,MAAM6O,cAAc,CAAC1H;oBACnC,OAAOY,QAAQ/H,MAAM8O,kBAAkB,CAAC/G,MAAMT,MAAM,CAAC,GAAG;gBAC1D;gBACA6G,YAAY,CAAC,EAAEhH,GAAG,EAAoB;oBACpC,MAAMY,QAAQ/H,MAAM6O,cAAc,CAAC1H;oBACnC,OAAOY,sBACL,KAAC9C;wBAAkB8C,OAAOA;uCAE1B,KAACvF;wBAAW2K,SAAQ;wBAAUsB,OAAM;kCACjC;;gBAGP;YACF;YACA;gBACEvF,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G,MACpBA,IAAI8C,QAAQ,GAAG9D,OAAO4I,QAAQ,CAAC5H,IAAI8C,QAAQ,IAAI;YACnD;YACA,iEAAiE;eAC7DvE,SACA,EAAE,GACF;gBACE;oBACEwD,OAAO;oBACP4E,YAAY;oBACZC,MAAM;oBACNC,UAAU;oBACV;;;;;eAKC,GACDC,aAAa,CAACC,QAAiB/G;wBAC7B,MAAM6H,OAAOC,MAAMC,OAAO,CAAC/H,GAAG,CAAC,oBAAoB,IAC9CA,GAAG,CAAC,oBAAoB,GACzB,EAAE;wBACN,OAAO6H,KAAKrI,GAAG,CAAC,CAACE;;2CAAOX,yBAAAA,MAAOmF,QAAQ,CAACxE,sBAAOA;2BAAI2D,IAAI,CAAC;oBAC1D;gBACF;aACD;YACL;gBACEtB,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G,MACpBzC,YAAYyC,KAAKR,GAAG,CAAClC,iBAAiB+F,IAAI,CAAC;YAC/C;YACA;;;;;OAKC,GACD;gBACEtB,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G;wBAAwBA;2BAAP+F,QAAO/F,kBAAAA,IAAIkD,UAAU,YAAdlD,kBAAkB;;gBAChEgI,gBAAgB,CAACC,GAAYC,IAC3BrP,MAAMsP,eAAe,CAACjJ,eAAewD,QAAQ,EAAEuF,KAC7CpP,MAAMsP,eAAe,CAACjJ,eAAewD,QAAQ,EAAEwF,MACjDnC,OAAOkC,YAAAA,IAAK,IAAIG,aAAa,CAACrC,OAAOmC,YAAAA,IAAK;YAC9C;YACA;gBACEnG,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G;wBAAkBA;2BAAD,EAACA,YAAAA,IAAI2F,IAAI,YAAR3F,YAAY,EAAE,EAAEqD,IAAI,CAAC;;YAC/D;YACA,kEAAkE;YAClE,uBAAuB;YACvB;gBACEtB,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAiB/G,MAC7BnH,MAAMwP,eAAe,CAACrI,KAAKR,GAAG,CAAC+B,cAAc8B,IAAI,CAAC;YACtD;YACA;gBACEtB,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQ/G;wBACNA;2BAAdxC,eAAcwC,oBAAAA,GAAG,CAAC,eAAe,YAAnBA,oBAAuBA,GAAG,CAAC,YAAY;;YACzD;YACA,qEAAqE;YACrE,kEAAkE;YAClE,iEAAiE;YACjE,2CAA2C;eACxCnG,mBAAmBoF,WAAW2D,MAAM;YACvC;gBACEb,OAAO;gBACP4E,YAAY;gBACZ2B,OAAO;gBACPC,UAAU;gBACVC,YAAY;gBACZC,mBAAmB;gBACnBzB,YAAY,CAAC,EAAEhH,GAAG,EAAoB;wBAsBhBA;oBArBpB;;;;;WAKC,GACD,MAAM0I,YAAYC,QAAQ3I,IAAI4I,kBAAkB;oBAChD,MAAMC,iBAAiBhQ,MAAMiQ,wBAAwB,CAAC9I,SAAS;oBAC/D,MAAM+I,iBAAiBL,YACnB,4BACA,CAAC7P,MAAMmQ,aAAa,CAAChJ,OACnB,8BACA6I,iBACEhL,iCACA;oBACR,qBACE,KAACjD;wBACCqO,SAAS,CAACC,QAAUA,MAAMC,eAAe;wBACzCjC,IAAI;4BAAEkC,SAAS;4BAAQC,YAAY;4BAAUjC,QAAQ;wBAAO;kCAE5D,cAAA,KAAC/M;4BACCuH,OAAOmE,QAAO/F,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgBA,IAAIE,GAAG;4BACrCoJ,OAAO;gCACL;oCACEC,KAAK;oCACL3H,OAAO;oCACP4H,oBACE,KAACrQ;wCAAQsQ,MAAM3Q,qBAAqB2Q,IAAI;wCAAEC,MAAM;;oCAElDC,MAAMxK,OAAOoE,IAAI,CAACvD,IAAIC,MAAM;gCAC9B;gCACA;oCACEsJ,KAAK;oCACL3H,OAAO;oCACP4H,oBACE,KAACrQ;wCACCsQ,MAAMzQ,yBAAyByQ,IAAI;wCACnCC,MAAM;;oCAGVT,SAAS,IAAMxE,cAAczE;oCAC7B4J,UAAUb,mBAAmB;oCAC7Bc,cAAc,EAAEd,yBAAAA,iBAAkBe;gCACpC;gCACA;oCACEP,KAAK;oCACL3H,OAAO;oCACP4H,oBACE,KAACrQ;wCAAQsQ,MAAMxQ,qBAAqBwQ,IAAI;wCAAEC,MAAM;;oCAElDT,SAAS,IAAM5E,aAAarE;gCAC9B;gCACA;oCACEuJ,KAAK;oCACL3H,OAAO;oCACP4H,oBACE,KAACrQ;wCAAQsQ,MAAM1Q,wBAAwB0Q,IAAI;wCAAEC,MAAM;;oCAErDT,SAAS,IAAM1E,gBAAgBvE;oCAC/B4J,UACE,CAAC/Q,MAAMmQ,aAAa,CAAChJ,QACrB2I,QAAQ3I,IAAI4I,kBAAkB;oCAChCiB,gBAAgB7J,IAAI4I,kBAAkB,GAClC,4BACA;gCACN;6BACD;;;gBAIT;YACF;SACD,EACD;QACE5J;QACAG;QACAiH;QACA7H;QACAQ;QACAwC;QACAtC,WAAW2D,MAAM;QACjB1D,eAAewD,QAAQ;KACxB;IAEH;;;GAGC,GACD,MAAMqH,gBAAgB7N,QACpB,IACEc,iBAAiB0J,SAASlK,yBAAyBqF,eAAepF,2BACpE;QAACiK;QAAS7E;KAAc;IAE1B,MAAMmI,OAAOlQ,eAAe2G,OAAOsJ;IAEnC,qBACE;;0BACE,KAAC7Q;gBACC+Q,QAAQ;gBACRC,MAAMrR,MAAMsR,cAAc,CAAC,YAAY;oBACrCC,QAAQ;gBACV;gBACAC,cAAc;gBACdC,cAAc;gBACdC,aAAa;oBACX,8DAA8D;oBAC9DC,sBACE,MAACrQ;;0CACC,KAACsD;gCAAiBc,QAAQA;gCAAQM,OAAOA;;0CACzC,KAAChE;gCAAO6O,MAAK;gCAAQT,SAAS9E;gCAAcyF,UAAU,CAACtG,KAAKjD,MAAM;0CAC/D;;0CAEH,KAACxF;gCACC6O,MAAK;gCACL1D,SAAQ;gCACRiD,SAAS,IAAMrE,cAAc;0CAE5B;;;;gBAIT;0BAEA,cAAA,MAACxJ;oBAAM6L,SAAS;;wBAEb1I,uBACC,KAACP;4BAAiBO,QAAQA;2CAE1B,KAACJ;sCAOH,MAAC/D;4BAAewH,OAAM;;8CACpB,KAAC1H;oCAAgBuQ,YAAYhK;oCAAOiK,UAAS;;8CAC7C,KAACxN;oCACCmJ,QAAQ7J;oCACRmO,SAASlO;oCACTkE,SAASA;oCACTO,UAAUJ;oCACVU,SAASK;oCACT+I,aAAa;;;;wBAGhBzK,WAAW,aAAaI,OAAOF,MAAM,KAAK,kBACzC,KAAC9F;4BACCqH,OAAO;4BACPiJ,aACE;2CAIJ;;8CACE,KAAC3M;oCACCoF,MAAMA;oCACNwH,UAAUjH;oCACVkH,kBAAkBjH;oCAClB9E,QAAQA;oCACRgM,KAAKjH;oCACLlF,OAAOA;oCACPN,QAAQA;oCACRC,KAAKA;;8CAEP,KAACxE;oCAAuB2H,OAAOqI,KAAKiB,WAAW;8CAC7C,cAAA,KAAC5R;wCACCiK,MAAMM;wCACN8C,SAASsD,KAAKtD,OAAO;wCACrBwE,OAAOnR;wCACPoR,YAAY;4CACVL,UAAUjH;4CACV3C,UAAU4C;wCACZ;wCACAsH,SAASjL,WAAW;wCACpBkL,QAAQ,CAACC,KAAKtL,MACZrB,OAAO4M,IAAI,CACTpM,OAAOoE,IAAI,CAACvD,IAAIC,MAAM;wCAG1B,0DAA0D;wCAC1DuL,uBAAuBxB,KAAKwB,qBAAqB;wCACjDC,+BACEzB,KAAKyB,6BAA6B;wCAEpCC,WAAW1B,KAAK0B,SAAS;wCACzBC,mBAAmB3B,KAAK2B,iBAAiB;wCACzC,uDAAuD;wCACvD,iDAAiD;wCACjDC,YAAW;wCACXC,aAAa5K,WAAW4K,WAAW;wCACnCC,qBAAqB7K,WAAW6K,mBAAmB;wCACnDC,WAAW;wCACXC,aAAa,CAAC,yBAAyB,EAAEzL,OAAOF,MAAM,CAAC4L,cAAc,GAAG,mBAAmB,CAAC;wCAC5F,8DAA8D;wCAC9DC,UAAU;;;8CAGd,KAAC9S;oCACCoK,MAAMA;oCACNE,UAAUA;oCACVyI,UAAUvI,SAASvD,MAAM;oCACzB+L,OAAO9I,KAAKjD,MAAM;oCAClBgM,cAAc5I;oCACd6I,kBAAkB3I;;;;wBAIvBrD,0BACC,KAAC3F;4BAAM4R,UAAS;sCACb,CAAC,YAAY,EAAEnO,aAAa6N,cAAc,GAAG,oBAAoB,CAAC,GACjE,oDACA,GAAG7N,aAAa6N,cAAc,GAAG,6BAA6B,CAAC,GAC/D;6BAEF;;;;0BAGR,KAACvO;gBACC8O,MAAM7H;gBACN8H,SAAS,IAAM7H,cAAc;gBAC7BrG,MAAM,EAAEA,iBAAAA,SAAU;gBAClBC,KAAKA;gBACLkO,MAAM7H;gBACNqB,OAAOnB;gBACP/F,QAAQA;gBACRH,OAAOA;gBACP8N,UAAU,CAAChK,SAAW,KAAKsC,aAAatC;;0BAE1C,KAACiK;gBACCrJ,MAAMa;gBACNpF,QAAQA;gBACRyN,SAAS,IAAMpI,aAAa;gBAC5BwI,UAAU,CAAC9J;oBACT,IAAI,CAACqB,WAAW;oBAChB,KAAKgC,UACHhC,WACA;wBAAEtB,UAAUC,OAAOxH;oBAAc,GACjCwH,MAAM,mBAAmB;oBAE3BsB,aAAa;gBACf;;0BAEF,KAACpG;gBACCuO,MAAM7D,QAAQrE;gBACdmI,SAAS,IAAMlI,gBAAgB;gBAC/BhG,MAAM,EAAEA,iBAAAA,SAAU;gBAClB0B,MAAM,UAAEqE,gCAAAA,aAAcrE,MAAM,mBAAI;gBAChC6M,WAAW/G,OACTzB,CAAAA,gCAAAA,YAAc,CAAC,OAAO,MAAIA,gCAAAA,YAAc,CAAC,QAAQ,KAAI;;0BAQzD,KAACjH;gBACCmP,MAAM7D,QAAQnE;gBACdiI,SAAS,IAAMhI,cAAc;gBAC7BlG,MAAM,EACJA,iBAAAA,SACC1F,MAAMkU,gBAAgB,CAACvI,YAAYhG,KAAgCD,MAAM,IAAI;gBAEhFM,OAAOA;gBACPL,KAAKA;gBACLyB,MAAM,WAAEuE,8BAAAA,WAAYvE,MAAM,oBAAI;gBAC9BsD,IAAI,EAAEiB,qBAAAA,aAAc,CAAC;gBACrB/F,QAAQ,EAAEA,mBAAAA,WAAY;gBACtBO,QAAQA;;;;AAIhB;AACAX,gBAAgB2O,WAAW,GAAG;AAE9B;;;;;;CAMC,GACD,SAASxF,aAAalJ,KAGrB;IACC,MAAM,EAAEiF,IAAI,EAAErC,QAAQ,EAAE,GAAG5C;IAC3B,IAAIiF,KAAKqF,kBAAkB,EAAE;QAC3B,qBACE,KAAChO;YAAIsM,IAAI;gBAAEkC,SAAS;gBAAQC,YAAY;gBAAUjC,QAAQ;YAAO;sBAC/D,cAAA,KAACrJ;gBAAewF,MAAMA;;;IAG5B;IACA,MAAMpD,SAAStH,MAAM0O,aAAa,CAAChE;IACnC,qBACE,KAAC3I;QACCqO,SAAS,CAACC,QAAUA,MAAMC,eAAe;QACzCjC,IAAI;YACFkC,SAAS;YACTC,YAAY;YACZjC,QAAQ;YACRkB,OAAO;QACT;kBAEA,cAAA,MAACnN;YACCuO,MAAK;YACL1D,SAAQ;YACRiH,gBAAgB;YAChBtL,OAAOxB;YACPe,UAAU,CAACgI,QAAUhI,SAASgI,MAAMgE,MAAM,CAACvL,KAAK;YAChDwL,aAAa,kBAAM,KAACpP;oBAAewF,MAAMA;;YACzC2D,IAAI;gBAAEoB,OAAO;YAAO;;8BAEpB,KAACpN;oBAASyG,OAAM;8BAAO9I,MAAMuU,sBAAsB,CAACC,GAAG;;8BACvD,KAACnS;oBAASyG,OAAM;8BACb9I,MAAMuU,sBAAsB,CAACE,OAAO;;8BAEvC,KAACpS;oBAASyG,OAAM;8BAAe,GAAG9I,MAAMuU,sBAAsB,CAACG,WAAW,CAAC,CAAC,CAAC;;;;;AAIrF;AACA/F,aAAawF,WAAW,GAAG;AAE3B,kCAAkC,GAClC,SAASJ,kBAAkBtO,KAK1B;;IACC,MAAM,EAAEiF,IAAI,EAAEvE,MAAM,EAAEyN,OAAO,EAAEI,QAAQ,EAAE,GAAGvO;IAC5C,MAAM,CAACyE,KAAKyK,OAAO,GAAGrR,SAAwB;IAC9C,MAAMsR,UAAU1K,cAAAA,MAAOgD,eAAOxC,wBAAAA,KAAMT,QAAQ,mBAAI;IAChD,qBACE,MAAChI;QACC0R,MAAM7D,QAAQpF;QACdkJ,SAASA;QACTiB,UAAS;QACTC,SAAS;QACTC,WAAW;YAAEC,YAAY;gBAAEC,UAAU,IAAMN,OAAO;YAAM;QAAE;;0BAE1D,KAACvS;0BAAa,CAAC,OAAO,EAAE8K,OAAOxC,CAAAA,wBAAAA,IAAM,CAAC,OAAO,MAAIA,wBAAAA,IAAM,CAAC,QAAQ,KAAI,SAAS;;0BAC7E,KAACvI;0BACC,cAAA,KAACJ;oBAAIsM,IAAI;wBAAE6G,IAAI;oBAAE;8BACf,cAAA,KAACnQ;wBACC+D,OAAO8L;wBACPvM,UAAUsM;wBACVxO,QAAQA;wBACR0K,MAAK;;;;0BAIX,MAAC3O;;kCACC,KAACF;wBAAOoO,SAASwD;kCAAU;;kCAC3B,KAAC5R;wBAAOmL,SAAQ;wBAAYiD,SAAS,IAAM4D,SAASY;kCACjD;;;;;;AAKX;AACAb,kBAAkBI,WAAW,GAAG;AAEhC,eAAe3O,gBAAe"}
@@ -39,14 +39,78 @@ import { refreshCrmNextActivity } from "../model/next-activity-api.js";
39
39
  import { completeCrmTask } from "../model/task-api.js";
40
40
  import { crmTaskCallScope } from "../model/task-routes.js";
41
41
  import { tasksCsv } from "../model/tasks-csv.js";
42
- import { CRM_TASK_VIEW_LIMIT, CRM_TASK_VIEWS } from "../model/task-views.js";
42
+ import { CRM_TASK_KIND_LABELS, CRM_TASK_KINDS, CRM_TASK_PRIORITIES, CRM_TASK_PRIORITY_LABELS, CRM_TASK_VIEW_LIMIT, CRM_TASK_VIEWS } from "../model/task-views.js";
43
+ import { matchListFilter } from "@aglyn/shared-ui-jsx/const/list-filter";
44
+ import { crmFilterColumns, crmRowMatchesSearch } from "../model/crm-grid-filter.js";
45
+ import { useCrmGridFilter } from "../hooks/use-crm-grid-filter.js";
46
+ import CrmFilterBar from "./crm-filter-bar.js";
47
+ import { CrmListToolbar } from "./crm-list-toolbar.js";
43
48
  import { TaskDueText, TaskKindCell, TaskPriorityChip, TaskRecordLink } from "./task-cells.js";
44
49
  import TaskEditDrawer from "./task-edit-drawer.js";
45
50
  import TasksCalendar from "./tasks-calendar.js";
46
51
  import { TaskImportButton } from "./task-import-drawer.js";
47
52
  import TaskSnoozeMenu from "./task-snooze-menu.js";
48
53
  import TasksBulkBar from "./tasks-bulk-bar.js";
49
- /** What an empty view is headed, by view — "nothing overdue" is good news. */ const EMPTY_LABEL = {
54
+ /** What an empty view is headed, by view — "nothing overdue" is good news. */ /*
55
+ * What the tasks grid's panel offers (AGL-3313). `view` is the task view
56
+ * the query serves — a hidden column, since it is no field of a task — and
57
+ * the rest narrow the rows that view loaded.
58
+ */ const TASK_FILTER_FIELDS = [
59
+ {
60
+ column: 'view',
61
+ kind: 'exact',
62
+ path: 'view',
63
+ operators: [
64
+ 'equals'
65
+ ]
66
+ },
67
+ {
68
+ column: 'kind',
69
+ kind: 'exact',
70
+ path: 'kind',
71
+ operators: [
72
+ 'equals',
73
+ 'isAnyOf'
74
+ ]
75
+ },
76
+ {
77
+ column: 'priority',
78
+ kind: 'exact',
79
+ path: 'priority',
80
+ operators: [
81
+ 'equals',
82
+ 'isAnyOf'
83
+ ]
84
+ },
85
+ {
86
+ column: 'assigneeUid',
87
+ kind: 'exact',
88
+ path: 'assigneeUid',
89
+ operators: [
90
+ 'equals',
91
+ 'isAnyOf'
92
+ ]
93
+ }
94
+ ];
95
+ const TASK_FILTER_HEADERS = {
96
+ view: 'Show',
97
+ kind: 'Kind',
98
+ priority: 'Priority',
99
+ assigneeUid: 'Assignee'
100
+ };
101
+ /** The filter-only `view` column never shows. */ const TASK_HIDDEN_COLUMNS = {
102
+ view: false
103
+ };
104
+ /** No `view` clause is "My tasks", which the section opened on before views existed. */ const TASK_VIEW_DEFAULT = {
105
+ field: 'view',
106
+ op: 'equals',
107
+ value: 'mine'
108
+ };
109
+ /** What the quick search reads on a task. */ const TASK_SEARCH_FIELDS = [
110
+ 'title',
111
+ 'notes'
112
+ ];
113
+ const EMPTY_LABEL = {
50
114
  mine: 'Nothing is assigned to you',
51
115
  overdue: 'Nothing is overdue',
52
116
  today: 'Nothing is due today',
@@ -118,15 +182,30 @@ import TasksBulkBar from "./tasks-bulk-bar.js";
118
182
  }, [
119
183
  views.state.filters
120
184
  ]);
121
- const setView = useCallback((next)=>views.setFilters([
122
- {
123
- field: 'view',
124
- op: 'equals',
125
- value: next
126
- }
127
- ]), [
185
+ /*
186
+ * The grid's own Filters panel and quick search edit the view's clauses
187
+ * (AGL-3313). "Show" is the task view the query serves — the hidden
188
+ * `view` column, as the toggle stored it, with none meaning "My tasks" —
189
+ * and Kind, Priority and Assignee narrow the rows it loaded.
190
+ */ const clauses = useMemo(()=>views.state.filters.some((clause)=>clause.field === 'view') ? views.state.filters : [
191
+ TASK_VIEW_DEFAULT,
192
+ ...views.state.filters
193
+ ], [
194
+ views.state.filters
195
+ ]);
196
+ const setClauses = useCallback((next)=>views.setFilters(next.filter((clause)=>!(clause.field === 'view' && clause.value === TASK_VIEW_DEFAULT.value))), [
128
197
  views.setFilters
129
198
  ]);
199
+ const gridFilter = useCrmGridFilter({
200
+ clauses,
201
+ onChange: setClauses,
202
+ selectFields: [
203
+ 'view',
204
+ 'kind',
205
+ 'priority',
206
+ 'assigneeUid'
207
+ ]
208
+ });
130
209
  const list = useCrmTaskList({
131
210
  hostId,
132
211
  org: orgRecord,
@@ -134,8 +213,37 @@ import TasksBulkBar from "./tasks-bulk-bar.js";
134
213
  uid: user == null ? void 0 : user.uid,
135
214
  nowMs
136
215
  });
137
- const { tasks, status, fromCache, truncated, scope, orgId, readTokens } = list;
216
+ const { tasks: loaded, status, fromCache, truncated, scope, orgId, readTokens } = list;
138
217
  const directory = useOrgMemberDirectory(orgId);
218
+ const filterOptions = useMemo(()=>({
219
+ view: CRM_TASK_VIEWS.map((option)=>({
220
+ value: option.id,
221
+ label: option.label
222
+ })),
223
+ kind: CRM_TASK_KINDS.map((kind)=>({
224
+ value: kind,
225
+ label: CRM_TASK_KIND_LABELS[kind]
226
+ })),
227
+ priority: CRM_TASK_PRIORITIES.map((priority)=>({
228
+ value: priority,
229
+ label: CRM_TASK_PRIORITY_LABELS[priority]
230
+ })),
231
+ assigneeUid: directory.members.map((member)=>({
232
+ value: member.uid,
233
+ label: member.label
234
+ }))
235
+ }), [
236
+ directory.members
237
+ ]);
238
+ // Every clause but the served view, and the search, over the loaded rows.
239
+ const searchKey = gridFilter.searchWords.join(' ');
240
+ const tasks = useMemo(()=>loaded.filter((task)=>clauses.every((clause)=>clause.field === 'view' || matchListFilter(task, TASK_FILTER_FIELDS, clause)) && crmRowMatchesSearch(task, TASK_SEARCH_FIELDS, gridFilter.searchWords)), // `searchKey` stands for the words, which are a new array each render.
241
+ // eslint-disable-next-line react-hooks/exhaustive-deps
242
+ [
243
+ loaded,
244
+ clauses,
245
+ searchKey
246
+ ]);
139
247
  /*
140
248
  * Every record a task names, not only the one its "For" cell shows: the
141
249
  * export writes the contact, the company and the deal each by name, and
@@ -390,7 +498,11 @@ import TasksBulkBar from "./tasks-bulk-bar.js";
390
498
  nameOf,
391
499
  scope
392
500
  ]);
393
- /* The column and sort models are the view's (AGL-2617). */ const grid = useCrmViewGrid(views, columns);
501
+ /* The column and sort models are the view's (AGL-2617). */ const filterColumns = useMemo(()=>crmFilterColumns(columns, TASK_FILTER_FIELDS, filterOptions, TASK_FILTER_HEADERS), [
502
+ columns,
503
+ filterOptions
504
+ ]);
505
+ const grid = useCrmViewGrid(views, filterColumns, TASK_HIDDEN_COLUMNS);
394
506
  return /*#__PURE__*/ _jsxs(_Fragment, {
395
507
  children: [
396
508
  /*#__PURE__*/ _jsx(CardDisplay, {
@@ -416,32 +528,20 @@ import TasksBulkBar from "./tasks-bulk-bar.js";
416
528
  children: /*#__PURE__*/ _jsxs(Stack, {
417
529
  spacing: 2,
418
530
  children: [
419
- /*#__PURE__*/ _jsxs(Stack, {
420
- direction: "row",
421
- spacing: 2,
422
- sx: {
423
- alignItems: 'center',
424
- flexWrap: 'wrap',
425
- rowGap: 1
426
- },
531
+ /*#__PURE__*/ _jsxs(CrmListToolbar, {
532
+ label: "Task filters",
427
533
  children: [
428
534
  /*#__PURE__*/ _jsx(CrmViewsControl, {
429
535
  controller: views,
430
536
  allLabel: "All tasks"
431
537
  }),
432
- /*#__PURE__*/ _jsx(ToggleButtonGroup, {
433
- exclusive: true,
434
- size: "small",
435
- color: "primary",
436
- value: view,
437
- onChange: (_event, next)=>{
438
- if (next) setView(next);
439
- },
440
- "aria-label": "Task view",
441
- children: CRM_TASK_VIEWS.map((option)=>/*#__PURE__*/ _jsx(ToggleButton, {
442
- value: option.id,
443
- children: option.label
444
- }, option.id))
538
+ /*#__PURE__*/ _jsx(CrmFilterBar, {
539
+ fields: TASK_FILTER_FIELDS,
540
+ headers: TASK_FILTER_HEADERS,
541
+ clauses: clauses,
542
+ onChange: setClauses,
543
+ options: filterOptions,
544
+ servedField: "view"
445
545
  }),
446
546
  /*#__PURE__*/ _jsxs(ToggleButtonGroup, {
447
547
  exclusive: true,
@@ -483,7 +583,7 @@ import TasksBulkBar from "./tasks-bulk-bar.js";
483
583
  variant: "body2",
484
584
  color: "error",
485
585
  children: 'The tasks could not be loaded. Reload to try again.'
486
- }) : status === 'success' && !tasks.length ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
586
+ }) : status === 'success' && !loaded.length ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
487
587
  label: EMPTY_LABEL[view],
488
588
  description: EMPTY_COPY[view],
489
589
  action: /*#__PURE__*/ _jsx(Button, {
@@ -539,7 +639,12 @@ import TasksBulkBar from "./tasks-bulk-bar.js";
539
639
  task: found
540
640
  });
541
641
  },
542
- disableColumnFilter: true,
642
+ // The panel and the search are the grid's; the section
643
+ // answers them, the view through the query (AGL-3313).
644
+ filterMode: "server",
645
+ filterModel: gridFilter.filterModel,
646
+ onFilterModelChange: gridFilter.onFilterModelChange,
647
+ quickFilter: true,
543
648
  // Columns and sort are the view's, controlled (AGL-2617).
544
649
  columnVisibilityModel: grid.columnVisibilityModel,
545
650
  onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,