@aglyn/plugins-crm 1.0.0-beta.186 → 1.0.0-beta.187

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-crm",
3
- "version": "1.0.0-beta.186",
3
+ "version": "1.0.0-beta.187",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.186",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.186",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.186",
31
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.186",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.186",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.186",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.186",
35
- "@aglyn/shared-util-email": "1.0.0-beta.186",
36
- "@aglyn/shared-util-http": "1.0.0-beta.186",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.186",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.186",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.186",
28
+ "@aglyn/aglyn": "1.0.0-beta.187",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.187",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.187",
31
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.187",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.187",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.187",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.187",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.187",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.187",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.187",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.187",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.187",
40
40
  "@dnd-kit/core": "^6.3.1",
41
41
  "@dnd-kit/utilities": "^3.2.2",
42
42
  "@swc/helpers": "0.5.23",
@@ -26,7 +26,8 @@ import { CrmListActions, CrmListToolbar } from "./crm-list-toolbar.js";
26
26
  import { CRM_LIST_SLOTS, CrmColumnOrderProvider } from "./crm-column-menu.js";
27
27
  import { CRM_NEXT_ACTIVITY_FILTER_FIELD, CRM_NEXT_ACTIVITY_FILTER_HEADER, nextActivityColumn } from "./crm-next-activity-column.js";
28
28
  import CrmFilterBar from "./crm-filter-bar.js";
29
- import { crmFilterColumns } from "../model/crm-grid-filter.js";
29
+ import { crmFilterColumns, crmRowMatchesSearch } from "../model/crm-grid-filter.js";
30
+ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
30
31
  import { useCrmGridFilter } from "../hooks/use-crm-grid-filter.js";
31
32
  import CrmViewsControl from "./crm-views-control.js";
32
33
  import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
@@ -37,7 +38,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
37
38
  import { useContactFieldDefinitions } from "../hooks/use-contact-field-definitions.js";
38
39
  import { useCrmScope } from "../hooks/use-crm-scope.js";
39
40
  import { useDealStageApi } from "../hooks/use-deal-stage-api.js";
40
- import { BOARD_CLOSED_LIMIT, BOARD_OPEN_LIMIT, useDealsByStatus, usePagedDeals } from "../hooks/use-deals.js";
41
+ import { BOARD_CLOSED_LIMIT, BOARD_OPEN_LIMIT, DEAL_SEARCH_WINDOW, useDealSearchWindow, useDealsByStatus, usePagedDeals } from "../hooks/use-deals.js";
41
42
  import { useOrgMemberDirectory } from "../hooks/use-org-member-directory.js";
42
43
  import { usePipeline } from "../hooks/use-pipeline.js";
43
44
  import { downloadTextFile } from "../model/contacts-csv.js";
