@aglyn/plugins-crm 1.0.0-beta.200 → 1.0.0-beta.202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/activity-list.d.ts +36 -5
  3. package/src/lib/components/activity-list.js +159 -137
  4. package/src/lib/components/activity-list.js.map +1 -1
  5. package/src/lib/components/activity-timeline.d.ts +84 -0
  6. package/src/lib/components/activity-timeline.js +224 -0
  7. package/src/lib/components/activity-timeline.js.map +1 -0
  8. package/src/lib/components/company-properties-card.js +53 -36
  9. package/src/lib/components/company-properties-card.js.map +1 -1
  10. package/src/lib/components/contact-associations-card.d.ts +3 -2
  11. package/src/lib/components/contact-associations-card.js +12 -12
  12. package/src/lib/components/contact-associations-card.js.map +1 -1
  13. package/src/lib/components/contact-detail-page.d.ts +4 -3
  14. package/src/lib/components/contact-detail-page.js +4 -10
  15. package/src/lib/components/contact-detail-page.js.map +1 -1
  16. package/src/lib/components/contact-properties-card.d.ts +21 -4
  17. package/src/lib/components/contact-properties-card.js +129 -29
  18. package/src/lib/components/contact-properties-card.js.map +1 -1
  19. package/src/lib/components/contact-timeline-card.d.ts +12 -6
  20. package/src/lib/components/contact-timeline-card.js +132 -174
  21. package/src/lib/components/contact-timeline-card.js.map +1 -1
  22. package/src/lib/components/contacts-section.js +197 -198
  23. package/src/lib/components/contacts-section.js.map +1 -1
  24. package/src/lib/components/crm-email-state-chip.js +4 -1
  25. package/src/lib/components/crm-email-state-chip.js.map +1 -1
  26. package/src/lib/components/deal-products-card.js +15 -13
  27. package/src/lib/components/deal-products-card.js.map +1 -1
  28. package/src/lib/components/deal-properties-card.js +7 -0
  29. package/src/lib/components/deal-properties-card.js.map +1 -1
  30. package/src/lib/components/deal-stage-card.js +31 -29
  31. package/src/lib/components/deal-stage-card.js.map +1 -1
  32. package/src/lib/components/deals-section.js +9 -14
  33. package/src/lib/components/deals-section.js.map +1 -1
  34. package/src/lib/components/fields-section.d.ts +4 -0
  35. package/src/lib/components/fields-section.js +368 -154
  36. package/src/lib/components/fields-section.js.map +1 -1
  37. package/src/lib/components/form-contact-fields-card.js +24 -20
  38. package/src/lib/components/form-contact-fields-card.js.map +1 -1
  39. package/src/lib/components/lead-campaigns-card.d.ts +1 -1
  40. package/src/lib/components/lead-campaigns-card.js +32 -35
  41. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  42. package/src/lib/components/lead-properties-card.d.ts +14 -6
  43. package/src/lib/components/lead-properties-card.js +317 -313
  44. package/src/lib/components/lead-properties-card.js.map +1 -1
  45. package/src/lib/components/lead-surfaces-note.js +13 -2
  46. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  47. package/src/lib/components/linked-deals-card.js +12 -10
  48. package/src/lib/components/linked-deals-card.js.map +1 -1
  49. package/src/lib/components/recent-activity-feed.d.ts +12 -8
  50. package/src/lib/components/recent-activity-feed.js +54 -29
  51. package/src/lib/components/recent-activity-feed.js.map +1 -1
  52. package/src/lib/components/record-activity-card.d.ts +4 -2
  53. package/src/lib/components/record-activity-card.js +29 -11
  54. package/src/lib/components/record-activity-card.js.map +1 -1
  55. package/src/lib/components/reports/activity-card.js +18 -12
  56. package/src/lib/components/reports/activity-card.js.map +1 -1
  57. package/src/lib/components/reports/lead-funnel-card.js +47 -37
  58. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  59. package/src/lib/components/reports/pipeline-card.js +22 -16
  60. package/src/lib/components/reports/pipeline-card.js.map +1 -1
  61. package/src/lib/components/reports/report-export.d.ts +4 -11
  62. package/src/lib/components/reports/report-export.js +16 -33
  63. package/src/lib/components/reports/report-export.js.map +1 -1
  64. package/src/lib/components/reports/source-conversion-card.js +18 -12
  65. package/src/lib/components/reports/source-conversion-card.js.map +1 -1
  66. package/src/lib/components/reports/tasks-card.js +35 -25
  67. package/src/lib/components/reports/tasks-card.js.map +1 -1
  68. package/src/lib/components/tasks-section.js +23 -23
  69. package/src/lib/components/tasks-section.js.map +1 -1
  70. package/src/lib/constants/field-list-query.d.ts +23 -0
  71. package/src/lib/constants/field-list-query.js +80 -0
  72. package/src/lib/constants/field-list-query.js.map +1 -0
  73. package/src/lib/model/lead-filters.d.ts +1 -1
  74. package/src/lib/server/capture-contact.js +7 -5
  75. package/src/lib/server/capture-contact.js.map +1 -1
  76. package/src/lib/components/contact-custom-fields-card.d.ts +0 -39
  77. package/src/lib/components/contact-custom-fields-card.js +0 -204
  78. package/src/lib/components/contact-custom-fields-card.js.map +0 -1
