@aglyn/plugins-data 1.0.0-beta.195 → 1.0.0-beta.197
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 +9 -9
- package/src/lib/components/dataset-record-filter.d.ts +77 -0
- package/src/lib/components/dataset-record-filter.js +154 -0
- package/src/lib/components/dataset-record-filter.js.map +1 -0
- package/src/lib/components/host-datasets-card.component.js +191 -171
- package/src/lib/components/host-datasets-card.component.js.map +1 -1
|
@@ -16,21 +16,25 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
-
import {
|
|
19
|
+
import { checkDatasetQuota, checkEntitlement, checkQuota, coerceDocumentValues, datasetIntegrityUpdate, datasetValueToInput, effectiveDatasetModel, formatDatasetValue, getCustomFieldType, modelFromFieldEntries, parseDatasetFieldEntries, pluginDocsHelp, validateDocument } from "@aglyn/aglyn";
|
|
20
20
|
import { exportShortfall, mapImportColumns, parseImportRows } from "../model/index.js";
|
|
21
21
|
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
22
|
+
import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
|
|
22
23
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
24
|
+
import { ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
23
25
|
import QuotaReadoutComponent from "@aglyn/shared-ui-jsx/components/quota-readout.component";
|
|
24
|
-
import {
|
|
26
|
+
import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
|
|
27
|
+
import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
|
|
25
28
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
26
29
|
import { Timestamp } from "@aglyn/shared-util-timestamp";
|
|
27
|
-
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack,
|
|
30
|
+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TextField, Typography } from "@mui/material";
|
|
28
31
|
import { collection, deleteDoc, deleteField, doc, documentId, getCountFromServer, getDocs, limit, orderBy, query, where, setDoc, writeBatch } from "firebase/firestore";
|
|
29
32
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
30
33
|
import { useFirestore, useFirestoreCollection, useHostActivityLogger, useOrgDataScope, usePagedCollection, useScopeTokens, useUser } from "@aglyn/tenant-feature-instance";
|
|
31
34
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
32
35
|
import { DatasetSchemaDialog } from "./dataset-schema-dialog.component.js";
|
|
33
36
|
import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
37
|
+
import { datasetRecordFilter, matchRecordWindow, planRecordFilter, RECORD_FILTER_WINDOW, recordColumn } from "./dataset-record-filter.js";
|
|
34
38
|
/**
|
|
35
39
|
* How many existing records a keyed CSV import may consult.
|
|
36
40
|
*
|
|
@@ -62,7 +66,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
62
66
|
* dark-launch rule as everywhere (no plan, no gate). The host path is
|
|
63
67
|
* the pre-migration fallback for hosts not yet org-wired.
|
|
64
68
|
*/ export function HostDatasetsCard(props) {
|
|
65
|
-
var _datasets_find, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
|
|
69
|
+
var _datasets_find, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
|
|
66
70
|
const { hostId } = props;
|
|
67
71
|
// `dataScope` is null until the async org lookup settles (AGL-1061), and
|
|
68
72
|
// for a host with no owning org — there is no host fallback to act on
|
|
@@ -206,6 +210,10 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
206
210
|
const fields = useMemo(()=>model.order, [
|
|
207
211
|
model
|
|
208
212
|
]);
|
|
213
|
+
// The records grid's Filters panel, from the dataset's own schema.
|
|
214
|
+
const recordFilter = useMemo(()=>datasetRecordFilter(model), [
|
|
215
|
+
model
|
|
216
|
+
]);
|
|
209
217
|
// Guarded on BOTH the scope and a real selection (AGL-1440): with no
|
|
210
218
|
// datasets, `selected` never resolves, and `datasets/-none-/records` is not
|
|
211
219
|
// an empty read for everyone — the AGL-1044 scoped-sharing rules deny it
|
|
@@ -254,13 +262,93 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
254
262
|
* `sortDatasetRecords` is deliberately NOT applied to the page. Re-sorting a
|
|
255
263
|
* window of an id-ordered walk by `order` is the same lie the old code told:
|
|
256
264
|
* rows would run in one order within a page and another across pages.
|
|
257
|
-
|
|
265
|
+
* The grid's column sort is off for the same reason.
|
|
266
|
+
*
|
|
267
|
+
* ## Filtering, and what reaches every record
|
|
268
|
+
*
|
|
269
|
+
* The grid's Filters panel and quick search narrow the walk through the
|
|
270
|
+
* record's `filterKeys` (`datasetFilterKeys`): a token array Firestore's
|
|
271
|
+
* automatic single-field index serves with `array-contains` beside the same
|
|
272
|
+
* document-name order, so it needs no composite index and no index
|
|
273
|
+
* deploy. ONE condition is served that way — the first clause a token can
|
|
274
|
+
* answer (`datasetFilterToken`), else the first search word — and when it is
|
|
275
|
+
* the only condition, the walk pages through every matching record exactly
|
|
276
|
+
* as it pages through all of them.
|
|
277
|
+
*
|
|
278
|
+
* Anything more — a second clause, a second word, a multi-word `contains`,
|
|
279
|
+
* or a clause no token answers (number and date ranges, empty checks,
|
|
280
|
+
* negations) — cannot be one `array-contains`. Those read a WINDOW of
|
|
281
|
+
* {@link RECORD_FILTER_WINDOW} records under the served condition (or the
|
|
282
|
+
* plain walk when none is servable), match the rest in memory, and page the
|
|
283
|
+
* matches here; the caption says when the window was full. Ranges on
|
|
284
|
+
* numbers and dates are therefore always window-only.
|
|
285
|
+
*/ const gridFilter = useListGridFilter({
|
|
286
|
+
selectFields: recordFilter.selectFields
|
|
287
|
+
});
|
|
288
|
+
// A clause on a field this dataset does not have (the reader switched
|
|
289
|
+
// datasets) narrows nothing and is not shown.
|
|
290
|
+
const recordClauses = useMemo(()=>gridFilter.clauses.filter((clause)=>recordFilter.fields.some((field)=>field.column === clause.field)), [
|
|
291
|
+
gridFilter.clauses,
|
|
292
|
+
recordFilter.fields
|
|
293
|
+
]);
|
|
294
|
+
const recordPlan = useMemo(()=>planRecordFilter(model, recordClauses, gridFilter.searchWords), [
|
|
295
|
+
model,
|
|
296
|
+
recordClauses,
|
|
297
|
+
gridFilter.searchWords
|
|
298
|
+
]);
|
|
299
|
+
/** The walk, narrowed by the served token when there is one. */ const recordsQuery = useCallback((cap)=>dataScope && (selected == null ? void 0 : selected.$id) ? query(collection(firestore, dataScope[0], dataScope[1], 'datasets', selected.$id, 'records'), ...recordPlan.token ? [
|
|
300
|
+
where('filterKeys', 'array-contains', recordPlan.token)
|
|
301
|
+
] : [], orderBy(documentId()), limit(cap)) : null, [
|
|
258
302
|
firestore,
|
|
259
303
|
dataScope,
|
|
260
|
-
selected == null ? void 0 : selected.$id
|
|
304
|
+
selected == null ? void 0 : selected.$id,
|
|
305
|
+
recordPlan.token
|
|
306
|
+
]);
|
|
307
|
+
const { rows: pagedRecords, hasMore: hasMorePaged, page: pagedPage, setPage: setPagedPage, pageSize: recordPageSize, setPageSize: setPagedPageSize } = usePagedCollection((pageLimit)=>recordPlan.windowed ? null : recordsQuery(pageLimit), [
|
|
308
|
+
recordsQuery,
|
|
309
|
+
recordPlan.windowed
|
|
310
|
+
], {
|
|
311
|
+
idField: '$id'
|
|
312
|
+
});
|
|
313
|
+
// The window: one read past the cap, so "the window was full" is a fact.
|
|
314
|
+
const { data: windowDocs } = useFirestoreCollection(()=>recordPlan.windowed ? recordsQuery(RECORD_FILTER_WINDOW + 1) : null, [
|
|
315
|
+
recordsQuery,
|
|
316
|
+
recordPlan.windowed
|
|
261
317
|
], {
|
|
262
318
|
idField: '$id'
|
|
263
319
|
});
|
|
320
|
+
const windowRecords = useMemo(()=>recordPlan.windowed ? (windowDocs != null ? windowDocs : []).slice(0, RECORD_FILTER_WINDOW) : [], [
|
|
321
|
+
recordPlan.windowed,
|
|
322
|
+
windowDocs
|
|
323
|
+
]);
|
|
324
|
+
const windowFull = recordPlan.windowed && ((_ref = windowDocs == null ? void 0 : windowDocs.length) != null ? _ref : 0) > RECORD_FILTER_WINDOW;
|
|
325
|
+
const windowMatches = useMemo(()=>recordPlan.windowed ? matchRecordWindow(model, recordFilter.fields, windowRecords, recordPlan) : [], [
|
|
326
|
+
recordPlan,
|
|
327
|
+
model,
|
|
328
|
+
recordFilter.fields,
|
|
329
|
+
windowRecords
|
|
330
|
+
]);
|
|
331
|
+
const [windowPage, setWindowPage] = useState(0);
|
|
332
|
+
const [windowPageSize, setWindowPageSize] = useState(recordPageSize);
|
|
333
|
+
useEffect(()=>{
|
|
334
|
+
setWindowPage(0);
|
|
335
|
+
}, [
|
|
336
|
+
recordPlan,
|
|
337
|
+
selected == null ? void 0 : selected.$id
|
|
338
|
+
]);
|
|
339
|
+
/** Every record this card has read: the page, or the whole window. */ const records = recordPlan.windowed ? windowRecords : pagedRecords;
|
|
340
|
+
/** The rows on screen. */ const shownRecords = recordPlan.windowed ? windowMatches.slice(windowPage * windowPageSize, (windowPage + 1) * windowPageSize) : pagedRecords;
|
|
341
|
+
const recordPage = recordPlan.windowed ? windowPage : pagedPage;
|
|
342
|
+
const setRecordPage = recordPlan.windowed ? setWindowPage : setPagedPage;
|
|
343
|
+
const hasMoreRecords = recordPlan.windowed ? (windowPage + 1) * windowPageSize < windowMatches.length : hasMorePaged;
|
|
344
|
+
const setRecordPageSize = useCallback((next)=>{
|
|
345
|
+
setPagedPageSize(next);
|
|
346
|
+
setWindowPageSize(next);
|
|
347
|
+
setWindowPage(0);
|
|
348
|
+
}, [
|
|
349
|
+
setPagedPageSize
|
|
350
|
+
]);
|
|
351
|
+
const filteringRecords = recordClauses.length > 0 || gridFilter.searchWords.some((word)=>word.trim());
|
|
264
352
|
/**
|
|
265
353
|
* The two HEAD-COUNTS on this card are server aggregates, not the lengths
|
|
266
354
|
* of the two capped listeners above (AGL-1716, the AGL-1706 shape).
|
|
@@ -358,45 +446,6 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
358
446
|
// changes nothing about which way it can be wrong.
|
|
359
447
|
const datasetCount = serverDatasetCount != null ? serverDatasetCount : datasets.length;
|
|
360
448
|
const recordCount = serverRecordCount && serverRecordCount.datasetId === countedDatasetId ? serverRecordCount.count : records.length;
|
|
361
|
-
/*
|
|
362
|
-
* NO FILTERS PANEL AND NO SEARCH over the records (AGL-3317). The walk is
|
|
363
|
-
* cursor-paged by document name, and a record's fields live under
|
|
364
|
-
* `values`, which the index configuration exempts from single-field
|
|
365
|
-
* indexing — so no `where` on any field can be served, and a filter or a
|
|
366
|
-
* search over the loaded page would present one page as the dataset. The
|
|
367
|
-
* Filter and Sort boxes stay because they say they apply to this page, and
|
|
368
|
-
* the Dataset select is a scope: it picks which collection is walked.
|
|
369
|
-
*/ // Query layer (AGL-181): the same evaluator the renderer uses, applied in
|
|
370
|
-
// memory over the rows in front of the reader. That window is now one PAGE
|
|
371
|
-
// rather than a 500-row sample (AGL-2501), which is a smaller claim and a
|
|
372
|
-
// true one — the helper text says which, because a filter box that looks
|
|
373
|
-
// like it searches the collection and does not is worse than one that says
|
|
374
|
-
// so.
|
|
375
|
-
const [filterText, setFilterText] = useState('');
|
|
376
|
-
const [sortText, setSortText] = useState('');
|
|
377
|
-
const visibleRecords = useMemo(()=>{
|
|
378
|
-
const where = parseDatasetFilter(filterText);
|
|
379
|
-
const orderBy = parseDatasetSort(sortText);
|
|
380
|
-
if (!where && !orderBy) return records;
|
|
381
|
-
const rows = records.map((record)=>{
|
|
382
|
-
var _record_values;
|
|
383
|
-
return _extends({
|
|
384
|
-
__record: record
|
|
385
|
-
}, (_record_values = record.values) != null ? _record_values : {});
|
|
386
|
-
});
|
|
387
|
-
return applyDatasetQuery(model, rows, _extends({}, where ? {
|
|
388
|
-
where: [
|
|
389
|
-
where
|
|
390
|
-
]
|
|
391
|
-
} : {}, orderBy ? {
|
|
392
|
-
orderBy
|
|
393
|
-
} : {})).map((row)=>row.__record);
|
|
394
|
-
}, [
|
|
395
|
-
records,
|
|
396
|
-
filterText,
|
|
397
|
-
sortText,
|
|
398
|
-
model
|
|
399
|
-
]);
|
|
400
449
|
// --- Create dataset -----------------------------------------------------
|
|
401
450
|
const [creator, setCreator] = useState(null);
|
|
402
451
|
const handleOpenCreator = useCallback(()=>{
|
|
@@ -427,7 +476,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
427
476
|
]);
|
|
428
477
|
// Entries are human names — "Roast preference" → stable id
|
|
429
478
|
// `roast_preference` with the pretty name kept for headers (AGL-558).
|
|
430
|
-
const creatorEntries = parseDatasetFieldEntries((
|
|
479
|
+
const creatorEntries = parseDatasetFieldEntries((_ref1 = creator == null ? void 0 : creator.fields) != null ? _ref1 : '');
|
|
431
480
|
const creatorFields = creatorEntries.map((entry)=>entry.id);
|
|
432
481
|
const handleCreate = useCallback(async ()=>{
|
|
433
482
|
// No org, no create. The `else` that used to sit below this was the
|
|
@@ -694,7 +743,8 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
694
743
|
* an edit landing underneath updates the open dialog. When the row leaves
|
|
695
744
|
* the page — deleted, or paged past — this answers `null` and the dialog
|
|
696
745
|
* closes, which is the honest response to a record no longer in front of
|
|
697
|
-
* this reader. Nothing here queries: `records` is the page
|
|
746
|
+
* this reader. Nothing here queries: `records` is the page (or filter window)
|
|
747
|
+
* query's answer.
|
|
698
748
|
*/ const [viewerId, setViewerId] = useState(null);
|
|
699
749
|
const viewerRecord = useMemo(()=>{
|
|
700
750
|
var _records_find;
|
|
@@ -765,6 +815,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
765
815
|
mergeFields: [
|
|
766
816
|
'values',
|
|
767
817
|
'referencedIds',
|
|
818
|
+
'filterKeys',
|
|
768
819
|
'updatedAt'
|
|
769
820
|
]
|
|
770
821
|
});
|
|
@@ -1178,10 +1229,69 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
1178
1229
|
enqueueSnackbar,
|
|
1179
1230
|
logActivity
|
|
1180
1231
|
]);
|
|
1232
|
+
/*
|
|
1233
|
+
* One column per schema field, filterable as far as `datasetRecordFilter`
|
|
1234
|
+
* declares it. The row OPENS the record, read-only (`onOpen`); the actions
|
|
1235
|
+
* column holds the keyboard's way in (`View`, a real button) beside Edit
|
|
1236
|
+
* and Delete, and a press anywhere in it never opens the row.
|
|
1237
|
+
*/ const recordGridColumns = useMemo(()=>listFilterGridColumns([
|
|
1238
|
+
...fields.map((fieldId)=>{
|
|
1239
|
+
var _ref;
|
|
1240
|
+
const field = model.fields[fieldId];
|
|
1241
|
+
return {
|
|
1242
|
+
field: recordColumn(fieldId),
|
|
1243
|
+
headerName: (_ref = field == null ? void 0 : field.name) != null ? _ref : fieldId,
|
|
1244
|
+
// Field descriptions (AGL-560) surface as header hints.
|
|
1245
|
+
description: field == null ? void 0 : field.description,
|
|
1246
|
+
flex: 1,
|
|
1247
|
+
minWidth: 140,
|
|
1248
|
+
sortable: false,
|
|
1249
|
+
renderCell: ({ row })=>{
|
|
1250
|
+
var _row_values, _row_values1;
|
|
1251
|
+
return (field == null ? void 0 : field.type) === 'reference' ? referenceLabel(fieldId, (_row_values = row.values) == null ? void 0 : _row_values[fieldId]) || '--' : field ? formatDatasetValue(field, (_row_values1 = row.values) == null ? void 0 : _row_values1[fieldId]) || '--' : '--';
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
}),
|
|
1255
|
+
listActionsColumn((record)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
1256
|
+
direction: "row",
|
|
1257
|
+
sx: {
|
|
1258
|
+
height: '100%',
|
|
1259
|
+
alignItems: 'center',
|
|
1260
|
+
justifyContent: 'flex-end'
|
|
1261
|
+
},
|
|
1262
|
+
children: [
|
|
1263
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1264
|
+
size: "small",
|
|
1265
|
+
onClick: ()=>setViewerId(record.$id),
|
|
1266
|
+
children: 'View'
|
|
1267
|
+
}),
|
|
1268
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1269
|
+
size: "small",
|
|
1270
|
+
onClick: handleOpenRecord(record),
|
|
1271
|
+
children: 'Edit'
|
|
1272
|
+
}),
|
|
1273
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1274
|
+
size: "small",
|
|
1275
|
+
color: "error",
|
|
1276
|
+
onClick: handleDeleteRecord(record),
|
|
1277
|
+
children: 'Delete'
|
|
1278
|
+
})
|
|
1279
|
+
]
|
|
1280
|
+
}), {
|
|
1281
|
+
width: 220
|
|
1282
|
+
})
|
|
1283
|
+
], recordFilter.fields, recordFilter.options, recordFilter.headers), [
|
|
1284
|
+
fields,
|
|
1285
|
+
model,
|
|
1286
|
+
referenceLabel,
|
|
1287
|
+
handleOpenRecord,
|
|
1288
|
+
handleDeleteRecord,
|
|
1289
|
+
recordFilter
|
|
1290
|
+
]);
|
|
1181
1291
|
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
1182
1292
|
header: 'Data',
|
|
1183
1293
|
help: pluginDocsHelp('datasets', {
|
|
1184
|
-
anchor: '#
|
|
1294
|
+
anchor: '#filter-records'
|
|
1185
1295
|
}),
|
|
1186
1296
|
contentGutterX: true,
|
|
1187
1297
|
contentGutterY: true,
|
|
@@ -1210,7 +1320,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
1210
1320
|
select: true,
|
|
1211
1321
|
size: "small",
|
|
1212
1322
|
label: "Dataset",
|
|
1213
|
-
value: (
|
|
1323
|
+
value: (_ref2 = selected == null ? void 0 : selected.$id) != null ? _ref2 : '',
|
|
1214
1324
|
onChange: (event)=>setSelectedId(event.target.value),
|
|
1215
1325
|
sx: {
|
|
1216
1326
|
minWidth: 180
|
|
@@ -1269,130 +1379,40 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
1269
1379
|
limit: checkQuota(org, 'recordsPerDataset', recordCount).limit,
|
|
1270
1380
|
noun: "record"
|
|
1271
1381
|
}) : null,
|
|
1272
|
-
selected && records.length > 0 ? /*#__PURE__*/ _jsxs(
|
|
1273
|
-
direction: "row",
|
|
1274
|
-
spacing: 1,
|
|
1382
|
+
selected && (records.length > 0 || recordPage > 0 || filteringRecords) ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
1275
1383
|
children: [
|
|
1276
|
-
/*#__PURE__*/ _jsx(
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
onChange:
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
minWidth: 220
|
|
1284
|
-
}
|
|
1384
|
+
/*#__PURE__*/ _jsx(ListFilterChips, {
|
|
1385
|
+
fields: recordFilter.fields,
|
|
1386
|
+
headers: recordFilter.headers,
|
|
1387
|
+
clauses: recordClauses,
|
|
1388
|
+
onChange: gridFilter.setClauses,
|
|
1389
|
+
options: recordFilter.options,
|
|
1390
|
+
servedField: recordPlan.windowed ? null : recordPlan.servedField
|
|
1285
1391
|
}),
|
|
1286
|
-
/*#__PURE__*/ _jsx(
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
children: [
|
|
1306
|
-
fields.map((fieldId)=>{
|
|
1307
|
-
var _field_name, _ref;
|
|
1308
|
-
const field = model.fields[fieldId];
|
|
1309
|
-
// Field descriptions (AGL-560) surface as header hints.
|
|
1310
|
-
return /*#__PURE__*/ _jsx(TableCell, {
|
|
1311
|
-
children: (field == null ? void 0 : field.description) ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
1312
|
-
title: field.description,
|
|
1313
|
-
children: /*#__PURE__*/ _jsx("span", {
|
|
1314
|
-
style: {
|
|
1315
|
-
cursor: 'help'
|
|
1316
|
-
},
|
|
1317
|
-
children: (_field_name = field.name) != null ? _field_name : fieldId
|
|
1318
|
-
})
|
|
1319
|
-
}) : (_ref = field == null ? void 0 : field.name) != null ? _ref : fieldId
|
|
1320
|
-
}, fieldId);
|
|
1321
|
-
}),
|
|
1322
|
-
/*#__PURE__*/ _jsx(TableCell, {
|
|
1323
|
-
align: "right",
|
|
1324
|
-
children: 'Actions'
|
|
1325
|
-
})
|
|
1326
|
-
]
|
|
1327
|
-
})
|
|
1328
|
-
}),
|
|
1329
|
-
/*#__PURE__*/ _jsx(TableBody, {
|
|
1330
|
-
children: visibleRecords.map((record)=>/*
|
|
1331
|
-
The row OPENS the record, read-only. Reading a record and
|
|
1332
|
-
changing one used to be the same gesture: `Edit` was the only
|
|
1333
|
-
way to see a document's full contents, so every look opened a
|
|
1334
|
-
form whose primary button writes.
|
|
1335
|
-
|
|
1336
|
-
The click is the mouse affordance and nothing else — a `<tr>`
|
|
1337
|
-
cannot be made into a real activatable control without
|
|
1338
|
-
overriding the `row` role a table depends on. The keyboard's
|
|
1339
|
-
affordance is the `View` button in the actions cluster: a
|
|
1340
|
-
genuine `<button>`, in the tab order, announced as one, with
|
|
1341
|
-
the theme's focus ring. Both reach the same handler.
|
|
1342
|
-
*/ /*#__PURE__*/ _jsxs(TableRow, {
|
|
1343
|
-
hover: true,
|
|
1344
|
-
onClick: ()=>setViewerId(record.$id),
|
|
1345
|
-
sx: {
|
|
1346
|
-
cursor: 'pointer'
|
|
1347
|
-
},
|
|
1348
|
-
children: [
|
|
1349
|
-
fields.map((fieldId)=>{
|
|
1350
|
-
var _model_fields_fieldId, _record_values, _record_values1;
|
|
1351
|
-
return /*#__PURE__*/ _jsx(TableCell, {
|
|
1352
|
-
children: ((_model_fields_fieldId = model.fields[fieldId]) == null ? void 0 : _model_fields_fieldId.type) === 'reference' ? referenceLabel(fieldId, (_record_values = record.values) == null ? void 0 : _record_values[fieldId]) || '--' : model.fields[fieldId] ? formatDatasetValue(model.fields[fieldId], (_record_values1 = record.values) == null ? void 0 : _record_values1[fieldId]) || '--' : '--'
|
|
1353
|
-
}, fieldId);
|
|
1354
|
-
}),
|
|
1355
|
-
/*#__PURE__*/ _jsxs(TableCell, {
|
|
1356
|
-
align: "right",
|
|
1357
|
-
sx: {
|
|
1358
|
-
whiteSpace: 'nowrap'
|
|
1359
|
-
},
|
|
1360
|
-
/*
|
|
1361
|
-
Every control in this cell is its OWN action. Without
|
|
1362
|
-
this the row's handler fires too, so `Delete` would open
|
|
1363
|
-
the viewer behind its own destructive write and `Edit`
|
|
1364
|
-
would leave a read-only dialog stacked under the form —
|
|
1365
|
-
the kind of defect nobody reports and everybody works
|
|
1366
|
-
around.
|
|
1367
|
-
*/ onClick: (event)=>event.stopPropagation(),
|
|
1368
|
-
children: [
|
|
1369
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1370
|
-
size: "small",
|
|
1371
|
-
onClick: ()=>setViewerId(record.$id),
|
|
1372
|
-
children: 'View'
|
|
1373
|
-
}),
|
|
1374
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1375
|
-
size: "small",
|
|
1376
|
-
onClick: handleOpenRecord(record),
|
|
1377
|
-
children: 'Edit'
|
|
1378
|
-
}),
|
|
1379
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1380
|
-
size: "small",
|
|
1381
|
-
color: "error",
|
|
1382
|
-
onClick: handleDeleteRecord(record),
|
|
1383
|
-
children: 'Delete'
|
|
1384
|
-
})
|
|
1385
|
-
]
|
|
1386
|
-
})
|
|
1387
|
-
]
|
|
1388
|
-
}, record.$id))
|
|
1389
|
-
})
|
|
1390
|
-
]
|
|
1392
|
+
windowFull ? /*#__PURE__*/ _jsx(Typography, {
|
|
1393
|
+
variant: "caption",
|
|
1394
|
+
color: "text.secondary",
|
|
1395
|
+
children: `Filtering the first ${RECORD_FILTER_WINDOW.toLocaleString()} records` + (recordPlan.token ? ' that match the first condition' : '') + ' — one filter or one search word on its own reaches every record.'
|
|
1396
|
+
}) : null,
|
|
1397
|
+
/*#__PURE__*/ _jsx(ListTable, {
|
|
1398
|
+
"aria-label": "Records",
|
|
1399
|
+
rows: shownRecords,
|
|
1400
|
+
columns: recordGridColumns,
|
|
1401
|
+
onOpen: (id)=>setViewerId(id),
|
|
1402
|
+
// One page of the walk (or of the window's matches), turned by
|
|
1403
|
+
// the footer below: the grid neither slices nor filters it.
|
|
1404
|
+
hideFooter: true,
|
|
1405
|
+
filterMode: "server",
|
|
1406
|
+
filterModel: gridFilter.filterModel,
|
|
1407
|
+
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
1408
|
+
quickFilter: true,
|
|
1409
|
+
disableColumnSorting: true,
|
|
1410
|
+
noRowsLabel: filteringRecords ? 'No records match these filters' : 'No records yet.'
|
|
1391
1411
|
}),
|
|
1392
1412
|
/*#__PURE__*/ _jsx(ListPagination, {
|
|
1393
1413
|
page: recordPage,
|
|
1394
1414
|
pageSize: recordPageSize,
|
|
1395
|
-
rowCount:
|
|
1415
|
+
rowCount: shownRecords.length,
|
|
1396
1416
|
hasMore: hasMoreRecords,
|
|
1397
1417
|
onPageChange: setRecordPage,
|
|
1398
1418
|
onPageSizeChange: setRecordPageSize
|
|
@@ -1444,7 +1464,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
1444
1464
|
children: [
|
|
1445
1465
|
/*#__PURE__*/ _jsx(TextField, {
|
|
1446
1466
|
label: "Name",
|
|
1447
|
-
value: (
|
|
1467
|
+
value: (_ref3 = creator == null ? void 0 : creator.name) != null ? _ref3 : '',
|
|
1448
1468
|
onChange: (event)=>setCreator((prev)=>prev ? _extends({}, prev, {
|
|
1449
1469
|
name: event.target.value
|
|
1450
1470
|
}) : prev),
|
|
@@ -1457,7 +1477,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
1457
1477
|
}),
|
|
1458
1478
|
/*#__PURE__*/ _jsx(TextField, {
|
|
1459
1479
|
label: "Fields",
|
|
1460
|
-
value: (
|
|
1480
|
+
value: (_ref4 = creator == null ? void 0 : creator.fields) != null ? _ref4 : '',
|
|
1461
1481
|
onChange: (event)=>setCreator((prev)=>prev ? _extends({}, prev, {
|
|
1462
1482
|
fields: event.target.value
|
|
1463
1483
|
}) : prev),
|
|
@@ -1691,7 +1711,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
1691
1711
|
minRows: 6,
|
|
1692
1712
|
size: "small",
|
|
1693
1713
|
label: "CSV (with header row) or JSON array",
|
|
1694
|
-
value: (
|
|
1714
|
+
value: (_ref5 = importer == null ? void 0 : importer.text) != null ? _ref5 : '',
|
|
1695
1715
|
onChange: (event)=>setImporter((prev)=>prev ? _extends({}, prev, {
|
|
1696
1716
|
text: event.target.value
|
|
1697
1717
|
}) : prev),
|
|
@@ -1704,7 +1724,7 @@ import { DatasetRecordDialog } from "./dataset-record-dialog.component.js";
|
|
|
1704
1724
|
select: true,
|
|
1705
1725
|
size: "small",
|
|
1706
1726
|
label: "Match on field (upsert)",
|
|
1707
|
-
value: (
|
|
1727
|
+
value: (_ref6 = importer == null ? void 0 : importer.keyField) != null ? _ref6 : '',
|
|
1708
1728
|
onChange: (event)=>setImporter((prev)=>prev ? _extends({}, prev, {
|
|
1709
1729
|
keyField: event.target.value
|
|
1710
1730
|
}) : prev),
|