@@ -67,6 +68,9 @@ const DEAL_GRID_FILTER_HEADERS = {
67
68
  status: 'Status',
68
69
  [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER
69
70
  };
71
+ /** What the table's search reads on a deal. */ const DEAL_SEARCH_FIELDS = [
72
+ 'title'
73
+ ];
70
74
  const DEAL_FILTER_OPTIONS = {
71
75
  status: [
72
76
  'open',
@@ -123,7 +127,7 @@ const DEAL_FILTER_OPTIONS = {
123
127
  * `dealsCsv()`, naming the pipeline, the stage and the owner; the bar
124
128
  * writes the same file over the selection.
125
129
  */ export function DealsSection(props) {
126
- var _ref, _ref1, _ref2, _ref3, _ref4, _scope_orgId, _ref5;
130
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _scope_orgId, _ref6;
127
131
  const { hostId, org, basePath } = props;
128
132
  const routes = crmRoutes(basePath != null ? basePath : '');
129
133
  const router = useRouter();
@@ -182,6 +186,28 @@ const DEAL_FILTER_OPTIONS = {
182
186
  const won = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'won', BOARD_CLOSED_LIMIT);
183
187
  const lost = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'lost', BOARD_CLOSED_LIMIT);
184
188
  const paged = usePagedDeals(view === 'table' ? scope.orgId : null, scope.visibleTo, statusFilter, (_ref3 = pipeline == null ? void 0 : pipeline.$id) != null ? _ref3 : null);
189
+ /*
190
+ * THE SEARCH (AGL-3315): the grid's quick-filter box, answered over a
191
+ * window of every deal the table's query would page through — read only
192
+ * while the box holds a word — so a deal on page four is found by any
193
+ * word of its title. See `useDealSearchWindow`.
194
+ */ const [searchWords, setSearchWords] = useState([]);
195
+ const searching = searchWords.some((word)=>word.trim());
196
+ const searchWindow = useDealSearchWindow(view === 'table' && searching ? scope.orgId : null, scope.visibleTo, statusFilter, (_ref4 = pipeline == null ? void 0 : pipeline.$id) != null ? _ref4 : null);
197
+ const searchKey = searchWords.join(' ');
198
+ const found = useMemo(()=>searchWindow.data.slice(0, DEAL_SEARCH_WINDOW).filter((deal)=>crmRowMatchesSearch(deal, DEAL_SEARCH_FIELDS, searchWords)), // `searchKey` stands for the words, which are a new array each render.
199
+ // eslint-disable-next-line react-hooks/exhaustive-deps
200
+ [
201
+ searchWindow.data,
202
+ searchKey
203
+ ]);
204
+ const searchTruncated = searchWindow.data.length > DEAL_SEARCH_WINDOW;
205
+ const [searchPage, setSearchPage] = useState(0);
206
+ const [searchPageSize, setSearchPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
207
+ useEffect(()=>setSearchPage(0), [
208
+ searchKey,
209
+ statusFilter
210
+ ]);
185
211
  const summary = useMemo(()=>boardSummary(open.data, pipeline), [
186
212
  open.data,
187
213
  pipeline
@@ -270,9 +296,12 @@ const DEAL_FILTER_OPTIONS = {
270
296
  pipelineState,
271
297
  roster.members
272
298
  ]);
299
+ // The page on screen — or, while searching, every deal the search found.
273
300
  const handleExport = useCallback(()=>{
274
- downloadTextFile('deals.csv', 'text/csv', dealsCsv(paged.rows, csvOptions));
301
+ downloadTextFile('deals.csv', 'text/csv', dealsCsv(searching ? found : paged.rows, csvOptions));
275
302
  }, [
303
+ searching,
304
+ found,
276
305
  paged.rows,
277
306
  csvOptions
278
307
  ]);
@@ -403,7 +432,15 @@ const DEAL_FILTER_OPTIONS = {
403
432
  dealFields.active,
404
433
  nowMs
405
434
  ]);
406
- const tableRows = useMemo(()=>filterByNextActivity(paged.rows, viewFilters), [
435
+ /** The table's rows: the query's page, or the search's matches a page at a time. */ const searchRows = useMemo(()=>filterByNextActivity(found, viewFilters), [
436
+ found,
437
+ viewFilters
438
+ ]);
439
+ const tableRows = useMemo(()=>searching ? searchRows.slice(searchPage * searchPageSize, (searchPage + 1) * searchPageSize) : filterByNextActivity(paged.rows, viewFilters), [
440
+ searching,
441
+ searchRows,
442
+ searchPage,
443
+ searchPageSize,
407
444
  paged.rows,
408
445
  viewFilters
409
446
  ]);
@@ -423,7 +460,11 @@ const DEAL_FILTER_OPTIONS = {
423
460
  'status'
424
461
  ],
425
462
  single: true,
426
- keepAlongside: isNoNextActivityClause
463
+ keepAlongside: isNoNextActivityClause,
464
+ search: {
465
+ words: searchWords,
466
+ onChange: setSearchWords
467
+ }
427
468
  });
428
469
  const noOrg = scope.ready && !scope.orgId;
429
470
  const pipelineLoading = !noOrg && (pipelineState.status === 'loading' || pipelineState.seeding || !pipeline && pipelineState.status !== 'error');
@@ -497,7 +538,7 @@ const DEAL_FILTER_OPTIONS = {
497
538
  select: true,
498
539
  size: "small",
499
540
  label: "Pipeline",
500
- value: (_ref4 = pipeline == null ? void 0 : pipeline.$id) != null ? _ref4 : '',
541
+ value: (_ref5 = pipeline == null ? void 0 : pipeline.$id) != null ? _ref5 : '',
501
542
  onChange: (event)=>setPipelineId(event.target.value),
502
543
  sx: {
503
544
  minWidth: 160
@@ -645,7 +686,7 @@ const DEAL_FILTER_OPTIONS = {
645
686
  /*#__PURE__*/ _jsx(Button, {
646
687
  size: "small",
647
688
  onClick: handleExport,
648
- disabled: !paged.rows.length,
689
+ disabled: !(searching ? found : paged.rows).length,
649
690
  children: 'Export CSV'
650
691
  })
651
692
  ]
@@ -675,18 +716,18 @@ const DEAL_FILTER_OPTIONS = {
675
716
  },
676
717
  onOpen: (_id, row)=>openDeal(row),
677
718
  // The panel's status is the query's; "No next activity"
678
- // narrows the loaded page (AGL-3313). No quick search:
679
- // nothing indexes a deal's name, and a search over one
680
- // page answers "no match" for a deal on the next.
719
+ // narrows the loaded rows (AGL-3313); the search runs
720
+ // over every deal in the window (AGL-3315).
681
721
  filterMode: "server",
682
722
  filterModel: gridFilter.filterModel,
683
723
  onFilterModelChange: gridFilter.onFilterModelChange,
684
- quickFilter: false,
724
+ quickFilter: true,
725
+ loading: searching && searchWindow.status === 'loading',
685
726
  // An empty table is said inside the grid, so its toolbar
686
727
  // stays to change the status that emptied it.
687
- noRowsLabel: statusFilter === 'all' ? 'No deals yet' : `No ${statusFilter} deals`,
688
- noRowsDescription: statusFilter === 'all' ? 'A deal is a sale in progress, moved across the pipeline as it advances.' : undefined,
689
- noRowsAction: statusFilter === 'all' ? /*#__PURE__*/ _jsx(Button, {
728
+ noRowsLabel: searching ? `No deals match “${searchKey.trim()}”` : statusFilter === 'all' ? 'No deals yet' : `No ${statusFilter} deals`,
729
+ noRowsDescription: statusFilter === 'all' && !searching ? 'A deal is a sale in progress, moved across the pipeline as it advances.' : undefined,
730
+ noRowsAction: statusFilter === 'all' && !searching ? /*#__PURE__*/ _jsx(Button, {
690
731
  size: "small",
691
732
  variant: "contained",
692
733
  startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
@@ -705,14 +746,25 @@ const DEAL_FILTER_OPTIONS = {
705
746
  hideFooter: true
706
747
  })
707
748
  }),
708
- /*#__PURE__*/ _jsx(ListPagination, {
749
+ searching ? /*#__PURE__*/ _jsx(ListPagination, {
750
+ page: searchPage,
751
+ pageSize: searchPageSize,
752
+ rowCount: tableRows.length,
753
+ count: searchRows.length,
754
+ onPageChange: setSearchPage,
755
+ onPageSizeChange: setSearchPageSize
756
+ }) : /*#__PURE__*/ _jsx(ListPagination, {
709
757
  page: paged.page,
710
758
  pageSize: paged.pageSize,
711
759
  rowCount: paged.rows.length,
712
760
  hasMore: paged.hasMore,
713
761
  onPageChange: paged.setPage,
714
762
  onPageSizeChange: paged.setPageSize
715
- })
763
+ }),
764
+ searching && searchTruncated ? /*#__PURE__*/ _jsx(Alert, {
765
+ severity: "info",
766
+ children: `The search reads the ${DEAL_SEARCH_WINDOW.toLocaleString()} most recently ` + 'changed deals; narrow by status to reach older ones.'
767
+ }) : null
716
768
  ]
717
769
  })
718
770
  ]
@@ -745,7 +797,7 @@ const DEAL_FILTER_OPTIONS = {
745
797
  }),
746
798
  /*#__PURE__*/ _jsx(LostReasonDialog, {
747
799
  open: Boolean(losing),
748
- dealTitle: (_ref5 = losing == null ? void 0 : losing.title) != null ? _ref5 : '',
800
+ dealTitle: (_ref6 = losing == null ? void 0 : losing.title) != null ? _ref6 : '',
749
801
  onClose: ()=>setLosing(null),
750
802
  onConfirm: confirmLost
751
803
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deals-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n type CrmDealStatus,\n dealStageById,\n filterByNextActivity,\n findOrgMember,\n isNoNextActivityClause,\n ORG_SCOPE_TOKEN,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n mdiCogOutline,\n mdiPlus,\n mdiTableLarge,\n mdiViewColumnOutline,\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 type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport CrmFilterBar from './crm-filter-bar'\nimport { crmFilterColumns } from '../model/crm-grid-filter'\nimport { useCrmGridFilter } from '../hooks/use-crm-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n MenuItem,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n BOARD_CLOSED_LIMIT,\n BOARD_OPEN_LIMIT,\n useDealsByStatus,\n usePagedDeals,\n} from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport { type DealCsvOptions, dealsCsv } from '../model/deals-csv'\nimport {\n boardSummary,\n DEAL_STATUS_LABELS,\n type DealDoc,\n formatAmountByCurrency,\n formatMoney,\n} from '../model/deal-board-model'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { DealBoard } from './deal-board'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { DealImportButton } from './deal-import-drawer'\nimport DealsBulkBar from './deals-bulk-bar'\nimport { LostReasonDialog } from './lost-reason-dialog'\nimport { OwnerAvatar } from './owner-avatar'\nimport { PipelinesDialog } from './pipelines-dialog'\n\ntype View = 'board' | 'table'\ntype StatusFilter = CrmDealStatus | 'all'\n\n/** What the deals table's panel offers: the served status, and \"No next activity\". */\nconst DEAL_GRID_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'status', kind: 'exact', path: 'status', operators: ['equals'] },\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n]\nconst DEAL_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n}\nconst DEAL_FILTER_OPTIONS = {\n status: (['open', 'won', 'lost'] as const).map((value) => ({\n value,\n label: DEAL_STATUS_LABELS[value],\n })),\n}\n\n\n/** What a pipeline made at the organization level is stamped with — the org's own token. */\nconst ORG_PIPELINE_TOKENS: readonly string[] = [ORG_SCOPE_TOKEN]\n\n/**\n * `/crm/deals` — the pipeline (AGL-2598).\n *\n * ## Two views of one pipeline\n *\n * The BOARD is one pipeline's open deals, one column per open stage, read\n * in one bounded listener over `(visibleTo, pipelineId, status, updatedAt)`\n * and sorted into columns here — a listener per column would be five\n * subscriptions for one board. The TABLE is the same pipeline's deals,\n * paged by the query, with a status toggle; it is where the closed history\n * is found. The three figures above both come from the board's read, so\n * they describe what is in play whichever view is showing.\n *\n * ## Which pipeline\n *\n * The switcher in the header (AGL-2620) picks among the ACTIVE pipelines\n * and opens on the default; the board, the table, the figures and the New\n * deal drawer all follow it. It is section state rather than a route: a\n * pipeline is a lens on the section, and a saved view that names one\n * composes with it. **Pipelines** opens the dialog that creates, renames,\n * defaults, archives and — through it — edits the stages of each.\n *\n * ## Every move goes through the route\n *\n * A drag, a card's \"Move to\", \"Mark won\" and \"Mark lost\" all end in\n * `useDealStageApi`, never a Firestore write: the stage is the fact the\n * automations listen for, and only the server emits the event. The section\n * marks the card pending while the request runs and lets the listener land\n * it — see `DealBoard` for why a move is not applied optimistically.\n *\n * ## Creation is a button\n *\n * \"New deal\" opens a drawer; there is no form above the list. The drawer\n * writes client-direct against the rules, stamped with this console's scope.\n *\n * ## The table selects and exports (AGL-2621)\n *\n * The table's rows are selectable, and a selection raises `DealsBulkBar`\n * over it — whose stage moves go through the same `useDealStageApi` as a\n * drag. Export CSV beside the status toggle writes the page through\n * `dealsCsv()`, naming the pipeline, the stage and the owner; the bar\n * writes the same file over the selection.\n */\nexport function DealsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath ?? '')\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const { activePipelines } = pipelineState\n const [pipelineId, setPipelineId] = useState<string | null>(null)\n // The chosen pipeline while it is still active; else the default. A\n // pipeline archived from another tab falls back rather than leaving the\n // board on a pipeline the picker no longer lists.\n const pipeline =\n (pipelineId ? activePipelines.find((entry) => entry.$id === pipelineId) : null) ??\n pipelineState.pipeline\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n // The org's deal fields, for the table's optional columns (AGL-2661).\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n\n const [view, setView] = useState<View>('board')\n const [closedExpanded, setClosedExpanded] = useState(false)\n /*\n * The table's status filter is the saved VIEW'S (AGL-2617): a view of\n * deals is a view of the table, and it holds the status beside the\n * columns and the sort. Opening one shows the table, because the board\n * has no columns to arrange and answers to the pipeline's stages alone.\n */\n const views = useCrmSavedView({\n section: 'deals',\n hostId,\n org,\n basePath: basePath ?? '',\n })\n const statusFilter: StatusFilter = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'status')?.value\n return value === 'open' || value === 'won' || value === 'lost' ? value : 'all'\n }, [views.state.filters])\n // The status is the query's clause; the \"No next activity\" clause beside\n // it (AGL-2661) narrows the loaded page and survives a status change.\n const viewFilters = views.state.filters\n useEffect(() => {\n if (views.currentId) setView('table')\n }, [views.currentId])\n\n // The board's read stays on in the table view too: it is what the summary\n // above both views is computed from, and it is already bounded.\n const open = useDealsByStatus(\n scope.orgId,\n scope.visibleTo,\n pipeline?.$id ?? null,\n 'open',\n BOARD_OPEN_LIMIT,\n )\n const closedPipelineId =\n view === 'board' && closedExpanded ? (pipeline?.$id ?? null) : null\n const won = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'won', BOARD_CLOSED_LIMIT)\n const lost = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'lost', BOARD_CLOSED_LIMIT)\n const paged = usePagedDeals(\n view === 'table' ? scope.orgId : null,\n scope.visibleTo,\n statusFilter,\n pipeline?.$id ?? null,\n )\n\n const summary = useMemo(() => boardSummary(open.data, pipeline), [open.data, pipeline])\n\n /*\n * MOVES IN FLIGHT. A card in this set is dimmed and cannot be dragged\n * again until the route answers; the listener is what moves it.\n */\n const [moving, setMoving] = useState<ReadonlySet<string>>(() => new Set())\n const track = useCallback(\n async (deal: DealDoc, request: () => Promise<unknown>, done: string) => {\n setMoving((current) => new Set(current).add(deal.$id))\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setMoving((current) => {\n const next = new Set(current)\n next.delete(deal.$id)\n return next\n })\n }\n },\n [enqueueSnackbar],\n )\n const handleMove = useCallback(\n (deal: DealDoc, stageId: string) => {\n const stage = dealStageById(pipeline, stageId)\n void track(\n deal,\n () => api.moveToStage(deal, stageId),\n `Moved to ${stage?.name ?? 'stage'}`,\n )\n },\n [api, pipeline, track],\n )\n const handleWon = useCallback(\n (deal: DealDoc) => void track(deal, () => api.markWon(deal), 'Deal won'),\n [api, track],\n )\n const [losing, setLosing] = useState<DealDoc | null>(null)\n const handleLost = useCallback((deal: DealDoc) => setLosing(deal), [])\n const confirmLost = useCallback(\n (reason: string) => {\n const deal = losing\n if (!deal) return\n setLosing(null)\n void track(deal, () => api.markLost(deal, reason), 'Deal marked lost')\n },\n [api, losing, track],\n )\n\n const openDeal = useCallback(\n (deal: DealDoc) => router.push(routes.deal(deal.$id)),\n [router, routes],\n )\n\n const [creating, setCreating] = useState(false)\n const [managingPipelines, setManagingPipelines] = useState(false)\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A page or a status is a different set of rows; a selection made on\n // the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [statusFilter, paged.page])\n const csvOptions: DealCsvOptions = useMemo(\n () => ({\n pipelineName: (id) => pipelineState.pipelineById(id)?.name,\n stageName: (pipelineId, stageId) =>\n dealStageById(pipelineState.pipelineById(pipelineId), stageId)?.name,\n ownerEmail: (uid) => {\n const member = findOrgMember(roster.members, uid)\n return member?.email || member?.label || uid\n },\n }),\n [pipelineState, roster.members],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('deals.csv', 'text/csv', dealsCsv(paged.rows, csvOptions))\n }, [paged.rows, csvOptions])\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'title',\n headerName: 'Deal',\n flex: 1.6,\n minWidth: 220,\n renderCell: ({ row }: { row: DealDoc }) => {\n const withWhom = [row.contactName, row.companyName].filter(Boolean).join(' · ')\n return (\n <Stack sx={{ justifyContent: 'center', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.title || 'Untitled deal'}\n </Typography>\n {withWhom ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap sx={{ lineHeight: 1.25 }}>\n {withWhom}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n {\n field: 'stageId',\n headerName: 'Stage',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) =>\n dealStageById(pipelineState.pipelineById(row.pipelineId), row.stageId)?.name ??\n row.stageId,\n },\n {\n field: 'amountCents',\n headerName: 'Amount',\n flex: 0.8,\n minWidth: 120,\n align: 'right',\n headerAlign: 'right',\n valueGetter: (_value: unknown, row: DealDoc) => row.amountCents ?? null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.amountCents === 'number' ? formatMoney(row.amountCents, row.currency) : '—',\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) => roster.nameOf(row.ownerUid),\n renderCell: ({ row }: { row: DealDoc }) => {\n const label = roster.nameOf(row.ownerUid)\n return label ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <OwnerAvatar label={label} size={22} />\n <Typography variant=\"body2\" noWrap>\n {label}\n </Typography>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'expectedCloseAtMs',\n headerName: 'Expected close',\n flex: 0.8,\n minWidth: 130,\n type: 'date',\n valueGetter: (_value: unknown, row: DealDoc) =>\n typeof row.expectedCloseAtMs === 'number' ? new Date(row.expectedCloseAtMs) : null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.expectedCloseAtMs === 'number'\n ? new Date(row.expectedCloseAtMs).toLocaleDateString()\n : '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.6,\n minWidth: 100,\n renderCell: ({ row }: { row: DealDoc }) => (\n <Chip\n size=\"small\"\n label={DEAL_STATUS_LABELS[row.status] ?? row.status}\n color={row.status === 'won' ? 'success' : row.status === 'lost' ? 'default' : 'primary'}\n variant={row.status === 'open' ? 'outlined' : 'filled'}\n />\n ),\n },\n // When the earliest open task against the deal is due (AGL-2661).\n nextActivityColumn(nowMs),\n // The org's deal fields as optional columns (AGL-2661).\n ...customFieldColumns(dealFields.active),\n ],\n [pipelineState, roster, dealFields.active, nowMs],\n )\n const tableRows = useMemo(\n () => filterByNextActivity(paged.rows, viewFilters),\n [paged.rows, viewFilters],\n )\n /*\n * The table's column and sort models are the view's (AGL-2617); its\n * filters are the grid's own panel over the view's clauses (AGL-3313).\n * The query serves the status, so the panel's clause replaces it, and\n * \"No next activity\" stands beside it over the loaded page.\n */\n const filterColumns = useMemo(\n () => crmFilterColumns(columns, DEAL_GRID_FILTER_FIELDS, DEAL_FILTER_OPTIONS, DEAL_GRID_FILTER_HEADERS),\n [columns],\n )\n const grid = useCrmViewGrid(views, filterColumns)\n const gridFilter = useCrmGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['status'],\n single: true,\n keepAlongside: isNoNextActivityClause,\n })\n\n const noOrg = scope.ready && !scope.orgId\n const pipelineLoading =\n !noOrg && (pipelineState.status === 'loading' || pipelineState.seeding || (!pipeline && pipelineState.status !== 'error'))\n\n return (\n <>\n <CardDisplay\n header={'Deals'}\n help={pluginDocsHelp('deals', { anchor: '#the-board-and-the-table' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <Button\n size=\"small\"\n startIcon={<MdiIcon path={mdiCogOutline.path} size={0.8} />}\n disabled={!scope.orgId || pipelineState.status === 'loading'}\n onClick={() => setManagingPipelines(true)}\n >\n {'Pipelines'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={3}\n sx={{ alignItems: 'flex-end', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Figure label=\"Open deals\" value={summary.openCount.toLocaleString()} />\n <Figure label=\"Pipeline value\" value={formatAmountByCurrency(summary.valueByCurrency)} />\n <Figure label=\"Weighted value\" value={formatAmountByCurrency(summary.weightedByCurrency)} />\n <Stack sx={{ flex: 1 }} />\n {/* Which pipeline the board and table show, above them rather than under (AGL-3311). */}\n {activePipelines.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Pipeline\"\n value={pipeline?.$id ?? ''}\n onChange={(event) => setPipelineId(event.target.value)}\n sx={{ minWidth: 160 }}\n slotProps={{ select: { 'aria-label': 'Pipeline' } }}\n >\n {activePipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={view}\n onChange={(_event, next) => {\n if (next) setView(next as View)\n }}\n aria-label=\"View\"\n >\n <ToggleButton value=\"board\" aria-label=\"Board\">\n <MdiIcon path={mdiViewColumnOutline.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Board'}\n </Typography>\n </ToggleButton>\n <ToggleButton value=\"table\" aria-label=\"Table\">\n <MdiIcon path={mdiTableLarge.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Table'}\n </Typography>\n </ToggleButton>\n </ToggleButtonGroup>\n </Stack>\n\n {noOrg ? (\n <Alert severity=\"info\">\n {'This site has no organization, so it has no pipeline.'}\n </Alert>\n ) : pipelineState.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The pipeline could not be read. Deals need the \"Manage data\" permission.'}\n </Alert>\n ) : pipelineLoading ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {pipelineState.seeding ? 'Setting up your Sales pipeline…' : 'Loading the pipeline…'}\n </Typography>\n </Stack>\n ) : view === 'board' && pipeline ? (\n <>\n {open.status === 'success' && open.data.length === 0 ? (\n <EmptyStateComponent\n compact\n label={'No open deals yet'}\n description={'A deal is a sale in progress, moved across the stages below as it advances.'}\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n }\n />\n ) : null}\n <DealBoard\n pipeline={pipeline}\n deals={open.data}\n won={closedExpanded && won.status !== 'loading' ? won.data : null}\n lost={closedExpanded && lost.status !== 'loading' ? lost.data : null}\n closedExpanded={closedExpanded}\n onToggleClosed={() => setClosedExpanded((current) => !current)}\n labelFor={roster.nameOf}\n moving={moving}\n onOpen={openDeal}\n onMove={handleMove}\n onWon={handleWon}\n onLost={handleLost}\n nowMs={nowMs}\n />\n </>\n ) : (\n <Stack spacing={1.5}>\n {/*\n The view this table is showing, and the clauses narrowing it\n as chips (AGL-2617, AGL-3313). Status and \"No next activity\"\n are set in the grid's own Filters panel.\n */}\n <CrmListToolbar label=\"Deal filters\">\n <CrmViewsControl controller={views} allLabel=\"All deals\" />\n <CrmFilterBar\n fields={DEAL_GRID_FILTER_FIELDS}\n headers={DEAL_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={DEAL_FILTER_OPTIONS}\n servedField=\"status\"\n />\n <Stack sx={{ flex: 1 }} />\n <DealImportButton hostId={hostId} />\n <Button size=\"small\" onClick={handleExport} disabled={!paged.rows.length}>\n {'Export CSV'}\n </Button>\n </CrmListToolbar>\n <>\n <DealsBulkBar\n hostId={hostId}\n scope={scope.scope}\n rows={tableRows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n pipelineById={pipelineState.pipelineById}\n roster={roster}\n api={api}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={tableRows}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n onOpen={(_id, row) => openDeal(row as DealDoc)}\n // The panel's status is the query's; \"No next activity\"\n // narrows the loaded page (AGL-3313). No quick search:\n // nothing indexes a deal's name, and a search over one\n // page answers \"no match\" for a deal on the next.\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter={false}\n // An empty table is said inside the grid, so its toolbar\n // stays to change the status that emptied it.\n noRowsLabel={statusFilter === 'all' ? 'No deals yet' : `No ${statusFilter} deals`}\n noRowsDescription={\n statusFilter === 'all'\n ? 'A deal is a sale in progress, moved across the pipeline as it advances.'\n : undefined\n }\n noRowsAction={\n statusFilter === 'all' ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n ) : undefined\n }\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </>\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n <DealEditDrawer\n open={creating}\n onClose={() => setCreating(false)}\n hostId={hostId}\n org={org}\n pipelines={activePipelines}\n defaultPipeline={pipeline}\n />\n <PipelinesDialog\n open={managingPipelines}\n onClose={() => setManagingPipelines(false)}\n orgId={scope.orgId ?? ''}\n // A pipeline made here is stamped the way the seeded default is\n // (`usePipeline`): the site's own tokens under a site; at the\n // organization level the org token, with the picked site as its\n // provenance (AGL-2630).\n hostId={scope.createHostId}\n pipelines={pipelineState.pipelines}\n fromCache={pipelineState.fromCache}\n unreadable={pipelineState.status === 'error'}\n visibleToTokens={scope.visibleTo}\n createTokens={scope.level === 'site' ? scope.createTokens : ORG_PIPELINE_TOKENS}\n />\n <LostReasonDialog\n open={Boolean(losing)}\n dealTitle={losing?.title ?? ''}\n onClose={() => setLosing(null)}\n onConfirm={confirmLost}\n />\n </>\n )\n}\nDealsSection.displayName = 'DealsSection'\n\nfunction Figure(props: { label: string; value: string }) {\n return (\n <Stack sx={{ lineHeight: 1.2 }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"h6\" component=\"span\">\n {props.value}\n </Typography>\n </Stack>\n )\n}\nFigure.displayName = 'Figure'\n\nexport default DealsSection\n"],"names":["dealStageById","filterByNextActivity","findOrgMember","isNoNextActivityClause","ORG_SCOPE_TOKEN","pluginDocsHelp","mdiCogOutline","mdiPlus","mdiTableLarge","mdiViewColumnOutline","CardDisplay","MdiIcon","ListPagination","ListTable","useCrmSavedView","useCrmViewGrid","CrmListActions","CrmListToolbar","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","CrmFilterBar","crmFilterColumns","useCrmGridFilter","CrmViewsControl","EmptyStateComponent","useSnackbar","Alert","Button","Chip","CircularProgress","MenuItem","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useRouter","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmScope","useDealStageApi","BOARD_CLOSED_LIMIT","BOARD_OPEN_LIMIT","useDealsByStatus","usePagedDeals","useOrgMemberDirectory","usePipeline","downloadTextFile","crmRoutes","dealsCsv","boardSummary","DEAL_STATUS_LABELS","formatAmountByCurrency","formatMoney","customFieldColumns","DealBoard","DealEditDrawer","DealImportButton","DealsBulkBar","LostReasonDialog","OwnerAvatar","PipelinesDialog","DEAL_GRID_FILTER_FIELDS","column","kind","path","operators","DEAL_GRID_FILTER_HEADERS","status","DEAL_FILTER_OPTIONS","map","value","label","ORG_PIPELINE_TOKENS","DealsSection","props","pipelineId","scope","hostId","org","basePath","routes","router","enqueueSnackbar","pipelineState","orgId","activePipelines","setPipelineId","pipeline","find","entry","$id","roster","api","nowMs","Date","now","dealFields","view","setView","closedExpanded","setClosedExpanded","views","section","statusFilter","state","filters","clause","field","viewFilters","currentId","open","visibleTo","closedPipelineId","won","lost","paged","summary","data","moving","setMoving","Set","track","deal","request","done","current","add","variant","persist","error","Error","message","allowDuplicate","next","delete","handleMove","stageId","stage","moveToStage","name","handleWon","markWon","losing","setLosing","handleLost","confirmLost","reason","markLost","openDeal","push","creating","setCreating","managingPipelines","setManagingPipelines","selectedIds","setSelectedIds","page","csvOptions","pipelineName","id","pipelineById","stageName","ownerEmail","uid","member","members","email","handleExport","rows","columns","headerName","flex","minWidth","renderCell","row","withWhom","contactName","companyName","filter","Boolean","join","sx","justifyContent","lineHeight","title","color","noWrap","valueGetter","_value","align","headerAlign","amountCents","currency","nameOf","ownerUid","direction","spacing","alignItems","size","type","expectedCloseAtMs","toLocaleDateString","active","tableRows","filterColumns","grid","gridFilter","clauses","onChange","setFilters","selectFields","single","keepAlongside","noOrg","ready","pipelineLoading","seeding","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","startIcon","disabled","onClick","flexWrap","rowGap","Figure","openCount","toLocaleString","valueByCurrency","weightedByCurrency","length","select","event","target","slotProps","exclusive","_event","aria-label","ml","severity","compact","description","deals","onToggleClosed","labelFor","onOpen","onMove","onWon","onLost","controller","allLabel","fields","headers","options","servedField","selected","onSelectedChange","csv","columnOrder","slots","selectable","_id","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","noRowsDescription","undefined","noRowsAction","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","onClose","pipelines","defaultPipeline","createHostId","fromCache","unreadable","visibleToTokens","createTokens","level","dealTitle","onConfirm","displayName","component"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deals-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n type CrmDealStatus,\n dealStageById,\n filterByNextActivity,\n findOrgMember,\n isNoNextActivityClause,\n ORG_SCOPE_TOKEN,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n mdiCogOutline,\n mdiPlus,\n mdiTableLarge,\n mdiViewColumnOutline,\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 type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport CrmFilterBar from './crm-filter-bar'\nimport { crmFilterColumns, crmRowMatchesSearch } from '../model/crm-grid-filter'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useCrmGridFilter } from '../hooks/use-crm-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n MenuItem,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n BOARD_CLOSED_LIMIT,\n BOARD_OPEN_LIMIT,\n DEAL_SEARCH_WINDOW,\n useDealSearchWindow,\n useDealsByStatus,\n usePagedDeals,\n} from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport { type DealCsvOptions, dealsCsv } from '../model/deals-csv'\nimport {\n boardSummary,\n DEAL_STATUS_LABELS,\n type DealDoc,\n formatAmountByCurrency,\n formatMoney,\n} from '../model/deal-board-model'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { DealBoard } from './deal-board'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { DealImportButton } from './deal-import-drawer'\nimport DealsBulkBar from './deals-bulk-bar'\nimport { LostReasonDialog } from './lost-reason-dialog'\nimport { OwnerAvatar } from './owner-avatar'\nimport { PipelinesDialog } from './pipelines-dialog'\n\ntype View = 'board' | 'table'\ntype StatusFilter = CrmDealStatus | 'all'\n\n/** What the deals table's panel offers: the served status, and \"No next activity\". */\nconst DEAL_GRID_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'status', kind: 'exact', path: 'status', operators: ['equals'] },\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n]\nconst DEAL_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n}\n/** What the table's search reads on a deal. */\nconst DEAL_SEARCH_FIELDS = ['title'] as const\nconst DEAL_FILTER_OPTIONS = {\n status: (['open', 'won', 'lost'] as const).map((value) => ({\n value,\n label: DEAL_STATUS_LABELS[value],\n })),\n}\n\n\n/** What a pipeline made at the organization level is stamped with — the org's own token. */\nconst ORG_PIPELINE_TOKENS: readonly string[] = [ORG_SCOPE_TOKEN]\n\n/**\n * `/crm/deals` — the pipeline (AGL-2598).\n *\n * ## Two views of one pipeline\n *\n * The BOARD is one pipeline's open deals, one column per open stage, read\n * in one bounded listener over `(visibleTo, pipelineId, status, updatedAt)`\n * and sorted into columns here — a listener per column would be five\n * subscriptions for one board. The TABLE is the same pipeline's deals,\n * paged by the query, with a status toggle; it is where the closed history\n * is found. The three figures above both come from the board's read, so\n * they describe what is in play whichever view is showing.\n *\n * ## Which pipeline\n *\n * The switcher in the header (AGL-2620) picks among the ACTIVE pipelines\n * and opens on the default; the board, the table, the figures and the New\n * deal drawer all follow it. It is section state rather than a route: a\n * pipeline is a lens on the section, and a saved view that names one\n * composes with it. **Pipelines** opens the dialog that creates, renames,\n * defaults, archives and — through it — edits the stages of each.\n *\n * ## Every move goes through the route\n *\n * A drag, a card's \"Move to\", \"Mark won\" and \"Mark lost\" all end in\n * `useDealStageApi`, never a Firestore write: the stage is the fact the\n * automations listen for, and only the server emits the event. The section\n * marks the card pending while the request runs and lets the listener land\n * it — see `DealBoard` for why a move is not applied optimistically.\n *\n * ## Creation is a button\n *\n * \"New deal\" opens a drawer; there is no form above the list. The drawer\n * writes client-direct against the rules, stamped with this console's scope.\n *\n * ## The table selects and exports (AGL-2621)\n *\n * The table's rows are selectable, and a selection raises `DealsBulkBar`\n * over it — whose stage moves go through the same `useDealStageApi` as a\n * drag. Export CSV beside the status toggle writes the page through\n * `dealsCsv()`, naming the pipeline, the stage and the owner; the bar\n * writes the same file over the selection.\n */\nexport function DealsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath ?? '')\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const { activePipelines } = pipelineState\n const [pipelineId, setPipelineId] = useState<string | null>(null)\n // The chosen pipeline while it is still active; else the default. A\n // pipeline archived from another tab falls back rather than leaving the\n // board on a pipeline the picker no longer lists.\n const pipeline =\n (pipelineId ? activePipelines.find((entry) => entry.$id === pipelineId) : null) ??\n pipelineState.pipeline\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n // The org's deal fields, for the table's optional columns (AGL-2661).\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n\n const [view, setView] = useState<View>('board')\n const [closedExpanded, setClosedExpanded] = useState(false)\n /*\n * The table's status filter is the saved VIEW'S (AGL-2617): a view of\n * deals is a view of the table, and it holds the status beside the\n * columns and the sort. Opening one shows the table, because the board\n * has no columns to arrange and answers to the pipeline's stages alone.\n */\n const views = useCrmSavedView({\n section: 'deals',\n hostId,\n org,\n basePath: basePath ?? '',\n })\n const statusFilter: StatusFilter = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'status')?.value\n return value === 'open' || value === 'won' || value === 'lost' ? value : 'all'\n }, [views.state.filters])\n // The status is the query's clause; the \"No next activity\" clause beside\n // it (AGL-2661) narrows the loaded page and survives a status change.\n const viewFilters = views.state.filters\n useEffect(() => {\n if (views.currentId) setView('table')\n }, [views.currentId])\n\n // The board's read stays on in the table view too: it is what the summary\n // above both views is computed from, and it is already bounded.\n const open = useDealsByStatus(\n scope.orgId,\n scope.visibleTo,\n pipeline?.$id ?? null,\n 'open',\n BOARD_OPEN_LIMIT,\n )\n const closedPipelineId =\n view === 'board' && closedExpanded ? (pipeline?.$id ?? null) : null\n const won = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'won', BOARD_CLOSED_LIMIT)\n const lost = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'lost', BOARD_CLOSED_LIMIT)\n const paged = usePagedDeals(\n view === 'table' ? scope.orgId : null,\n scope.visibleTo,\n statusFilter,\n pipeline?.$id ?? null,\n )\n\n /*\n * THE SEARCH (AGL-3315): the grid's quick-filter box, answered over a\n * window of every deal the table's query would page through — read only\n * while the box holds a word — so a deal on page four is found by any\n * word of its title. See `useDealSearchWindow`.\n */\n const [searchWords, setSearchWords] = useState<string[]>([])\n const searching = searchWords.some((word) => word.trim())\n const searchWindow = useDealSearchWindow(\n view === 'table' && searching ? scope.orgId : null,\n scope.visibleTo,\n statusFilter,\n pipeline?.$id ?? null,\n )\n const searchKey = searchWords.join(' ')\n const found = useMemo(\n () =>\n searchWindow.data\n .slice(0, DEAL_SEARCH_WINDOW)\n .filter((deal) => crmRowMatchesSearch(deal, DEAL_SEARCH_FIELDS, searchWords)),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [searchWindow.data, searchKey],\n )\n const searchTruncated = searchWindow.data.length > DEAL_SEARCH_WINDOW\n const [searchPage, setSearchPage] = useState(0)\n const [searchPageSize, setSearchPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n useEffect(() => setSearchPage(0), [searchKey, statusFilter])\n\n const summary = useMemo(() => boardSummary(open.data, pipeline), [open.data, pipeline])\n\n /*\n * MOVES IN FLIGHT. A card in this set is dimmed and cannot be dragged\n * again until the route answers; the listener is what moves it.\n */\n const [moving, setMoving] = useState<ReadonlySet<string>>(() => new Set())\n const track = useCallback(\n async (deal: DealDoc, request: () => Promise<unknown>, done: string) => {\n setMoving((current) => new Set(current).add(deal.$id))\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setMoving((current) => {\n const next = new Set(current)\n next.delete(deal.$id)\n return next\n })\n }\n },\n [enqueueSnackbar],\n )\n const handleMove = useCallback(\n (deal: DealDoc, stageId: string) => {\n const stage = dealStageById(pipeline, stageId)\n void track(\n deal,\n () => api.moveToStage(deal, stageId),\n `Moved to ${stage?.name ?? 'stage'}`,\n )\n },\n [api, pipeline, track],\n )\n const handleWon = useCallback(\n (deal: DealDoc) => void track(deal, () => api.markWon(deal), 'Deal won'),\n [api, track],\n )\n const [losing, setLosing] = useState<DealDoc | null>(null)\n const handleLost = useCallback((deal: DealDoc) => setLosing(deal), [])\n const confirmLost = useCallback(\n (reason: string) => {\n const deal = losing\n if (!deal) return\n setLosing(null)\n void track(deal, () => api.markLost(deal, reason), 'Deal marked lost')\n },\n [api, losing, track],\n )\n\n const openDeal = useCallback(\n (deal: DealDoc) => router.push(routes.deal(deal.$id)),\n [router, routes],\n )\n\n const [creating, setCreating] = useState(false)\n const [managingPipelines, setManagingPipelines] = useState(false)\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A page or a status is a different set of rows; a selection made on\n // the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [statusFilter, paged.page])\n const csvOptions: DealCsvOptions = useMemo(\n () => ({\n pipelineName: (id) => pipelineState.pipelineById(id)?.name,\n stageName: (pipelineId, stageId) =>\n dealStageById(pipelineState.pipelineById(pipelineId), stageId)?.name,\n ownerEmail: (uid) => {\n const member = findOrgMember(roster.members, uid)\n return member?.email || member?.label || uid\n },\n }),\n [pipelineState, roster.members],\n )\n // The page on screen — or, while searching, every deal the search found.\n const handleExport = useCallback(() => {\n downloadTextFile('deals.csv', 'text/csv', dealsCsv(searching ? found : paged.rows, csvOptions))\n }, [searching, found, paged.rows, csvOptions])\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'title',\n headerName: 'Deal',\n flex: 1.6,\n minWidth: 220,\n renderCell: ({ row }: { row: DealDoc }) => {\n const withWhom = [row.contactName, row.companyName].filter(Boolean).join(' · ')\n return (\n <Stack sx={{ justifyContent: 'center', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.title || 'Untitled deal'}\n </Typography>\n {withWhom ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap sx={{ lineHeight: 1.25 }}>\n {withWhom}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n {\n field: 'stageId',\n headerName: 'Stage',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) =>\n dealStageById(pipelineState.pipelineById(row.pipelineId), row.stageId)?.name ??\n row.stageId,\n },\n {\n field: 'amountCents',\n headerName: 'Amount',\n flex: 0.8,\n minWidth: 120,\n align: 'right',\n headerAlign: 'right',\n valueGetter: (_value: unknown, row: DealDoc) => row.amountCents ?? null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.amountCents === 'number' ? formatMoney(row.amountCents, row.currency) : '—',\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) => roster.nameOf(row.ownerUid),\n renderCell: ({ row }: { row: DealDoc }) => {\n const label = roster.nameOf(row.ownerUid)\n return label ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <OwnerAvatar label={label} size={22} />\n <Typography variant=\"body2\" noWrap>\n {label}\n </Typography>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'expectedCloseAtMs',\n headerName: 'Expected close',\n flex: 0.8,\n minWidth: 130,\n type: 'date',\n valueGetter: (_value: unknown, row: DealDoc) =>\n typeof row.expectedCloseAtMs === 'number' ? new Date(row.expectedCloseAtMs) : null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.expectedCloseAtMs === 'number'\n ? new Date(row.expectedCloseAtMs).toLocaleDateString()\n : '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.6,\n minWidth: 100,\n renderCell: ({ row }: { row: DealDoc }) => (\n <Chip\n size=\"small\"\n label={DEAL_STATUS_LABELS[row.status] ?? row.status}\n color={row.status === 'won' ? 'success' : row.status === 'lost' ? 'default' : 'primary'}\n variant={row.status === 'open' ? 'outlined' : 'filled'}\n />\n ),\n },\n // When the earliest open task against the deal is due (AGL-2661).\n nextActivityColumn(nowMs),\n // The org's deal fields as optional columns (AGL-2661).\n ...customFieldColumns(dealFields.active),\n ],\n [pipelineState, roster, dealFields.active, nowMs],\n )\n /** The table's rows: the query's page, or the search's matches a page at a time. */\n const searchRows = useMemo(\n () => filterByNextActivity(found, viewFilters),\n [found, viewFilters],\n )\n const tableRows = useMemo(\n () =>\n searching\n ? searchRows.slice(searchPage * searchPageSize, (searchPage + 1) * searchPageSize)\n : filterByNextActivity(paged.rows, viewFilters),\n [searching, searchRows, searchPage, searchPageSize, paged.rows, viewFilters],\n )\n /*\n * The table's column and sort models are the view's (AGL-2617); its\n * filters are the grid's own panel over the view's clauses (AGL-3313).\n * The query serves the status, so the panel's clause replaces it, and\n * \"No next activity\" stands beside it over the loaded page.\n */\n const filterColumns = useMemo(\n () => crmFilterColumns(columns, DEAL_GRID_FILTER_FIELDS, DEAL_FILTER_OPTIONS, DEAL_GRID_FILTER_HEADERS),\n [columns],\n )\n const grid = useCrmViewGrid(views, filterColumns)\n const gridFilter = useCrmGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['status'],\n single: true,\n keepAlongside: isNoNextActivityClause,\n search: { words: searchWords, onChange: setSearchWords },\n })\n\n const noOrg = scope.ready && !scope.orgId\n const pipelineLoading =\n !noOrg && (pipelineState.status === 'loading' || pipelineState.seeding || (!pipeline && pipelineState.status !== 'error'))\n\n return (\n <>\n <CardDisplay\n header={'Deals'}\n help={pluginDocsHelp('deals', { anchor: '#the-board-and-the-table' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <Button\n size=\"small\"\n startIcon={<MdiIcon path={mdiCogOutline.path} size={0.8} />}\n disabled={!scope.orgId || pipelineState.status === 'loading'}\n onClick={() => setManagingPipelines(true)}\n >\n {'Pipelines'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={3}\n sx={{ alignItems: 'flex-end', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Figure label=\"Open deals\" value={summary.openCount.toLocaleString()} />\n <Figure label=\"Pipeline value\" value={formatAmountByCurrency(summary.valueByCurrency)} />\n <Figure label=\"Weighted value\" value={formatAmountByCurrency(summary.weightedByCurrency)} />\n <Stack sx={{ flex: 1 }} />\n {/* Which pipeline the board and table show, above them rather than under (AGL-3311). */}\n {activePipelines.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Pipeline\"\n value={pipeline?.$id ?? ''}\n onChange={(event) => setPipelineId(event.target.value)}\n sx={{ minWidth: 160 }}\n slotProps={{ select: { 'aria-label': 'Pipeline' } }}\n >\n {activePipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={view}\n onChange={(_event, next) => {\n if (next) setView(next as View)\n }}\n aria-label=\"View\"\n >\n <ToggleButton value=\"board\" aria-label=\"Board\">\n <MdiIcon path={mdiViewColumnOutline.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Board'}\n </Typography>\n </ToggleButton>\n <ToggleButton value=\"table\" aria-label=\"Table\">\n <MdiIcon path={mdiTableLarge.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Table'}\n </Typography>\n </ToggleButton>\n </ToggleButtonGroup>\n </Stack>\n\n {noOrg ? (\n <Alert severity=\"info\">\n {'This site has no organization, so it has no pipeline.'}\n </Alert>\n ) : pipelineState.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The pipeline could not be read. Deals need the \"Manage data\" permission.'}\n </Alert>\n ) : pipelineLoading ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {pipelineState.seeding ? 'Setting up your Sales pipeline…' : 'Loading the pipeline…'}\n </Typography>\n </Stack>\n ) : view === 'board' && pipeline ? (\n <>\n {open.status === 'success' && open.data.length === 0 ? (\n <EmptyStateComponent\n compact\n label={'No open deals yet'}\n description={'A deal is a sale in progress, moved across the stages below as it advances.'}\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n }\n />\n ) : null}\n <DealBoard\n pipeline={pipeline}\n deals={open.data}\n won={closedExpanded && won.status !== 'loading' ? won.data : null}\n lost={closedExpanded && lost.status !== 'loading' ? lost.data : null}\n closedExpanded={closedExpanded}\n onToggleClosed={() => setClosedExpanded((current) => !current)}\n labelFor={roster.nameOf}\n moving={moving}\n onOpen={openDeal}\n onMove={handleMove}\n onWon={handleWon}\n onLost={handleLost}\n nowMs={nowMs}\n />\n </>\n ) : (\n <Stack spacing={1.5}>\n {/*\n The view this table is showing, and the clauses narrowing it\n as chips (AGL-2617, AGL-3313). Status and \"No next activity\"\n are set in the grid's own Filters panel.\n */}\n <CrmListToolbar label=\"Deal filters\">\n <CrmViewsControl controller={views} allLabel=\"All deals\" />\n <CrmFilterBar\n fields={DEAL_GRID_FILTER_FIELDS}\n headers={DEAL_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={DEAL_FILTER_OPTIONS}\n servedField=\"status\"\n />\n <Stack sx={{ flex: 1 }} />\n <DealImportButton hostId={hostId} />\n <Button size=\"small\" onClick={handleExport} disabled={!(searching ? found : paged.rows).length}>\n {'Export CSV'}\n </Button>\n </CrmListToolbar>\n <>\n <DealsBulkBar\n hostId={hostId}\n scope={scope.scope}\n rows={tableRows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n pipelineById={pipelineState.pipelineById}\n roster={roster}\n api={api}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={tableRows}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n onOpen={(_id, row) => openDeal(row as DealDoc)}\n // The panel's status is the query's; \"No next activity\"\n // narrows the loaded rows (AGL-3313); the search runs\n // over every deal in the window (AGL-3315).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n loading={searching && searchWindow.status === 'loading'}\n // An empty table is said inside the grid, so its toolbar\n // stays to change the status that emptied it.\n noRowsLabel={\n searching\n ? `No deals match “${searchKey.trim()}”`\n : statusFilter === 'all'\n ? 'No deals yet'\n : `No ${statusFilter} deals`\n }\n noRowsDescription={\n statusFilter === 'all' && !searching\n ? 'A deal is a sale in progress, moved across the pipeline as it advances.'\n : undefined\n }\n noRowsAction={\n statusFilter === 'all' && !searching ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n ) : undefined\n }\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n hideFooter\n />\n </CrmColumnOrderProvider>\n {searching ? (\n <ListPagination\n page={searchPage}\n pageSize={searchPageSize}\n rowCount={tableRows.length}\n count={searchRows.length}\n onPageChange={setSearchPage}\n onPageSizeChange={setSearchPageSize}\n />\n ) : (\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n )}\n {searching && searchTruncated ? (\n <Alert severity=\"info\">\n {`The search reads the ${DEAL_SEARCH_WINDOW.toLocaleString()} most recently ` +\n 'changed deals; narrow by status to reach older ones.'}\n </Alert>\n ) : null}\n </>\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n <DealEditDrawer\n open={creating}\n onClose={() => setCreating(false)}\n hostId={hostId}\n org={org}\n pipelines={activePipelines}\n defaultPipeline={pipeline}\n />\n <PipelinesDialog\n open={managingPipelines}\n onClose={() => setManagingPipelines(false)}\n orgId={scope.orgId ?? ''}\n // A pipeline made here is stamped the way the seeded default is\n // (`usePipeline`): the site's own tokens under a site; at the\n // organization level the org token, with the picked site as its\n // provenance (AGL-2630).\n hostId={scope.createHostId}\n pipelines={pipelineState.pipelines}\n fromCache={pipelineState.fromCache}\n unreadable={pipelineState.status === 'error'}\n visibleToTokens={scope.visibleTo}\n createTokens={scope.level === 'site' ? scope.createTokens : ORG_PIPELINE_TOKENS}\n />\n <LostReasonDialog\n open={Boolean(losing)}\n dealTitle={losing?.title ?? ''}\n onClose={() => setLosing(null)}\n onConfirm={confirmLost}\n />\n </>\n )\n}\nDealsSection.displayName = 'DealsSection'\n\nfunction Figure(props: { label: string; value: string }) {\n return (\n <Stack sx={{ lineHeight: 1.2 }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"h6\" component=\"span\">\n {props.value}\n </Typography>\n </Stack>\n )\n}\nFigure.displayName = 'Figure'\n\nexport default DealsSection\n"],"names":["dealStageById","filterByNextActivity","findOrgMember","isNoNextActivityClause","ORG_SCOPE_TOKEN","pluginDocsHelp","mdiCogOutline","mdiPlus","mdiTableLarge","mdiViewColumnOutline","CardDisplay","MdiIcon","ListPagination","ListTable","useCrmSavedView","useCrmViewGrid","CrmListActions","CrmListToolbar","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","CrmFilterBar","crmFilterColumns","crmRowMatchesSearch","TABLE_PAGE_SIZE_DEFAULT","useCrmGridFilter","CrmViewsControl","EmptyStateComponent","useSnackbar","Alert","Button","Chip","CircularProgress","MenuItem","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useRouter","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmScope","useDealStageApi","BOARD_CLOSED_LIMIT","BOARD_OPEN_LIMIT","DEAL_SEARCH_WINDOW","useDealSearchWindow","useDealsByStatus","usePagedDeals","useOrgMemberDirectory","usePipeline","downloadTextFile","crmRoutes","dealsCsv","boardSummary","DEAL_STATUS_LABELS","formatAmountByCurrency","formatMoney","customFieldColumns","DealBoard","DealEditDrawer","DealImportButton","DealsBulkBar","LostReasonDialog","OwnerAvatar","PipelinesDialog","DEAL_GRID_FILTER_FIELDS","column","kind","path","operators","DEAL_GRID_FILTER_HEADERS","status","DEAL_SEARCH_FIELDS","DEAL_FILTER_OPTIONS","map","value","label","ORG_PIPELINE_TOKENS","DealsSection","props","pipelineId","scope","hostId","org","basePath","routes","router","enqueueSnackbar","pipelineState","orgId","activePipelines","setPipelineId","pipeline","find","entry","$id","roster","api","nowMs","Date","now","dealFields","view","setView","closedExpanded","setClosedExpanded","views","section","statusFilter","state","filters","clause","field","viewFilters","currentId","open","visibleTo","closedPipelineId","won","lost","paged","searchWords","setSearchWords","searching","some","word","trim","searchWindow","searchKey","join","found","data","slice","filter","deal","searchTruncated","length","searchPage","setSearchPage","searchPageSize","setSearchPageSize","summary","moving","setMoving","Set","track","request","done","current","add","variant","persist","error","Error","message","allowDuplicate","next","delete","handleMove","stageId","stage","moveToStage","name","handleWon","markWon","losing","setLosing","handleLost","confirmLost","reason","markLost","openDeal","push","creating","setCreating","managingPipelines","setManagingPipelines","selectedIds","setSelectedIds","page","csvOptions","pipelineName","id","pipelineById","stageName","ownerEmail","uid","member","members","email","handleExport","rows","columns","headerName","flex","minWidth","renderCell","row","withWhom","contactName","companyName","Boolean","sx","justifyContent","lineHeight","title","color","noWrap","valueGetter","_value","align","headerAlign","amountCents","currency","nameOf","ownerUid","direction","spacing","alignItems","size","type","expectedCloseAtMs","toLocaleDateString","active","searchRows","tableRows","filterColumns","grid","gridFilter","clauses","onChange","setFilters","selectFields","single","keepAlongside","search","words","noOrg","ready","pipelineLoading","seeding","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","startIcon","disabled","onClick","flexWrap","rowGap","Figure","openCount","toLocaleString","valueByCurrency","weightedByCurrency","select","event","target","slotProps","exclusive","_event","aria-label","ml","severity","compact","description","deals","onToggleClosed","labelFor","onOpen","onMove","onWon","onLost","controller","allLabel","fields","headers","options","servedField","selected","onSelectedChange","csv","columnOrder","slots","selectable","_id","filterMode","filterModel","onFilterModelChange","quickFilter","loading","noRowsLabel","noRowsDescription","undefined","noRowsAction","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","pageSize","rowCount","count","onPageChange","onPageSizeChange","hasMore","setPage","setPageSize","onClose","pipelines","defaultPipeline","createHostId","fromCache","unreadable","visibleToTokens","createTokens","level","dealTitle","onConfirm","displayName","component"],"mappings":"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"}
@@ -34,6 +34,27 @@ export declare function useDealsByStatus(orgId: string | null, readTokens: reado
34
34
  * the first shape orders on one field and needs none.
35
35
  */
36
36
  export declare function usePagedDeals(orgId: string | null, readTokens: readonly string[] | null, status: CrmDealStatus | 'all', pipelineId?: string | null): import("@aglyn/tenant-feature-instance").UsePagedCollectionResult<DealDoc>;
37
+ /**
38
+ * How many deals a table search reads (AGL-3315): the newest by last
39
+ * change, in the table's own scope, status and pipeline, plus one probe row
40
+ * so "there are more" is a fact rather than a guess at the boundary.
41
+ */
42
+ export declare const DEAL_SEARCH_WINDOW = 1000;
43
+ /**
44
+ * The deals a table SEARCH runs over (AGL-3315) — a window, read only while
45
+ * the search box holds something.
46
+ *
47
+ * A search has to find a deal on any page, and it has to find it by a word
48
+ * from the middle of its title: "Acme renewal" is looked for as "renewal".
49
+ * An indexed prefix, the way the companies list searches names, answers
50
+ * only the first word, and word tokens would need every writer and a
51
+ * backfill to agree on them. An organization's deals are counted in the
52
+ * tens or hundreds — the board already reads every open one — so the
53
+ * window is the whole collection for all but the largest, and the table
54
+ * says so when it is not. The query is the table's own, one of the four
55
+ * shapes `usePagedDeals` states, so it rides indexes that already exist.
56
+ */
57
+ export declare function useDealSearchWindow(orgId: string | null, readTokens: readonly string[] | null, status: CrmDealStatus | 'all', pipelineId?: string | null): import("@aglyn/tenant-feature-instance").UseFirestoreCollectionResult<DealDoc>;
37
58
  /**
38
59
  * The deals that name one contact or one company, for the card on that
39
60
  * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.
@@ -74,6 +74,40 @@ import { crmScopeListable, crmVisibleToClause } from "./use-crm-scope.js";
74
74
  idField: '$id'
75
75
  });
76
76
  }
77
+ /**
78
+ * How many deals a table search reads (AGL-3315): the newest by last
79
+ * change, in the table's own scope, status and pipeline, plus one probe row
80
+ * so "there are more" is a fact rather than a guess at the boundary.
81
+ */ export const DEAL_SEARCH_WINDOW = 1000;
82
+ /**
83
+ * The deals a table SEARCH runs over (AGL-3315) — a window, read only while
84
+ * the search box holds something.
85
+ *
86
+ * A search has to find a deal on any page, and it has to find it by a word
87
+ * from the middle of its title: "Acme renewal" is looked for as "renewal".
88
+ * An indexed prefix, the way the companies list searches names, answers
89
+ * only the first word, and word tokens would need every writer and a
90
+ * backfill to agree on them. An organization's deals are counted in the
91
+ * tens or hundreds — the board already reads every open one — so the
92
+ * window is the whole collection for all but the largest, and the table
93
+ * says so when it is not. The query is the table's own, one of the four
94
+ * shapes `usePagedDeals` states, so it rides indexes that already exist.
95
+ */ export function useDealSearchWindow(orgId, readTokens, status, pipelineId = null) {
96
+ const firestore = useFirestore();
97
+ return useFirestoreCollection(()=>orgId && crmScopeListable(readTokens) ? query(collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals), ...crmVisibleToClause(readTokens), ...pipelineId ? [
98
+ where('pipelineId', '==', pipelineId)
99
+ ] : [], ...status === 'all' ? [] : [
100
+ where('status', '==', status)
101
+ ], orderBy('updatedAt', 'desc'), limit(DEAL_SEARCH_WINDOW + 1)) : null, [
102
+ firestore,
103
+ orgId,
104
+ readTokens,
105
+ status,
106
+ pipelineId
107
+ ], {
108
+ idField: '$id'
109
+ });
110
+ }
77
111
  /**
78
112
  * The deals that name one contact or one company, for the card on that
79
113
  * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-deals.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, type CrmDealStatus } from '@aglyn/aglyn'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n collection,\n doc,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport type { DealDoc } from '../model/deal-board-model'\nimport { crmScopeListable, crmVisibleToClause } from './use-crm-scope'\n\n/**\n * The most open cards one pipeline draws. A board past this is a board\n * nobody reads column by column; the table is paged and reaches the rest.\n */\nexport const BOARD_OPEN_LIMIT = 500\n\n/** Closed deals shown per closed column when it is expanded. */\nexport const BOARD_CLOSED_LIMIT = 50\n\n/** Deals listed on a contact's or a company's page. */\nexport const LINKED_DEALS_LIMIT = 25\n\n/**\n * The deals of one status in one pipeline, newest activity first (AGL-2598).\n *\n * Every query here carries the viewer's scope tokens and an `orderBy`, and\n * each shape sits on an index the foundation declared:\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. Passing `null` for the\n * pipeline or an EMPTY token list issues nothing, which is how a column\n * stays quiet until the pipeline has loaded and a collapsed Won column\n * costs no read. A `null` token list is the ORGANIZATION level (AGL-2630):\n * the clause is dropped and the query rides the `(pipelineId, status,\n * updatedAt DESC)` twin of that index.\n */\nexport function useDealsByStatus(\n orgId: string | null,\n readTokens: readonly string[] | null,\n pipelineId: string | null,\n status: CrmDealStatus,\n max: number,\n) {\n const firestore = useFirestore()\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && pipelineId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where('pipelineId', '==', pipelineId),\n where('status', '==', status),\n orderBy('updatedAt', 'desc'),\n limit(max),\n )\n : null,\n [firestore, orgId, readTokens, pipelineId, status, max],\n { idField: '$id' },\n )\n}\n\n/**\n * Every deal the viewer may see, paged, optionally narrowed to one status\n * and to one pipeline — the table's read. Four shapes, four indexes:\n * `(visibleTo, updatedAt DESC)`, `(visibleTo, status, updatedAt DESC)`,\n * `(visibleTo, pipelineId, updatedAt DESC)` and\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. At the organization\n * level (AGL-2630) the scope clause is dropped and the same shapes ride\n * their twins without `visibleTo` — `(status, updatedAt DESC)`,\n * `(pipelineId, updatedAt DESC)`, `(pipelineId, status, updatedAt DESC)`;\n * the first shape orders on one field and needs none.\n */\nexport function usePagedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n status: CrmDealStatus | 'all',\n pipelineId: string | null = null,\n) {\n const firestore = useFirestore()\n return usePagedCollection<DealDoc>(\n (pageLimit) =>\n orgId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n ...(pipelineId ? [where('pipelineId', '==', pipelineId)] : []),\n ...(status === 'all' ? [] : [where('status', '==', status)]),\n orderBy('updatedAt', 'desc'),\n limit(pageLimit),\n )\n : null,\n [firestore, orgId, readTokens, status, pipelineId],\n { idField: '$id' },\n )\n}\n\n/**\n * The deals that name one contact or one company, for the card on that\n * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.\n */\nexport function useLinkedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n link: { contactId: string } | { companyId: string },\n) {\n const firestore = useFirestore()\n const field = 'contactId' in link ? 'contactId' : 'companyId'\n const id = 'contactId' in link ? link.contactId : link.companyId\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && id && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where(field, '==', id),\n orderBy('updatedAt', 'desc'),\n limit(LINKED_DEALS_LIMIT),\n )\n : null,\n [firestore, orgId, readTokens, field, id],\n { idField: '$id' },\n )\n}\n\n/** One deal, live. `undefined` while loading, and after a delete. */\nexport function useDeal(orgId: string | null, dealId: string) {\n const firestore = useFirestore()\n return useFirestoreDoc<DealDoc>(\n () =>\n orgId && dealId\n ? doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals, dealId)\n : null,\n [firestore, orgId, dealId],\n { idField: '$id' },\n )\n}\n"],"names":["CRM_COLLECTIONS","useFirestore","useFirestoreCollection","useFirestoreDoc","usePagedCollection","collection","doc","limit","orderBy","query","where","crmScopeListable","crmVisibleToClause","BOARD_OPEN_LIMIT","BOARD_CLOSED_LIMIT","LINKED_DEALS_LIMIT","useDealsByStatus","orgId","readTokens","pipelineId","status","max","firestore","deals","idField","usePagedDeals","pageLimit","useLinkedDeals","link","field","id","contactId","companyId","useDeal","dealId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,eAAe,QAA4B,eAAc;AAClE,SACEC,YAAY,EACZC,sBAAsB,EACtBC,eAAe,EACfC,kBAAkB,QACb,iCAAgC;AACvC,SACEC,UAAU,EACVC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAE3B,SAASC,gBAAgB,EAAEC,kBAAkB,QAAQ,qBAAiB;AAEtE;;;CAGC,GACD,OAAO,MAAMC,mBAAmB,IAAG;AAEnC,8DAA8D,GAC9D,OAAO,MAAMC,qBAAqB,GAAE;AAEpC,qDAAqD,GACrD,OAAO,MAAMC,qBAAqB,GAAE;AAEpC;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,iBACdC,KAAoB,EACpBC,UAAoC,EACpCC,UAAyB,EACzBC,MAAqB,EACrBC,GAAW;IAEX,MAAMC,YAAYrB;IAClB,OAAOC,uBACL,IACEe,SAASE,cAAcR,iBAAiBO,cACpCT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,aACtBR,MAAM,cAAc,MAAMS,aAC1BT,MAAM,UAAU,MAAMU,SACtBZ,QAAQ,aAAa,SACrBD,MAAMc,QAER,MACN;QAACC;QAAWL;QAAOC;QAAYC;QAAYC;QAAQC;KAAI,EACvD;QAAEG,SAAS;IAAM;AAErB;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,cACdR,KAAoB,EACpBC,UAAoC,EACpCE,MAA6B,EAC7BD,aAA4B,IAAI;IAEhC,MAAMG,YAAYrB;IAClB,OAAOG,mBACL,CAACsB,YACCT,SAASN,iBAAiBO,cACtBT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,gBAClBC,aAAa;YAACT,MAAM,cAAc,MAAMS;SAAY,GAAG,EAAE,KACzDC,WAAW,QAAQ,EAAE,GAAG;YAACV,MAAM,UAAU,MAAMU;SAAQ,EAC3DZ,QAAQ,aAAa,SACrBD,MAAMmB,cAER,MACN;QAACJ;QAAWL;QAAOC;QAAYE;QAAQD;KAAW,EAClD;QAAEK,SAAS;IAAM;AAErB;AAEA;;;CAGC,GACD,OAAO,SAASG,eACdV,KAAoB,EACpBC,UAAoC,EACpCU,IAAmD;IAEnD,MAAMN,YAAYrB;IAClB,MAAM4B,QAAQ,eAAeD,OAAO,cAAc;IAClD,MAAME,KAAK,eAAeF,OAAOA,KAAKG,SAAS,GAAGH,KAAKI,SAAS;IAChE,OAAO9B,uBACL,IACEe,SAASa,MAAMnB,iBAAiBO,cAC5BT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,aACtBR,MAAMmB,OAAO,MAAMC,KACnBtB,QAAQ,aAAa,SACrBD,MAAMQ,uBAER,MACN;QAACO;QAAWL;QAAOC;QAAYW;QAAOC;KAAG,EACzC;QAAEN,SAAS;IAAM;AAErB;AAEA,mEAAmE,GACnE,OAAO,SAASS,QAAQhB,KAAoB,EAAEiB,MAAc;IAC1D,MAAMZ,YAAYrB;IAClB,OAAOE,gBACL,IACEc,SAASiB,SACL5B,IAAIgB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,EAAEW,UACrD,MACN;QAACZ;QAAWL;QAAOiB;KAAO,EAC1B;QAAEV,SAAS;IAAM;AAErB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-deals.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, type CrmDealStatus } from '@aglyn/aglyn'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n collection,\n doc,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport type { DealDoc } from '../model/deal-board-model'\nimport { crmScopeListable, crmVisibleToClause } from './use-crm-scope'\n\n/**\n * The most open cards one pipeline draws. A board past this is a board\n * nobody reads column by column; the table is paged and reaches the rest.\n */\nexport const BOARD_OPEN_LIMIT = 500\n\n/** Closed deals shown per closed column when it is expanded. */\nexport const BOARD_CLOSED_LIMIT = 50\n\n/** Deals listed on a contact's or a company's page. */\nexport const LINKED_DEALS_LIMIT = 25\n\n/**\n * The deals of one status in one pipeline, newest activity first (AGL-2598).\n *\n * Every query here carries the viewer's scope tokens and an `orderBy`, and\n * each shape sits on an index the foundation declared:\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. Passing `null` for the\n * pipeline or an EMPTY token list issues nothing, which is how a column\n * stays quiet until the pipeline has loaded and a collapsed Won column\n * costs no read. A `null` token list is the ORGANIZATION level (AGL-2630):\n * the clause is dropped and the query rides the `(pipelineId, status,\n * updatedAt DESC)` twin of that index.\n */\nexport function useDealsByStatus(\n orgId: string | null,\n readTokens: readonly string[] | null,\n pipelineId: string | null,\n status: CrmDealStatus,\n max: number,\n) {\n const firestore = useFirestore()\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && pipelineId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where('pipelineId', '==', pipelineId),\n where('status', '==', status),\n orderBy('updatedAt', 'desc'),\n limit(max),\n )\n : null,\n [firestore, orgId, readTokens, pipelineId, status, max],\n { idField: '$id' },\n )\n}\n\n/**\n * Every deal the viewer may see, paged, optionally narrowed to one status\n * and to one pipeline — the table's read. Four shapes, four indexes:\n * `(visibleTo, updatedAt DESC)`, `(visibleTo, status, updatedAt DESC)`,\n * `(visibleTo, pipelineId, updatedAt DESC)` and\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. At the organization\n * level (AGL-2630) the scope clause is dropped and the same shapes ride\n * their twins without `visibleTo` — `(status, updatedAt DESC)`,\n * `(pipelineId, updatedAt DESC)`, `(pipelineId, status, updatedAt DESC)`;\n * the first shape orders on one field and needs none.\n */\nexport function usePagedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n status: CrmDealStatus | 'all',\n pipelineId: string | null = null,\n) {\n const firestore = useFirestore()\n return usePagedCollection<DealDoc>(\n (pageLimit) =>\n orgId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n ...(pipelineId ? [where('pipelineId', '==', pipelineId)] : []),\n ...(status === 'all' ? [] : [where('status', '==', status)]),\n orderBy('updatedAt', 'desc'),\n limit(pageLimit),\n )\n : null,\n [firestore, orgId, readTokens, status, pipelineId],\n { idField: '$id' },\n )\n}\n\n/**\n * How many deals a table search reads (AGL-3315): the newest by last\n * change, in the table's own scope, status and pipeline, plus one probe row\n * so \"there are more\" is a fact rather than a guess at the boundary.\n */\nexport const DEAL_SEARCH_WINDOW = 1000\n\n/**\n * The deals a table SEARCH runs over (AGL-3315) — a window, read only while\n * the search box holds something.\n *\n * A search has to find a deal on any page, and it has to find it by a word\n * from the middle of its title: \"Acme renewal\" is looked for as \"renewal\".\n * An indexed prefix, the way the companies list searches names, answers\n * only the first word, and word tokens would need every writer and a\n * backfill to agree on them. An organization's deals are counted in the\n * tens or hundreds — the board already reads every open one — so the\n * window is the whole collection for all but the largest, and the table\n * says so when it is not. The query is the table's own, one of the four\n * shapes `usePagedDeals` states, so it rides indexes that already exist.\n */\nexport function useDealSearchWindow(\n orgId: string | null,\n readTokens: readonly string[] | null,\n status: CrmDealStatus | 'all',\n pipelineId: string | null = null,\n) {\n const firestore = useFirestore()\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n ...(pipelineId ? [where('pipelineId', '==', pipelineId)] : []),\n ...(status === 'all' ? [] : [where('status', '==', status)]),\n orderBy('updatedAt', 'desc'),\n limit(DEAL_SEARCH_WINDOW + 1),\n )\n : null,\n [firestore, orgId, readTokens, status, pipelineId],\n { idField: '$id' },\n )\n}\n\n/**\n * The deals that name one contact or one company, for the card on that\n * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.\n */\nexport function useLinkedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n link: { contactId: string } | { companyId: string },\n) {\n const firestore = useFirestore()\n const field = 'contactId' in link ? 'contactId' : 'companyId'\n const id = 'contactId' in link ? link.contactId : link.companyId\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && id && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where(field, '==', id),\n orderBy('updatedAt', 'desc'),\n limit(LINKED_DEALS_LIMIT),\n )\n : null,\n [firestore, orgId, readTokens, field, id],\n { idField: '$id' },\n )\n}\n\n/** One deal, live. `undefined` while loading, and after a delete. */\nexport function useDeal(orgId: string | null, dealId: string) {\n const firestore = useFirestore()\n return useFirestoreDoc<DealDoc>(\n () =>\n orgId && dealId\n ? doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals, dealId)\n : null,\n [firestore, orgId, dealId],\n { idField: '$id' },\n )\n}\n"],"names":["CRM_COLLECTIONS","useFirestore","useFirestoreCollection","useFirestoreDoc","usePagedCollection","collection","doc","limit","orderBy","query","where","crmScopeListable","crmVisibleToClause","BOARD_OPEN_LIMIT","BOARD_CLOSED_LIMIT","LINKED_DEALS_LIMIT","useDealsByStatus","orgId","readTokens","pipelineId","status","max","firestore","deals","idField","usePagedDeals","pageLimit","DEAL_SEARCH_WINDOW","useDealSearchWindow","useLinkedDeals","link","field","id","contactId","companyId","useDeal","dealId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,eAAe,QAA4B,eAAc;AAClE,SACEC,YAAY,EACZC,sBAAsB,EACtBC,eAAe,EACfC,kBAAkB,QACb,iCAAgC;AACvC,SACEC,UAAU,EACVC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAE3B,SAASC,gBAAgB,EAAEC,kBAAkB,QAAQ,qBAAiB;AAEtE;;;CAGC,GACD,OAAO,MAAMC,mBAAmB,IAAG;AAEnC,8DAA8D,GAC9D,OAAO,MAAMC,qBAAqB,GAAE;AAEpC,qDAAqD,GACrD,OAAO,MAAMC,qBAAqB,GAAE;AAEpC;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,iBACdC,KAAoB,EACpBC,UAAoC,EACpCC,UAAyB,EACzBC,MAAqB,EACrBC,GAAW;IAEX,MAAMC,YAAYrB;IAClB,OAAOC,uBACL,IACEe,SAASE,cAAcR,iBAAiBO,cACpCT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,aACtBR,MAAM,cAAc,MAAMS,aAC1BT,MAAM,UAAU,MAAMU,SACtBZ,QAAQ,aAAa,SACrBD,MAAMc,QAER,MACN;QAACC;QAAWL;QAAOC;QAAYC;QAAYC;QAAQC;KAAI,EACvD;QAAEG,SAAS;IAAM;AAErB;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,cACdR,KAAoB,EACpBC,UAAoC,EACpCE,MAA6B,EAC7BD,aAA4B,IAAI;IAEhC,MAAMG,YAAYrB;IAClB,OAAOG,mBACL,CAACsB,YACCT,SAASN,iBAAiBO,cACtBT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,gBAClBC,aAAa;YAACT,MAAM,cAAc,MAAMS;SAAY,GAAG,EAAE,KACzDC,WAAW,QAAQ,EAAE,GAAG;YAACV,MAAM,UAAU,MAAMU;SAAQ,EAC3DZ,QAAQ,aAAa,SACrBD,MAAMmB,cAER,MACN;QAACJ;QAAWL;QAAOC;QAAYE;QAAQD;KAAW,EAClD;QAAEK,SAAS;IAAM;AAErB;AAEA;;;;CAIC,GACD,OAAO,MAAMG,qBAAqB,KAAI;AAEtC;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,oBACdX,KAAoB,EACpBC,UAAoC,EACpCE,MAA6B,EAC7BD,aAA4B,IAAI;IAEhC,MAAMG,YAAYrB;IAClB,OAAOC,uBACL,IACEe,SAASN,iBAAiBO,cACtBT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,gBAClBC,aAAa;YAACT,MAAM,cAAc,MAAMS;SAAY,GAAG,EAAE,KACzDC,WAAW,QAAQ,EAAE,GAAG;YAACV,MAAM,UAAU,MAAMU;SAAQ,EAC3DZ,QAAQ,aAAa,SACrBD,MAAMoB,qBAAqB,MAE7B,MACN;QAACL;QAAWL;QAAOC;QAAYE;QAAQD;KAAW,EAClD;QAAEK,SAAS;IAAM;AAErB;AAEA;;;CAGC,GACD,OAAO,SAASK,eACdZ,KAAoB,EACpBC,UAAoC,EACpCY,IAAmD;IAEnD,MAAMR,YAAYrB;IAClB,MAAM8B,QAAQ,eAAeD,OAAO,cAAc;IAClD,MAAME,KAAK,eAAeF,OAAOA,KAAKG,SAAS,GAAGH,KAAKI,SAAS;IAChE,OAAOhC,uBACL,IACEe,SAASe,MAAMrB,iBAAiBO,cAC5BT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,aACtBR,MAAMqB,OAAO,MAAMC,KACnBxB,QAAQ,aAAa,SACrBD,MAAMQ,uBAER,MACN;QAACO;QAAWL;QAAOC;QAAYa;QAAOC;KAAG,EACzC;QAAER,SAAS;IAAM;AAErB;AAEA,mEAAmE,GACnE,OAAO,SAASW,QAAQlB,KAAoB,EAAEmB,MAAc;IAC1D,MAAMd,YAAYrB;IAClB,OAAOE,gBACL,IACEc,SAASmB,SACL9B,IAAIgB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,EAAEa,UACrD,MACN;QAACd;QAAWL;QAAOmB;KAAO,EAC1B;QAAEZ,SAAS;IAAM;AAErB"}