@aglyn/plugins-forms 1.0.0-beta.199 → 1.0.0-beta.200
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 +12 -12
- package/src/lib/components/form-detail-card.js +6 -2
- package/src/lib/components/form-detail-card.js.map +1 -1
- package/src/lib/components/form-list-query.d.ts +48 -0
- package/src/lib/components/form-list-query.js +216 -0
- package/src/lib/components/form-list-query.js.map +1 -0
- package/src/lib/components/host-forms-card.component.d.ts +0 -6
- package/src/lib/components/host-forms-card.component.js +153 -201
- package/src/lib/components/host-forms-card.component.js.map +1 -1
|
@@ -17,120 +17,42 @@
|
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
19
|
import * as Aglyn from "@aglyn/aglyn";
|
|
20
|
-
import { buildRoute, isFormArchived, PageHeaderActions, pluginDocsHelp,
|
|
20
|
+
import { buildRoute, isFormArchived, PageHeaderActions, pluginDocsHelp, Route } from "@aglyn/aglyn";
|
|
21
21
|
import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
|
|
22
22
|
import { ICON_VARIANT_SHOW_DETAIL } from "@aglyn/shared-data-enums";
|
|
23
23
|
import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline, mdiContentCopy, mdiEyeOutline, mdiVectorSquare } from "@aglyn/shared-data-mdi";
|
|
24
24
|
import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
25
25
|
import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
|
|
26
26
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
27
|
+
import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
|
|
27
28
|
import ListTable, { ListRowActions, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
28
|
-
import {
|
|
29
|
+
import { hiddenFilterVisibility } from "@aglyn/shared-ui-jsx/const/list-filter";
|
|
30
|
+
import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
|
|
29
31
|
import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
30
|
-
import {
|
|
32
|
+
import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
|
|
31
33
|
import QuotaReadoutComponent from "@aglyn/shared-ui-jsx/components/quota-readout.component";
|
|
32
34
|
import { CreateArtifactDrawer } from "@aglyn/shared-ui-jsx-forms";
|
|
33
35
|
import { Alert, Button, Stack, Typography } from "@mui/material";
|
|
34
|
-
import { collection, doc, updateDoc } from "firebase/firestore";
|
|
35
|
-
import { DUPLICATE_MENU_LABEL, useConsoleHostRoute, useDuplicateResource, useFirestore, useHostCampaigns, useHostResourceApi, useLiveArtifactCount
|
|
36
|
-
import {
|
|
36
|
+
import { collection, doc, getCountFromServer, query, updateDoc, where } from "firebase/firestore";
|
|
37
|
+
import { DUPLICATE_MENU_LABEL, useConsoleHostRoute, useDuplicateResource, useFirestore, useHostCampaigns, useHostResourceApi, useLiveArtifactCount } from "@aglyn/tenant-feature-instance";
|
|
38
|
+
import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
|
|
37
39
|
import { useRouter } from "next/navigation";
|
|
38
|
-
import { useCallback, useMemo, useState } from "react";
|
|
40
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
39
41
|
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
* listener over the document id (`collectionPage`), so a filter matches over
|
|
47
|
-
* what the window has read, which the first filter widens
|
|
48
|
-
* (`usePagedRowsFilter`) and the caption under the chips owns up to. The
|
|
49
|
-
* query never changes shape, so no filter here needs an index. The figures
|
|
50
|
-
* are the `stats` counters `/api/forms/submit` keeps on the form document
|
|
51
|
-
* itself, read with the row.
|
|
52
|
-
*
|
|
53
|
-
* - Submissions and Leads are numbers. Leads is ABSENT on a form that does
|
|
54
|
-
* not route to leads — the column's dash — so `is empty` finds those and
|
|
55
|
-
* `= 0` finds a routed form that has produced none.
|
|
56
|
-
* - Last submission, Updated and Created are days. Created is a column
|
|
57
|
-
* hidden by default: `/api/hosts/resources` stamps it on every form it
|
|
58
|
-
* makes.
|
|
59
|
-
* - Status (active or retired), Lead routing (`routing.lead`) and Campaign
|
|
60
|
-
* (`campaignIds`) are hidden columns, matched on keys derived by
|
|
61
|
-
* {@link formFilterRow} so the row keeps the stored fields its menu reads.
|
|
62
|
-
* A form's campaigns are ids, matched member by member and byte for byte,
|
|
63
|
-
* as the campaigns list matches its `listIds`.
|
|
64
|
-
*
|
|
65
|
-
* Nothing else a form stores is a filter. It keeps no type and no template,
|
|
66
|
-
* and its plan slot is not its own: a retired form keeps one, which is the
|
|
67
|
-
* Status filter's answer.
|
|
68
|
-
*/ export const FORM_FILTER_FIELDS = [
|
|
69
|
-
inMemoryListField('displayName', 'text'),
|
|
70
|
-
inMemoryListField('slug', 'text'),
|
|
71
|
-
inMemoryListField('submissions', 'number', 'stats.submissions'),
|
|
72
|
-
inMemoryListField('leads', 'number', 'stats.leads'),
|
|
73
|
-
inMemoryListField('lastSubmission', 'date', 'stats.lastSubmissionAtMs'),
|
|
74
|
-
inMemoryListField('updatedAt', 'date'),
|
|
75
|
-
inMemoryListField('createdAt', 'date'),
|
|
76
|
-
inMemoryListField('status', 'select', 'statusKey'),
|
|
77
|
-
inMemoryListField('leadRouting', 'select', 'leadRoutingKey'),
|
|
78
|
-
_extends({}, inMemoryListField('campaignIds', 'select', 'campaignKeys'), {
|
|
79
|
-
tokensPath: 'campaignKeys',
|
|
80
|
-
verbatimTokens: true
|
|
81
|
-
})
|
|
82
|
-
];
|
|
83
|
-
export const FORM_FILTER_HEADERS = {
|
|
84
|
-
displayName: 'Display name',
|
|
85
|
-
slug: 'Slug',
|
|
86
|
-
submissions: 'Submissions',
|
|
87
|
-
leads: 'Leads',
|
|
88
|
-
lastSubmission: 'Last submission',
|
|
89
|
-
updatedAt: 'Updated',
|
|
90
|
-
createdAt: 'Created',
|
|
91
|
-
status: 'Status',
|
|
92
|
-
leadRouting: 'Lead routing',
|
|
93
|
-
campaignIds: 'Campaign'
|
|
94
|
-
};
|
|
95
|
-
const FORM_STATUS_OPTIONS = [
|
|
96
|
-
{
|
|
97
|
-
value: 'active',
|
|
98
|
-
label: 'Active'
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
value: 'retired',
|
|
102
|
-
label: 'Retired'
|
|
103
|
-
}
|
|
104
|
-
];
|
|
105
|
-
const FORM_LEAD_ROUTING_OPTIONS = [
|
|
106
|
-
{
|
|
107
|
-
value: 'on',
|
|
108
|
-
label: 'On'
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
value: 'off',
|
|
112
|
-
label: 'Off'
|
|
113
|
-
}
|
|
114
|
-
];
|
|
115
|
-
/** What the quick search reads: the name, the slug and the form's id. */ export const FORM_SEARCH_FIELDS = [
|
|
42
|
+
import { FORM_LEAD_ROUTING_OPTIONS, FORM_LIST_FILTER_FIELDS, FORM_LIST_FILTER_HEADERS, FORM_LIST_QUERY, FORM_LIST_SELECT_FIELDS, FORM_STATUS_OPTIONS, formListRequest } from "./form-list-query.js";
|
|
43
|
+
/**
|
|
44
|
+
* The filterable columns the table shows. The rest of the declaration —
|
|
45
|
+
* Status, Lead routing, Campaign — reaches the Filters panel as hidden
|
|
46
|
+
* columns (`hiddenFilterColumns`).
|
|
47
|
+
*/ const FORM_VISIBLE_FILTER_COLUMNS = [
|
|
116
48
|
'displayName',
|
|
117
49
|
'slug',
|
|
118
|
-
'
|
|
50
|
+
'submissions',
|
|
51
|
+
'leads',
|
|
52
|
+
'lastSubmission',
|
|
53
|
+
'updatedAt'
|
|
119
54
|
];
|
|
120
|
-
const FORM_HIDDEN_COLUMNS =
|
|
121
|
-
createdAt: false,
|
|
122
|
-
status: false,
|
|
123
|
-
leadRouting: false,
|
|
124
|
-
campaignIds: false
|
|
125
|
-
};
|
|
126
|
-
/** A form row with the keys its hidden filters match on. */ export const formFilterRow = (form)=>{
|
|
127
|
-
var _form_routing;
|
|
128
|
-
return _extends({}, form, {
|
|
129
|
-
statusKey: isFormArchived(form) ? 'retired' : 'active',
|
|
130
|
-
leadRoutingKey: (form == null ? void 0 : (_form_routing = form.routing) == null ? void 0 : _form_routing.lead) === true ? 'on' : 'off',
|
|
131
|
-
campaignKeys: readCampaignIds(form)
|
|
132
|
-
});
|
|
133
|
-
};
|
|
55
|
+
const FORM_HIDDEN_COLUMNS = hiddenFilterVisibility(FORM_LIST_FILTER_FIELDS, FORM_VISIBLE_FILTER_COLUMNS);
|
|
134
56
|
/**
|
|
135
57
|
* THE FORM CATALOG, WITH ITS SHAPE VISIBLE BEFORE THERE IS ANYTHING IN IT.
|
|
136
58
|
*
|
|
@@ -193,6 +115,7 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
193
115
|
const [createError, setCreateError] = useState(null);
|
|
194
116
|
const [creating, setCreating] = useState(false);
|
|
195
117
|
const [retireError, setRetireError] = useState(null);
|
|
118
|
+
/** Bumped by each retire or restore, so the retired count is asked again. */ const [retiredEpoch, setRetiredEpoch] = useState(0);
|
|
196
119
|
/**
|
|
197
120
|
* Retire a form, or put it back (AGL-2671).
|
|
198
121
|
*
|
|
@@ -213,8 +136,11 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
213
136
|
setRetireError(null);
|
|
214
137
|
try {
|
|
215
138
|
await updateDoc(doc(firestore, 'hosts', hostId, 'forms', formId), {
|
|
216
|
-
archivedAt: retired ? Date.now() : null
|
|
139
|
+
archivedAt: retired ? Date.now() : null,
|
|
140
|
+
// The mirror the list's query asks (AGL-3330).
|
|
141
|
+
retired
|
|
217
142
|
});
|
|
143
|
+
setRetiredEpoch((epoch)=>epoch + 1);
|
|
218
144
|
} catch (unused) {
|
|
219
145
|
setRetireError(retired ? 'Could not retire that form. Try again shortly.' : 'Could not restore that form. Try again shortly.');
|
|
220
146
|
}
|
|
@@ -222,77 +148,70 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
222
148
|
firestore,
|
|
223
149
|
hostId
|
|
224
150
|
]);
|
|
225
|
-
|
|
226
|
-
*
|
|
151
|
+
/*
|
|
152
|
+
* THE LIST IS ITS QUERY (AGL-3330, on the AGL-3321 list query plan).
|
|
153
|
+
*
|
|
154
|
+
* The grid's Filters panel and search box edit the clauses and the words;
|
|
155
|
+
* `useListQuery` puts every one of them on ONE Firestore query and pages
|
|
156
|
+
* that query, so a page is a page of matches and a form whose only match
|
|
157
|
+
* is past the first page is still found. What one query cannot hold — a
|
|
158
|
+
* second range, a second array clause — is refused by name above the list
|
|
159
|
+
* (`ListQueryNotices`) rather than matched over the rows read. See
|
|
160
|
+
* `form-list-query.ts` for every field and the indexes they cost.
|
|
227
161
|
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* documents that HAVE the field, and the resources route stores an
|
|
231
|
-
* allow-list it never checks for presence, so ordering on a name would hide
|
|
232
|
-
* every form created without one rather than mis-sorting the list.
|
|
162
|
+
* Unfiltered it reads in document order, as it always has: `orderBy` on a
|
|
163
|
+
* name would drop every form stored without one rather than mis-sort it.
|
|
233
164
|
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
|
|
165
|
+
* A retired form is a TOMBSTONE, not a row, unless the reader asks for
|
|
166
|
+
* Status (AGL-2671). The query leaves them out (`retired == false`), so a
|
|
167
|
+
* page is a page of forms in use; a Status clause replaces that with the
|
|
168
|
+
* reader's own choice. Retiring a form removes it from the list, and
|
|
169
|
+
* without the Status filter that would be a one-way door: the row is the
|
|
170
|
+
* only route back to Restore.
|
|
171
|
+
*/ const [formClauses, setFormClauses] = useState([]);
|
|
172
|
+
const gridFilter = useListGridFilter({
|
|
173
|
+
clauses: formClauses,
|
|
174
|
+
onChange: setFormClauses,
|
|
175
|
+
selectFields: FORM_LIST_SELECT_FIELDS
|
|
176
|
+
});
|
|
177
|
+
const filtering = gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0;
|
|
178
|
+
const formsCollection = useMemo(()=>collection(firestore, 'hosts', hostId, 'forms'), [
|
|
237
179
|
firestore,
|
|
238
180
|
hostId
|
|
239
|
-
]
|
|
181
|
+
]);
|
|
182
|
+
const { status, rows: forms, hasMore, page, setPage, pageSize, setPageSize, plan } = useListQuery({
|
|
183
|
+
collection: formsCollection,
|
|
184
|
+
declaration: FORM_LIST_QUERY,
|
|
185
|
+
request: formListRequest(gridFilter.clauses, gridFilter.searchWords),
|
|
186
|
+
deps: [
|
|
187
|
+
firestore,
|
|
188
|
+
hostId
|
|
189
|
+
],
|
|
240
190
|
idField: '$id'
|
|
241
191
|
});
|
|
242
192
|
/*
|
|
243
|
-
* A retired form is a TOMBSTONE, not a row — unless the reader asks for the
|
|
244
|
-
* tombstones (AGL-2671). Client-side because it has to be: Firestore cannot
|
|
245
|
-
* ask for the ABSENCE of a field, so a form created through the resources
|
|
246
|
-
* route (which carries no `archivedAt` at all) and one archived later are
|
|
247
|
-
* not one value to filter on. The cost is that a tombstone spends a slot in
|
|
248
|
-
* whichever page it falls in.
|
|
249
|
-
*
|
|
250
|
-
* The Filters panel's Status is how the reader asks (AGL-3317). With no
|
|
251
|
-
* Status clause the list leaves the tombstones out, as it always has, and
|
|
252
|
-
* reads one page; a Status clause hands the panel every row the window
|
|
253
|
-
* read, retired included, and the clause itself picks which to show. That
|
|
254
|
-
* default is the absence of a clause rather than a clause the list starts
|
|
255
|
-
* with, because a starting clause would widen the window on every visit to
|
|
256
|
-
* answer a question nobody asked. Retiring a form removes it from the list,
|
|
257
|
-
* and without the Status filter that would be a one-way door: the row is
|
|
258
|
-
* the only route back to Restore.
|
|
259
|
-
*/ const [formClauses, setFormClauses] = useState([]);
|
|
260
|
-
const askedStatus = formClauses.some((clause)=>clause.field === 'status');
|
|
261
|
-
const formRows = useMemo(()=>formWindow.filter((form)=>askedStatus || !isFormArchived(form)).map(formFilterRow), [
|
|
262
|
-
formWindow,
|
|
263
|
-
askedStatus
|
|
264
|
-
]);
|
|
265
|
-
const formWindowRows = useMemo(()=>formData == null ? void 0 : formData.filter((form)=>askedStatus || !isFormArchived(form)).map(formFilterRow), [
|
|
266
|
-
formData,
|
|
267
|
-
askedStatus
|
|
268
|
-
]);
|
|
269
|
-
/*
|
|
270
193
|
* The Campaign filter's choices: the campaigns placed on this site, by
|
|
271
|
-
* name, and any campaign a
|
|
272
|
-
*
|
|
273
|
-
* filed under it can still be found.
|
|
194
|
+
* name, and any campaign a clause names that the list does not, by its id,
|
|
195
|
+
* so the chip can still be read and cleared.
|
|
274
196
|
*
|
|
275
|
-
* Read
|
|
276
|
-
* the org lookup and up to fifty campaign documents, which a
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
], [
|
|
282
|
-
formData
|
|
283
|
-
]);
|
|
197
|
+
* Read once the reader opens the Filters panel, not on every visit: the
|
|
198
|
+
* names cost the org lookup and up to fifty campaign documents, which a
|
|
199
|
+
* reader who came to open one form would pay for a filter they never
|
|
200
|
+
* touched. Until they land the column has no choices, and the panel lists
|
|
201
|
+
* it a moment later.
|
|
202
|
+
*/ const [wantsCampaigns, setWantsCampaigns] = useState(false);
|
|
284
203
|
const siteCampaigns = useHostCampaigns(hostId, {
|
|
285
|
-
enabled:
|
|
204
|
+
enabled: wantsCampaigns || formClauses.some((clause)=>clause.field === 'campaignIds')
|
|
286
205
|
});
|
|
287
206
|
const formFilterOptions = useMemo(()=>{
|
|
288
207
|
const named = siteCampaigns.options.map((option)=>({
|
|
289
208
|
value: option.value,
|
|
290
209
|
label: option.label
|
|
291
210
|
}));
|
|
292
|
-
const unnamed =
|
|
293
|
-
value
|
|
294
|
-
label:
|
|
295
|
-
}))
|
|
211
|
+
const unnamed = formClauses.filter((clause)=>clause.field === 'campaignIds').flatMap((clause)=>clause.value.split(',')).map((value)=>value.trim()).filter((value)=>value && !named.some((option)=>option.value === value)).map((value)=>({
|
|
212
|
+
value,
|
|
213
|
+
label: value
|
|
214
|
+
}));
|
|
296
215
|
return {
|
|
297
216
|
status: FORM_STATUS_OPTIONS,
|
|
298
217
|
leadRouting: FORM_LEAD_ROUTING_OPTIONS,
|
|
@@ -303,35 +222,35 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
303
222
|
};
|
|
304
223
|
}, [
|
|
305
224
|
siteCampaigns.options,
|
|
306
|
-
|
|
307
|
-
filedCampaignIds
|
|
225
|
+
formClauses
|
|
308
226
|
]);
|
|
309
|
-
const formFilter = usePagedRowsFilter({
|
|
310
|
-
data: formWindowRows,
|
|
311
|
-
rows: formRows,
|
|
312
|
-
hasMore,
|
|
313
|
-
page,
|
|
314
|
-
setPage,
|
|
315
|
-
pageSize,
|
|
316
|
-
setPageSize
|
|
317
|
-
}, {
|
|
318
|
-
fields: FORM_FILTER_FIELDS,
|
|
319
|
-
options: formFilterOptions,
|
|
320
|
-
headers: FORM_FILTER_HEADERS,
|
|
321
|
-
search: FORM_SEARCH_FIELDS,
|
|
322
|
-
clauses: formClauses,
|
|
323
|
-
onChange: setFormClauses
|
|
324
|
-
});
|
|
325
|
-
// The rows on screen: the page, or the page of the matches.
|
|
326
|
-
const forms = formFilter.rows;
|
|
327
227
|
/*
|
|
328
228
|
* The COUNT is a server aggregate, not the length of a page. `forms` is one
|
|
329
229
|
* page, and publishing that as the site's form count would read as room to
|
|
330
230
|
* spare on a site that is already at the ceiling.
|
|
331
231
|
*/ const liveFormCount = useLiveArtifactCount(hostId, 'forms');
|
|
332
|
-
// Pending or refused, the page
|
|
333
|
-
//
|
|
334
|
-
const formsUsed = liveFormCount != null ? liveFormCount :
|
|
232
|
+
// Pending or refused, the page stands in: a LOWER bound, never a confident
|
|
233
|
+
// zero.
|
|
234
|
+
const formsUsed = liveFormCount != null ? liveFormCount : forms.length;
|
|
235
|
+
/*
|
|
236
|
+
* How many forms are retired, for the "keep their slot" line (AGL-2674).
|
|
237
|
+
* An aggregate, because the list no longer reads a retired row unless it is
|
|
238
|
+
* asked for one; asked again after this card retires or restores a form.
|
|
239
|
+
*/ const [retiredCount, setRetiredCount] = useState(null);
|
|
240
|
+
useEffect(()=>{
|
|
241
|
+
let active = true;
|
|
242
|
+
getCountFromServer(query(formsCollection, where('retired', '==', true))).then((snapshot)=>{
|
|
243
|
+
if (active) setRetiredCount(snapshot.data().count);
|
|
244
|
+
}).catch(()=>{
|
|
245
|
+
// The line is a courtesy; without the count it is simply not shown.
|
|
246
|
+
});
|
|
247
|
+
return ()=>{
|
|
248
|
+
active = false;
|
|
249
|
+
};
|
|
250
|
+
}, [
|
|
251
|
+
formsCollection,
|
|
252
|
+
retiredEpoch
|
|
253
|
+
]);
|
|
335
254
|
/**
|
|
336
255
|
* Name first, then create (AGL-700).
|
|
337
256
|
*
|
|
@@ -482,21 +401,25 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
482
401
|
return (value == null ? void 0 : (_value_toLocaleString = value.toLocaleString) == null ? void 0 : _value_toLocaleString.call(value)) || '--';
|
|
483
402
|
}
|
|
484
403
|
},
|
|
404
|
+
/*
|
|
405
|
+
* Status and Lead routing, hidden until Columns shows them (AGL-3330).
|
|
406
|
+
* Each is a select the Filters panel offers either way, and draws the
|
|
407
|
+
* choice its value names. Status reads the FACT, `archivedAt`, rather
|
|
408
|
+
* than the `retired` mirror the query asks, so a form the backfill has
|
|
409
|
+
* not reached yet still reads true.
|
|
410
|
+
*/ {
|
|
411
|
+
field: 'status',
|
|
412
|
+
headerName: 'Status',
|
|
413
|
+
minWidth: 110,
|
|
414
|
+
valueGetter: (_value, row)=>String(isFormArchived(row))
|
|
415
|
+
},
|
|
485
416
|
{
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
valueGetter: (value)=>{
|
|
493
|
-
var _ref;
|
|
494
|
-
var _value_toDate;
|
|
495
|
-
return (_ref = value == null ? void 0 : (_value_toDate = value.toDate) == null ? void 0 : _value_toDate.call(value)) != null ? _ref : null;
|
|
496
|
-
},
|
|
497
|
-
valueFormatter: (value)=>{
|
|
498
|
-
var _value_toLocaleString;
|
|
499
|
-
return (value == null ? void 0 : (_value_toLocaleString = value.toLocaleString) == null ? void 0 : _value_toLocaleString.call(value)) || '--';
|
|
417
|
+
field: 'leadRouting',
|
|
418
|
+
headerName: 'Lead routing',
|
|
419
|
+
minWidth: 120,
|
|
420
|
+
valueGetter: (_value, row)=>{
|
|
421
|
+
var _row_routing;
|
|
422
|
+
return String((row == null ? void 0 : (_row_routing = row.routing) == null ? void 0 : _row_routing.lead) === true);
|
|
500
423
|
}
|
|
501
424
|
},
|
|
502
425
|
listActionsColumn((row)=>{
|
|
@@ -613,7 +536,7 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
613
536
|
limit: Aglyn.checkQuota(org, 'formsPerHost', formsUsed).limit,
|
|
614
537
|
noun: "form"
|
|
615
538
|
}),
|
|
616
|
-
|
|
539
|
+
retiredCount ? /*#__PURE__*/ _jsx(Typography, {
|
|
617
540
|
variant: "caption",
|
|
618
541
|
color: "text.secondary",
|
|
619
542
|
children: 'Retired forms keep their slot'
|
|
@@ -656,21 +579,43 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
656
579
|
},
|
|
657
580
|
children: retireError
|
|
658
581
|
}) : null,
|
|
659
|
-
/*#__PURE__*/ _jsx(ListFilterChips,
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
582
|
+
/*#__PURE__*/ _jsx(ListFilterChips, {
|
|
583
|
+
fields: FORM_LIST_FILTER_FIELDS,
|
|
584
|
+
headers: FORM_LIST_FILTER_HEADERS,
|
|
585
|
+
clauses: gridFilter.clauses,
|
|
586
|
+
onChange: gridFilter.setClauses,
|
|
587
|
+
options: formFilterOptions
|
|
588
|
+
}),
|
|
589
|
+
/*#__PURE__*/ _jsx(ListQueryNotices, {
|
|
590
|
+
refused: listQueryRefusals(plan.refused, {
|
|
591
|
+
fields: FORM_LIST_FILTER_FIELDS,
|
|
592
|
+
headers: FORM_LIST_FILTER_HEADERS,
|
|
593
|
+
options: formFilterOptions
|
|
594
|
+
}),
|
|
595
|
+
notices: plan.notices
|
|
596
|
+
}),
|
|
665
597
|
/*#__PURE__*/ _jsx(ListTable, _extends({
|
|
666
598
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
667
|
-
columns:
|
|
599
|
+
columns: listFilterGridColumns(columns, FORM_LIST_FILTER_FIELDS, formFilterOptions, FORM_LIST_FILTER_HEADERS),
|
|
668
600
|
initialState: {
|
|
669
601
|
columns: {
|
|
670
602
|
columnVisibilityModel: FORM_HIDDEN_COLUMNS
|
|
671
603
|
}
|
|
604
|
+
},
|
|
605
|
+
/*
|
|
606
|
+
* The grid must NOT also filter. The query answers the panel and
|
|
607
|
+
* the search, so a second pass in the browser could only drop rows
|
|
608
|
+
* that already matched — it compares what a column DRAWS, and the
|
|
609
|
+
* query compares what the document stores.
|
|
610
|
+
*/ filterMode: "server",
|
|
611
|
+
filterModel: gridFilter.filterModel,
|
|
612
|
+
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
613
|
+
quickFilter: true,
|
|
614
|
+
// The Campaign filter's names are read once the panel is opened.
|
|
615
|
+
onPreferencePanelOpen: (params)=>{
|
|
616
|
+
if (params.openedPanelValue === 'filters') setWantsCampaigns(true);
|
|
672
617
|
}
|
|
673
|
-
},
|
|
618
|
+
}, filtering ? {
|
|
674
619
|
noRowsLabel: 'No forms match these filters'
|
|
675
620
|
} : {
|
|
676
621
|
noRowsLabel: 'No forms yet',
|
|
@@ -691,7 +636,14 @@ const FORM_HIDDEN_COLUMNS = {
|
|
|
691
636
|
// Paged by the footer below, so the grid must not also slice.
|
|
692
637
|
hideFooter: true
|
|
693
638
|
})),
|
|
694
|
-
/*#__PURE__*/ _jsx(ListPagination,
|
|
639
|
+
/*#__PURE__*/ _jsx(ListPagination, {
|
|
640
|
+
page: page,
|
|
641
|
+
pageSize: pageSize,
|
|
642
|
+
rowCount: forms.length,
|
|
643
|
+
hasMore: hasMore,
|
|
644
|
+
onPageChange: setPage,
|
|
645
|
+
onPageSizeChange: setPageSize
|
|
646
|
+
}),
|
|
695
647
|
/*#__PURE__*/ _jsx(CreateArtifactDrawer, {
|
|
696
648
|
open: createOpen,
|
|
697
649
|
onClose: ()=>setCreateOpen(false),
|