@@ -16,19 +16,27 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
- import { CONTACT_FIELD_TYPE_LABELS, createResourceUid, CRM_COLLECTIONS, CRM_FIELD_OBJECT_LABELS, CRM_FIELD_OBJECTS, isCrmFieldObject, newResourceScopeFields, ORG_SCOPE_TOKEN, pluginDocsHelp } from "@aglyn/aglyn";
19
+ import { CONTACT_FIELD_TYPE_LABELS, CONTACT_FIELD_TYPES, CONTACT_FIELDS_MAX_PER_ORG, createResourceUid, CRM_COLLECTIONS, crmFieldListFields, CRM_FIELD_OBJECT_LABELS, CRM_FIELD_OBJECTS, isCrmFieldObject, newResourceScopeFields, ORG_SCOPE_TOKEN, pluginDocsHelp } from "@aglyn/aglyn";
20
20
  import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
21
+ import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
22
+ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
23
+ import { ListRowActions, ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
21
24
  import { mdiArchiveArrowUpOutline, mdiArchiveOutline, mdiArrowDown, mdiArrowUp, mdiDeleteOutline, mdiPencilOutline } from "@aglyn/shared-data-mdi";
22
25
  import { CardDisplay, MdiIcon, SrOnly, useConfirmationContext } from "@aglyn/shared-ui-jsx";
23
- import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
24
- import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
26
+ import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
27
+ import { planListQuery } from "@aglyn/shared-ui-jsx/const/list-query-plan";
28
+ import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
29
+ import { nameSearchNormalizers } from "@aglyn/aglyn/app-utils/name-search";
25
30
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
26
- import { useFirestore, useUser, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
27
- import { Button, Chip, IconButton, Stack, Tab, TableBody, TableCell, TableHead, TableRow, Tabs, Typography } from "@mui/material";
28
- import { collection, deleteDoc, doc, setDoc, updateDoc, writeBatch } from "firebase/firestore";
29
- import { useCallback, useState } from "react";
31
+ import { ceilingedWindow } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
32
+ import { listQueryConstraints } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
33
+ import { useFirestore, useFirestoreCollection, useUser, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
34
+ import { Button, Chip, IconButton, Stack, Tab, Tabs, Tooltip, Typography } from "@mui/material";
35
+ import { collection, deleteDoc, doc, limit, query, setDoc, updateDoc, writeBatch } from "firebase/firestore";
36
+ import { useCallback, useMemo, useState } from "react";
30
37
  import { useContactFieldDefinitions } from "../hooks/use-contact-field-definitions.js";
31
38
  import { useCrmScope } from "../hooks/use-crm-scope.js";
39
+ import { CRM_FIELD_LIST_QUERY, crmFieldListBase } from "../constants/field-list-query.js";
32
40
  import ContactFieldDrawer from "./contact-field-drawer.js";
33
41
  import { LeadSourceValuesCard } from "./lead-source-values-card.js";
34
42
  import { recomputeAllCrmNextActivity } from "../model/next-activity-api.js";
@@ -45,6 +53,322 @@ import { crmTaskCallScope } from "../model/task-routes.js";
45
53
  deal: 'deal',
46
54
  lead: 'lead'
47
55
  };
56
+ /** What the grid's Filters panel offers: the type and whether it is required, on the query. */ const FIELD_FILTER_FIELDS = CRM_FIELD_LIST_QUERY.fields;
57
+ const FIELD_FILTER_HEADERS = {
58
+ type: 'Type',
59
+ required: 'Required'
60
+ };
61
+ const FIELD_FILTER_OPTIONS = {
62
+ type: CONTACT_FIELD_TYPES.map((type)=>({
63
+ value: type,
64
+ label: CONTACT_FIELD_TYPE_LABELS[type]
65
+ })),
66
+ required: [
67
+ {
68
+ value: 'true',
69
+ label: 'Required'
70
+ },
71
+ {
72
+ value: 'false',
73
+ label: 'Optional'
74
+ }
75
+ ]
76
+ };
77
+ /** Why the arrows are off while the list is not in its stored order. */ export const FIELD_REORDER_LOCKED_REASON = 'Clear sorting and filters to reorder';
78
+ const typeLabel = (type)=>{
79
+ var _CONTACT_FIELD_TYPE_LABELS_type;
80
+ return (_CONTACT_FIELD_TYPE_LABELS_type = CONTACT_FIELD_TYPE_LABELS[type]) != null ? _CONTACT_FIELD_TYPE_LABELS_type : String(type != null ? type : '');
81
+ };
82
+ /**
83
+ * One tab's field definitions in the console's list table (AGL-3335): the
84
+ * toolbar's columns, filters, export and search, sorting by Field, Key and
85
+ * Type, and the shared footer.
86
+ *
87
+ * ## Order is data, not a sort
88
+ *
89
+ * The stored `order` is where each field appears on every record and form,
90
+ * so the arrows that move it are kept, and are live only while the table
91
+ * shows that order: no sort, no filter and no search. Under any of them the
92
+ * row above a field on screen is not the field above it in the stored
93
+ * order, and a move would land somewhere the reader cannot see; the arrows
94
+ * say so rather than disappear.
95
+ *
96
+ * The panel and the search are asked of Firestore (AGL-3321): a clause or
97
+ * a search word reads the tab's definitions that match it
98
+ * (`CRM_FIELD_LIST_QUERY`), and never narrows the rows already on screen.
99
+ * That answer is every match, at most `CONTACT_FIELDS_MAX_PER_ORG`, so the
100
+ * grid sorts and pages it whole; a read that reaches the ceiling says so.
101
+ */ function FieldsTable(props) {
102
+ const { orgId, object, definitions, busyId, onMove, rowActions } = props;
103
+ const firestore = useFirestore();
104
+ const gridFilter = useListGridFilter({
105
+ selectFields: [
106
+ 'type',
107
+ 'required'
108
+ ]
109
+ });
110
+ const [sortModel, setSortModel] = useState([]);
111
+ const searching = gridFilter.searchWords.some((word)=>word.trim() !== '');
112
+ const reorderLocked = sortModel.length > 0 || gridFilter.clauses.length > 0 || searching;
113
+ const narrowed = gridFilter.clauses.length > 0 || searching;
114
+ const plan = useMemo(()=>planListQuery(CRM_FIELD_LIST_QUERY, {
115
+ clauses: gridFilter.clauses,
116
+ search: gridFilter.searchWords,
117
+ base: crmFieldListBase(object)
118
+ }, nameSearchNormalizers), // The words are a new array each render; their text is their identity.
119
+ // eslint-disable-next-line react-hooks/exhaustive-deps
120
+ [
121
+ object,
122
+ gridFilter.clauses,
123
+ gridFilter.searchWords.join(' ')
124
+ ]);
125
+ const planKey = JSON.stringify({
126
+ filters: plan.filters,
127
+ orderBy: plan.orderBy
128
+ });
129
+ /*
130
+ * Read only while something narrows the tab. Unnarrowed, the table is the
131
+ * shared definitions read every CRM surface already holds, in its stored
132
+ * order — the same fields, with no second read.
133
+ */ const matched = useFirestoreCollection(()=>narrowed && orgId ? query(collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.contactFields), ...listQueryConstraints(plan), limit(CONTACT_FIELDS_MAX_PER_ORG + 1)) : null, [
134
+ firestore,
135
+ orgId,
136
+ narrowed,
137
+ planKey
138
+ ], {
139
+ idField: '$id'
140
+ });
141
+ const answer = useMemo(()=>ceilingedWindow(matched.data, CONTACT_FIELDS_MAX_PER_ORG), [
142
+ matched.data
143
+ ]);
144
+ const positions = useMemo(()=>new Map(definitions.map((definition, position)=>[
145
+ definition.$id,
146
+ position
147
+ ])), [
148
+ definitions
149
+ ]);
150
+ const rows = useMemo(()=>{
151
+ const shown = narrowed ? [
152
+ ...answer.rows
153
+ ].sort((a, b)=>{
154
+ var _positions_get, _positions_get1;
155
+ return ((_positions_get = positions.get(a.$id)) != null ? _positions_get : Number.MAX_SAFE_INTEGER) - ((_positions_get1 = positions.get(b.$id)) != null ? _positions_get1 : Number.MAX_SAFE_INTEGER);
156
+ }) : definitions;
157
+ return shown.map((definition)=>{
158
+ var _positions_get;
159
+ return {
160
+ $id: definition.$id,
161
+ label: definition.label,
162
+ key: definition.key,
163
+ type: definition.type,
164
+ required: definition.required ? 'true' : 'false',
165
+ position: (_positions_get = positions.get(definition.$id)) != null ? _positions_get : 0,
166
+ definition
167
+ };
168
+ });
169
+ }, [
170
+ narrowed,
171
+ answer.rows,
172
+ definitions,
173
+ positions
174
+ ]);
175
+ const refused = useMemo(()=>listQueryRefusals(plan.refused, {
176
+ fields: FIELD_FILTER_FIELDS,
177
+ headers: FIELD_FILTER_HEADERS,
178
+ options: FIELD_FILTER_OPTIONS
179
+ }), [
180
+ plan.refused
181
+ ]);
182
+ const columns = useMemo(()=>listFilterGridColumns([
183
+ {
184
+ field: 'position',
185
+ headerName: 'Order',
186
+ width: 132,
187
+ sortable: false,
188
+ filterable: false,
189
+ hideable: false,
190
+ disableColumnMenu: true,
191
+ renderCell: ({ row })=>{
192
+ const arrows = /*#__PURE__*/ _jsxs(Stack, {
193
+ direction: "row",
194
+ spacing: 0,
195
+ sx: {
196
+ alignItems: 'center'
197
+ },
198
+ children: [
199
+ /*#__PURE__*/ _jsx(Typography, {
200
+ variant: "body2",
201
+ color: "text.secondary",
202
+ sx: {
203
+ minWidth: 24,
204
+ textAlign: 'right',
205
+ mr: 0.5
206
+ },
207
+ children: row.position + 1
208
+ }),
209
+ /*#__PURE__*/ _jsxs(IconButton, {
210
+ size: "small",
211
+ disabled: reorderLocked || row.position === 0 || Boolean(busyId),
212
+ onClick: ()=>onMove(row.definition, -1),
213
+ children: [
214
+ /*#__PURE__*/ _jsx(MdiIcon, {
215
+ path: mdiArrowUp.path,
216
+ size: 0.7
217
+ }),
218
+ /*#__PURE__*/ _jsx(SrOnly, {
219
+ children: `Move ${row.label} up`
220
+ })
221
+ ]
222
+ }),
223
+ /*#__PURE__*/ _jsxs(IconButton, {
224
+ size: "small",
225
+ disabled: reorderLocked || row.position === definitions.length - 1 || Boolean(busyId),
226
+ onClick: ()=>onMove(row.definition, 1),
227
+ children: [
228
+ /*#__PURE__*/ _jsx(MdiIcon, {
229
+ path: mdiArrowDown.path,
230
+ size: 0.7
231
+ }),
232
+ /*#__PURE__*/ _jsx(SrOnly, {
233
+ children: `Move ${row.label} down`
234
+ })
235
+ ]
236
+ })
237
+ ]
238
+ });
239
+ return reorderLocked ? /*#__PURE__*/ _jsx(Tooltip, {
240
+ title: FIELD_REORDER_LOCKED_REASON,
241
+ children: /*#__PURE__*/ _jsx("span", {
242
+ children: arrows
243
+ })
244
+ }) : arrows;
245
+ }
246
+ },
247
+ {
248
+ field: 'label',
249
+ headerName: 'Field',
250
+ flex: 1,
251
+ minWidth: 180,
252
+ renderCell: ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
253
+ direction: "row",
254
+ spacing: 1,
255
+ sx: {
256
+ flexWrap: 'wrap',
257
+ alignItems: 'center'
258
+ },
259
+ children: [
260
+ /*#__PURE__*/ _jsx(Typography, {
261
+ variant: "body2",
262
+ children: row.label
263
+ }),
264
+ row.definition.retiredAt ? /*#__PURE__*/ _jsx(Chip, {
265
+ size: "small",
266
+ variant: "outlined",
267
+ color: "warning",
268
+ label: "Retired"
269
+ }) : null
270
+ ]
271
+ })
272
+ },
273
+ {
274
+ field: 'key',
275
+ headerName: 'Key',
276
+ flex: 1,
277
+ minWidth: 160,
278
+ renderCell: ({ row })=>/*#__PURE__*/ _jsx(Typography, {
279
+ variant: "body2",
280
+ color: "text.secondary",
281
+ sx: {
282
+ fontFamily: 'monospace'
283
+ },
284
+ children: row.key
285
+ })
286
+ },
287
+ {
288
+ field: 'type',
289
+ headerName: 'Type',
290
+ width: 170,
291
+ // By what the type reads as, not by its stored key.
292
+ sortComparator: (a, b)=>typeLabel(a).localeCompare(typeLabel(b)),
293
+ renderCell: ({ row })=>{
294
+ var _row_definition_options;
295
+ return /*#__PURE__*/ _jsxs(Typography, {
296
+ variant: "body2",
297
+ children: [
298
+ typeLabel(row.type),
299
+ row.definition.type === 'select' && ((_row_definition_options = row.definition.options) == null ? void 0 : _row_definition_options.length) ? ` · ${row.definition.options.length} choices` : ''
300
+ ]
301
+ });
302
+ }
303
+ },
304
+ {
305
+ field: 'required',
306
+ headerName: 'Required',
307
+ width: 120,
308
+ sortable: false,
309
+ renderCell: ({ row })=>/*#__PURE__*/ _jsx(Typography, {
310
+ variant: "body2",
311
+ color: "text.secondary",
312
+ children: row.required === 'true' ? 'Yes' : '—'
313
+ })
314
+ },
315
+ listActionsColumn((row)=>/*#__PURE__*/ _jsx(ListRowActions, {
316
+ label: row.label,
317
+ items: rowActions(row.definition)
318
+ }), {
319
+ width: 72
320
+ })
321
+ ], FIELD_FILTER_FIELDS, FIELD_FILTER_OPTIONS, FIELD_FILTER_HEADERS), [
322
+ reorderLocked,
323
+ busyId,
324
+ onMove,
325
+ rowActions,
326
+ definitions.length
327
+ ]);
328
+ return /*#__PURE__*/ _jsxs(Stack, {
329
+ spacing: 1,
330
+ children: [
331
+ /*#__PURE__*/ _jsx(ListFilterChips, {
332
+ fields: FIELD_FILTER_FIELDS,
333
+ headers: FIELD_FILTER_HEADERS,
334
+ clauses: gridFilter.clauses,
335
+ onChange: gridFilter.setClauses,
336
+ options: FIELD_FILTER_OPTIONS,
337
+ marksServed: false
338
+ }),
339
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
340
+ refused: refused,
341
+ notices: plan.notices
342
+ }),
343
+ narrowed && answer.truncated ? /*#__PURE__*/ _jsx(Typography, {
344
+ variant: "caption",
345
+ color: "text.secondary",
346
+ children: `Showing the first ${CONTACT_FIELDS_MAX_PER_ORG} matching fields.`
347
+ }) : null,
348
+ /*#__PURE__*/ _jsx(ListTable, {
349
+ "aria-label": "Fields",
350
+ rows: rows,
351
+ loading: narrowed && matched.status === 'loading',
352
+ columns: columns,
353
+ sortModel: sortModel,
354
+ onSortModelChange: setSortModel,
355
+ // Firestore narrowed the rows above; the grid only draws them.
356
+ filterMode: "server",
357
+ filterModel: gridFilter.filterModel,
358
+ onFilterModelChange: gridFilter.onFilterModelChange,
359
+ quickFilter: true,
360
+ noRowsLabel: "No fields match these filters",
361
+ getRowClassName: ({ row })=>row.definition.retiredAt ? 'field-retired' : '',
362
+ sx: {
363
+ '& .field-retired': {
364
+ opacity: 0.6
365
+ }
366
+ }
367
+ })
368
+ ]
369
+ });
370
+ }
371
+ FieldsTable.displayName = 'FieldsTable';
48
372
  /**
49
373
  * `/crm/fields` — the custom fields a holder keeps on a person (AGL-2601),
50
374
  * since AGL-2661 on a company and a deal too, and since AGL-3272 on a lead:
@@ -63,6 +387,8 @@ import { crmTaskCallScope } from "../model/task-routes.js";
63
387
  * rename is a new field and a retire.
64
388
  * - Order is a stored `order` on each document, moved with the arrows here,
65
389
  * and every reader sorts by it — the profile card, the columns, an export.
390
+ * The list table sorts, filters and searches a tab too (AGL-3335), and
391
+ * the arrows wait while it does — see `FieldsTable`.
66
392
  *
67
393
  * ## What is NOT on this page
68
394
  *
@@ -179,8 +505,10 @@ import { crmTaskCallScope } from "../model/task-routes.js";
179
505
  label: draft.label
180
506
  }, editing.type === 'select' ? {
181
507
  options: draft.options
182
- } : {}, {
183
- required: draft.required,
508
+ } : {}, crmFieldListFields(_extends({}, editing, {
509
+ label: draft.label,
510
+ required: draft.required
511
+ })), {
184
512
  updatedAt: now
185
513
  }));
186
514
  });
@@ -205,9 +533,13 @@ import { crmTaskCallScope } from "../model/task-routes.js";
205
533
  } : {}, {
206
534
  required: draft.required,
207
535
  order,
208
- retiredAt: null,
209
- // Which record the field describes — the tab it was made on.
210
- object,
536
+ retiredAt: null
537
+ }, crmFieldListFields({
538
+ key: draft.key,
539
+ label: draft.label,
540
+ required: draft.required,
541
+ object
542
+ }), {
211
543
  hostId: createHostId
212
544
  }, newResourceScopeFields([
213
545
  ORG_SCOPE_TOKEN
@@ -382,7 +714,10 @@ import { crmTaskCallScope } from "../model/task-routes.js";
382
714
  enqueueSnackbar,
383
715
  noun
384
716
  ]);
385
- const rowActions = (definition)=>[
717
+ /*
718
+ * Stable across renders, as the table's columns are built from them: a
719
+ * fresh function each render would rebuild every column on every render.
720
+ */ const rowActions = useCallback((definition)=>[
386
721
  {
387
722
  key: 'edit',
388
723
  label: 'Edit field',
@@ -418,7 +753,15 @@ import { crmTaskCallScope } from "../model/task-routes.js";
418
753
  onClick: ()=>void handleDelete(definition)
419
754
  }
420
755
  ] : []
421
- ];
756
+ ], [
757
+ openEdit,
758
+ toggleRetired,
759
+ handleDelete,
760
+ busyId
761
+ ]);
762
+ const onMove = useCallback((definition, direction)=>void move(definition, direction), [
763
+ move
764
+ ]);
422
765
  return /*#__PURE__*/ _jsxs(CardDisplay, {
423
766
  header: 'Fields',
424
767
  help: pluginDocsHelp('contactFields', {
@@ -481,146 +824,17 @@ import { crmTaskCallScope } from "../model/task-routes.js";
481
824
  onClick: openCreate,
482
825
  children: 'New field'
483
826
  }) : undefined
484
- }) : /*#__PURE__*/ _jsxs(ScrollTable, {
485
- size: "small",
486
- children: [
487
- /*#__PURE__*/ _jsx(TableHead, {
488
- children: /*#__PURE__*/ _jsxs(TableRow, {
489
- children: [
490
- /*#__PURE__*/ _jsx(TableCell, {
491
- sx: {
492
- width: 96
493
- },
494
- children: 'Order'
495
- }),
496
- /*#__PURE__*/ _jsx(TableCell, {
497
- children: 'Field'
498
- }),
499
- /*#__PURE__*/ _jsx(TableCell, {
500
- children: 'Key'
501
- }),
502
- /*#__PURE__*/ _jsx(TableCell, {
503
- children: 'Type'
504
- }),
505
- /*#__PURE__*/ _jsx(TableCell, {
506
- children: 'Required'
507
- }),
508
- /*#__PURE__*/ _jsx(TableCell, {
509
- align: "right"
510
- })
511
- ]
512
- })
513
- }),
514
- /*#__PURE__*/ _jsx(TableBody, {
515
- children: definitions.map((definition, index)=>{
516
- var _CONTACT_FIELD_TYPE_LABELS_definition_type;
517
- var _definition_options;
518
- return /*#__PURE__*/ _jsxs(TableRow, {
519
- hover: true,
520
- sx: definition.retiredAt ? {
521
- opacity: 0.6
522
- } : undefined,
523
- children: [
524
- /*#__PURE__*/ _jsx(TableCell, {
525
- children: /*#__PURE__*/ _jsxs(Stack, {
526
- direction: "row",
527
- spacing: 0,
528
- children: [
529
- /*#__PURE__*/ _jsxs(IconButton, {
530
- size: "small",
531
- disabled: index === 0 || Boolean(busyId),
532
- onClick: ()=>void move(definition, -1),
533
- children: [
534
- /*#__PURE__*/ _jsx(MdiIcon, {
535
- path: mdiArrowUp.path,
536
- size: 0.7
537
- }),
538
- /*#__PURE__*/ _jsx(SrOnly, {
539
- children: `Move ${definition.label} up`
540
- })
541
- ]
542
- }),
543
- /*#__PURE__*/ _jsxs(IconButton, {
544
- size: "small",
545
- disabled: index === definitions.length - 1 || Boolean(busyId),
546
- onClick: ()=>void move(definition, 1),
547
- children: [
548
- /*#__PURE__*/ _jsx(MdiIcon, {
549
- path: mdiArrowDown.path,
550
- size: 0.7
551
- }),
552
- /*#__PURE__*/ _jsx(SrOnly, {
553
- children: `Move ${definition.label} down`
554
- })
555
- ]
556
- })
557
- ]
558
- })
559
- }),
560
- /*#__PURE__*/ _jsx(TableCell, {
561
- children: /*#__PURE__*/ _jsxs(Stack, {
562
- direction: "row",
563
- spacing: 1,
564
- sx: {
565
- flexWrap: 'wrap',
566
- alignItems: 'center'
567
- },
568
- children: [
569
- /*#__PURE__*/ _jsx(Typography, {
570
- variant: "body2",
571
- children: definition.label
572
- }),
573
- definition.retiredAt ? /*#__PURE__*/ _jsx(Chip, {
574
- size: "small",
575
- variant: "outlined",
576
- color: "warning",
577
- label: "Retired"
578
- }) : null
579
- ]
580
- })
581
- }),
582
- /*#__PURE__*/ _jsx(TableCell, {
583
- children: /*#__PURE__*/ _jsx(Typography, {
584
- variant: "body2",
585
- color: "text.secondary",
586
- sx: {
587
- fontFamily: 'monospace'
588
- },
589
- children: definition.key
590
- })
591
- }),
592
- /*#__PURE__*/ _jsx(TableCell, {
593
- children: /*#__PURE__*/ _jsxs(Typography, {
594
- variant: "body2",
595
- children: [
596
- (_CONTACT_FIELD_TYPE_LABELS_definition_type = CONTACT_FIELD_TYPE_LABELS[definition.type]) != null ? _CONTACT_FIELD_TYPE_LABELS_definition_type : definition.type,
597
- definition.type === 'select' && ((_definition_options = definition.options) == null ? void 0 : _definition_options.length) ? ` · ${definition.options.length} choices` : ''
598
- ]
599
- })
600
- }),
601
- /*#__PURE__*/ _jsx(TableCell, {
602
- children: /*#__PURE__*/ _jsx(Typography, {
603
- variant: "body2",
604
- color: "text.secondary",
605
- children: definition.required ? 'Yes' : '—'
606
- })
607
- }),
608
- /*#__PURE__*/ _jsx(TableCell, {
609
- align: "right",
610
- sx: {
611
- width: 56
612
- },
613
- children: /*#__PURE__*/ _jsx(RowActionsMenu, {
614
- label: definition.label,
615
- items: rowActions(definition)
616
- })
617
- })
618
- ]
619
- }, definition.$id);
620
- })
621
- })
622
- ]
623
- }),
827
+ }) : /*
828
+ Keyed by the tab, so a sort, a filter or a search made on one
829
+ record's fields does not follow the reader onto another's.
830
+ */ /*#__PURE__*/ _jsx(FieldsTable, {
831
+ orgId: orgId,
832
+ object: object,
833
+ definitions: definitions,
834
+ busyId: busyId,
835
+ onMove: onMove,
836
+ rowActions: rowActions
837
+ }, object),
624
838
  definitions.length ? /*#__PURE__*/ _jsx(Typography, {
625
839
  variant: "caption",
626
840
  color: "text.secondary",