nexus-shared 4.0.0 → 4.1.0
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/CHANGELOG.md +795 -0
- package/README.md +220 -14
- package/dist/Client.Index.d.ts +13 -0
- package/dist/Client.Index.js +13 -0
- package/dist/Components/Chats/ChatComposer.d.ts +7 -1
- package/dist/Components/Chats/ChatComposer.js +9 -6
- package/dist/Components/Chats/ChatParts.d.ts +15 -2
- package/dist/Components/Chats/ChatParts.js +15 -4
- package/dist/Components/Chats/ChatPopup.d.ts +46 -0
- package/dist/Components/Chats/ChatPopup.js +34 -0
- package/dist/Components/Chats/ChatThread.d.ts +9 -1
- package/dist/Components/Chats/ChatThread.js +54 -6
- package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
- package/dist/Components/Dashboards/DashboardBoard.js +594 -0
- package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
- package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
- package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
- package/dist/Components/Dashboards/WidgetFrame.js +45 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +5 -1
- package/dist/Components/Forms/Crud.js +72 -24
- package/dist/Components/Forms/CrudActions.d.ts +5 -4
- package/dist/Components/Forms/CrudActions.js +42 -12
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudParts.d.ts +11 -4
- package/dist/Components/Forms/CrudParts.js +76 -10
- package/dist/Components/Forms/CrudRowForms.js +65 -5
- package/dist/Components/Forms/CrudTree.js +7 -7
- package/dist/Components/Forms/CrudWorkspace.d.ts +15 -5
- package/dist/Components/Forms/CrudWorkspace.js +19 -11
- package/dist/Components/Forms/Form.d.ts +3 -2
- package/dist/Components/Forms/Form.js +33 -26
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/SubmitForm.js +2 -15
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.js +4 -2
- package/dist/Components/Inputs/GroupForm.js +3 -2
- package/dist/Components/Inputs/InputRenderer.d.ts +5 -1
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +14 -1
- package/dist/Components/Inputs/RowsInput.js +78 -5
- package/dist/Components/Inputs/TabularForm.js +16 -3
- package/dist/Components/Inputs/TreeView.d.ts +24 -1
- package/dist/Components/Inputs/TreeView.js +84 -7
- package/dist/Components/Layouts/Popup.d.ts +22 -2
- package/dist/Components/Layouts/Popup.js +108 -6
- package/dist/Components/Messages/MailClient.d.ts +50 -0
- package/dist/Components/Messages/MailClient.js +410 -0
- package/dist/Components/Messages/MailColumns.d.ts +21 -0
- package/dist/Components/Messages/MailColumns.js +92 -0
- package/dist/Components/Messages/MailCompose.d.ts +46 -0
- package/dist/Components/Messages/MailCompose.js +154 -0
- package/dist/Components/Messages/MailReader.d.ts +43 -0
- package/dist/Components/Messages/MailReader.js +207 -0
- package/dist/Components/Messages/MailThread.d.ts +71 -0
- package/dist/Components/Messages/MailThread.js +185 -0
- package/dist/Components/Messages/MessageBell.d.ts +41 -0
- package/dist/Components/Messages/MessageBell.js +76 -0
- package/dist/Components/Viewers/DataTable.js +7 -5
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableParts.d.ts +3 -1
- package/dist/Components/Viewers/DataTableParts.js +2 -2
- package/dist/Components/Viewers/OwnerFiles.d.ts +57 -5
- package/dist/Components/Viewers/OwnerFiles.js +142 -31
- package/dist/Helpers/ApiModules.js +4 -0
- package/dist/Helpers/AuditEntries.d.ts +50 -0
- package/dist/Helpers/AuditEntries.js +96 -0
- package/dist/Helpers/ChatBackend.d.ts +8 -1
- package/dist/Helpers/ChatBackend.js +18 -0
- package/dist/Helpers/ChatHelpers.d.ts +2 -0
- package/dist/Helpers/ChatHelpers.js +4 -1
- package/dist/Helpers/CrudColumns.d.ts +44 -6
- package/dist/Helpers/CrudColumns.js +63 -13
- package/dist/Helpers/CrudHelpers.d.ts +36 -1
- package/dist/Helpers/CrudHelpers.js +61 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +18 -1
- package/dist/Helpers/CrudRowForms.js +35 -2
- package/dist/Helpers/DashboardHelpers.d.ts +115 -0
- package/dist/Helpers/DashboardHelpers.js +305 -0
- package/dist/Helpers/DomainCards.d.ts +140 -0
- package/dist/Helpers/DomainCards.js +370 -0
- package/dist/Helpers/FileAdapter.d.ts +82 -2
- package/dist/Helpers/FileAdapter.js +72 -1
- package/dist/Helpers/FormStore.d.ts +11 -1
- package/dist/Helpers/FormStore.js +66 -19
- package/dist/Helpers/InputConditions.d.ts +6 -0
- package/dist/Helpers/InputConditions.js +24 -0
- package/dist/Helpers/InputMemory.js +40 -30
- package/dist/Helpers/InputNarrowing.d.ts +38 -6
- package/dist/Helpers/InputNarrowing.js +102 -21
- package/dist/Helpers/MailClientHelpers.d.ts +96 -0
- package/dist/Helpers/MailClientHelpers.js +211 -0
- package/dist/Helpers/MessageBackend.d.ts +152 -0
- package/dist/Helpers/MessageBackend.js +329 -0
- package/dist/Helpers/MessageHelpers.d.ts +160 -0
- package/dist/Helpers/MessageHelpers.js +573 -0
- package/dist/Helpers/MessageUnread.d.ts +95 -0
- package/dist/Helpers/MessageUnread.js +245 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +17 -1
- package/dist/Helpers/NexusDriveAdapter.js +32 -3
- package/dist/Helpers/NumberHelpers.d.ts +61 -0
- package/dist/Helpers/NumberHelpers.js +124 -8
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/RowsStore.d.ts +24 -0
- package/dist/Helpers/RowsStore.js +79 -10
- package/dist/Helpers/TableHelpers.d.ts +15 -2
- package/dist/Helpers/TableHelpers.js +28 -1
- package/dist/Helpers/TableStore.d.ts +7 -3
- package/dist/Helpers/TableStore.js +9 -4
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +75 -6
- package/dist/Helpers/TreeStore.js +182 -18
- package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
- package/dist/Interfaces/ApiInterfaces.js +10 -1
- package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +78 -5
- package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
- package/dist/Interfaces/DashboardInterfaces.js +1 -0
- package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
- package/dist/Interfaces/DomainInterfaces.js +1 -0
- package/dist/Interfaces/FileInterfaces.d.ts +21 -0
- package/dist/Interfaces/FileInterfaces.js +2 -0
- package/dist/Interfaces/FormInterfaces.d.ts +78 -11
- package/dist/Interfaces/InputInterfaces.d.ts +12 -5
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +28 -2
- package/dist/Services/JoinSources.d.ts +47 -6
- package/dist/Services/JoinSources.js +68 -33
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Shared.Index.d.ts +12 -0
- package/dist/Shared.Index.js +12 -0
- package/package.json +7 -1
- package/src/Styles/Nexus.Button.css +29 -0
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Crud.css +119 -28
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +27 -0
- package/src/Styles/Nexus.Index.css +3 -0
- package/src/Styles/Nexus.Input.css +31 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Popup.css +58 -0
- package/src/Styles/Nexus.Table.css +1458 -1449
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Tree.css +10 -0
|
@@ -12,6 +12,17 @@
|
|
|
12
12
|
* itself: the view asks, and tells the store what came back (`beginLoad`, `receivePage`, `failLoad`).
|
|
13
13
|
*/
|
|
14
14
|
import { RequestCache } from "./RequestCache.js";
|
|
15
|
+
/** Where a tree keeps the filter chosen in this browser: per `key` and `viewer`, nothing without a viewer. */
|
|
16
|
+
export function treeFilterStorageKey(remember) {
|
|
17
|
+
const viewer = remember?.viewer?.trim();
|
|
18
|
+
return remember?.key && viewer ? `nx-tree-filter:${remember.key}:${viewer}` : null;
|
|
19
|
+
}
|
|
20
|
+
/** The filter on: the one chosen, else the default, else the first; `undefined` without filters. An id no filter has is passed over. */
|
|
21
|
+
export function shownTreeFilter(filters, chosen, defaultId) {
|
|
22
|
+
if (!filters || filters.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
return filters.find(filter => filter.id === chosen) ?? filters.find(filter => filter.id === defaultId) ?? filters[0];
|
|
25
|
+
}
|
|
15
26
|
/** Children's pages kept for every lazy tree on the page that names a `cacheKey`, for 5 minutes: see `RequestCache`. */
|
|
16
27
|
export const treePages = new RequestCache();
|
|
17
28
|
/** The start of every key a tree keeps its pages under, or one parent's pages (`null`: the top level). */
|
|
@@ -310,6 +321,11 @@ export class TreeStore {
|
|
|
310
321
|
openNodes = 0;
|
|
311
322
|
openCheckedNodes = 0;
|
|
312
323
|
indexCache = null;
|
|
324
|
+
/** The leaves shown (`setFilter`); `null`: all. */
|
|
325
|
+
match = null;
|
|
326
|
+
/** Goes up whenever a node's counts are worked out again, so what is kept from them knows it is old. */
|
|
327
|
+
counted = 0;
|
|
328
|
+
shownRoots = null;
|
|
313
329
|
constructor(items = [], options = {}, value) {
|
|
314
330
|
this.valueKey = options.valueKey ?? TREE_DEFAULT_KEYS.value;
|
|
315
331
|
this.parentKey = options.parentKey ?? TREE_DEFAULT_KEYS.parent;
|
|
@@ -322,8 +338,40 @@ export class TreeStore {
|
|
|
322
338
|
this.isChecked = options.isItemChecked ?? defaultChecked;
|
|
323
339
|
if (this.lazy && options.lazyRoots)
|
|
324
340
|
this.rootPages = idlePages();
|
|
341
|
+
this.match = options.filter ?? null;
|
|
325
342
|
this.setItems(items, value);
|
|
326
343
|
}
|
|
344
|
+
/** Whether a filter is on (`setFilter`). */
|
|
345
|
+
get filtered() {
|
|
346
|
+
return this.match !== null;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Shows only the leaves `match` takes, and the parents that hold one (`null`: every item). Nothing is checked or
|
|
350
|
+
* unchecked by it, and the value is the same whatever filter is on. While one is on, a parent's box, its count, and a
|
|
351
|
+
* check of it - or of "select all" - work on the leaves shown only: checking a parent checks its shown leaves, and a
|
|
352
|
+
* leaf the filter hides keeps its check exactly as it was. Expanded items stay expanded.
|
|
353
|
+
*/
|
|
354
|
+
setFilter(match) {
|
|
355
|
+
this.match = match;
|
|
356
|
+
this.recount(preOrder(this.roots));
|
|
357
|
+
this.rows = this.visibleRows(this.roots, null);
|
|
358
|
+
}
|
|
359
|
+
/** Children of a parent (`null`: the top level) that are rows of the filtered tree: all of them without a filter. */
|
|
360
|
+
shownChildCount(parent) {
|
|
361
|
+
if (parent)
|
|
362
|
+
return parent.shownChildren;
|
|
363
|
+
if (this.match === null)
|
|
364
|
+
return this.roots.length;
|
|
365
|
+
// Counted once per change of counts, not once per row drawn.
|
|
366
|
+
if (this.shownRoots?.counted !== this.counted) {
|
|
367
|
+
let count = 0;
|
|
368
|
+
for (const root of this.roots)
|
|
369
|
+
if (root.shown)
|
|
370
|
+
count++;
|
|
371
|
+
this.shownRoots = { counted: this.counted, count };
|
|
372
|
+
}
|
|
373
|
+
return this.shownRoots.count;
|
|
374
|
+
}
|
|
327
375
|
/** Items in the tree, drafts included. */
|
|
328
376
|
get size() {
|
|
329
377
|
return this.byKey.size;
|
|
@@ -456,15 +504,22 @@ export class TreeStore {
|
|
|
456
504
|
* unloaded items are not in the value - unless the value names it (`valueMode: "checked"`, where it stands for all).
|
|
457
505
|
*/
|
|
458
506
|
state(node) {
|
|
507
|
+
return this.stateOver(node, node.shownChecked, node.shownLeaves);
|
|
508
|
+
}
|
|
509
|
+
/** The state of a parent over all of its leaves, whatever the filter shows. */
|
|
510
|
+
wholeState(node) {
|
|
511
|
+
return this.stateOver(node, node.checkedLeaves, node.leaves);
|
|
512
|
+
}
|
|
513
|
+
stateOver(node, checked, leaves) {
|
|
459
514
|
if (!this.cascade || node.children.length === 0)
|
|
460
515
|
return node.checked ? "checked" : "unchecked";
|
|
461
516
|
if (node.partial)
|
|
462
|
-
return this.pending.has(node.key) ? "checked" :
|
|
463
|
-
if (
|
|
517
|
+
return this.pending.has(node.key) ? "checked" : checked > 0 ? "mixed" : "unchecked";
|
|
518
|
+
if (checked === 0)
|
|
464
519
|
return "unchecked";
|
|
465
|
-
return
|
|
520
|
+
return checked === leaves ? "checked" : "mixed";
|
|
466
521
|
}
|
|
467
|
-
/** Checked boxes and all boxes: leaves with `cascade`, every item without it. Drafts count. */
|
|
522
|
+
/** Checked boxes and all boxes: leaves with `cascade`, every item without it. Drafts count. The filter does not narrow it. */
|
|
468
523
|
counts() {
|
|
469
524
|
if (!this.cascade)
|
|
470
525
|
return { checked: this.checkedNodes, total: this.byKey.size };
|
|
@@ -476,6 +531,29 @@ export class TreeStore {
|
|
|
476
531
|
}
|
|
477
532
|
return { checked, total };
|
|
478
533
|
}
|
|
534
|
+
/** `counts` over what the filter shows: the leaves shown with `cascade`, the rows of items without it. */
|
|
535
|
+
shownCounts() {
|
|
536
|
+
if (this.match === null)
|
|
537
|
+
return this.counts();
|
|
538
|
+
let checked = 0;
|
|
539
|
+
let total = 0;
|
|
540
|
+
if (this.cascade) {
|
|
541
|
+
for (const root of this.roots) {
|
|
542
|
+
checked += root.shownChecked;
|
|
543
|
+
total += root.shownLeaves;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
for (const node of this.byKey.values()) {
|
|
548
|
+
if (!node.shown)
|
|
549
|
+
continue;
|
|
550
|
+
total++;
|
|
551
|
+
if (node.checked)
|
|
552
|
+
checked++;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return { checked, total };
|
|
556
|
+
}
|
|
479
557
|
/**
|
|
480
558
|
* The checked values, in tree order, following `valueMode`. In a lazy tree, values whose items are not loaded yet
|
|
481
559
|
* (or not settled) follow the others, so a saved value survives until its items come.
|
|
@@ -543,9 +621,12 @@ export class TreeStore {
|
|
|
543
621
|
return false;
|
|
544
622
|
if (!this.cascade)
|
|
545
623
|
return this.setNodeChecked(node, !node.checked);
|
|
546
|
-
return this.setChecked(node, node.
|
|
624
|
+
return this.setChecked(node, node.shownOpenChecked < node.shownOpenLeaves);
|
|
547
625
|
}
|
|
548
|
-
/**
|
|
626
|
+
/**
|
|
627
|
+
* Checks or unchecks a node; with `cascade`, every item under it that is not locked - and, while a filter is on, that
|
|
628
|
+
* the filter shows: the hidden ones keep their checks. Returns whether anything changed.
|
|
629
|
+
*/
|
|
549
630
|
setChecked(node, checked) {
|
|
550
631
|
if (node.locked || node.status)
|
|
551
632
|
return false;
|
|
@@ -559,7 +640,7 @@ export class TreeStore {
|
|
|
559
640
|
this.dropClaimsAbove(node);
|
|
560
641
|
return changed;
|
|
561
642
|
}
|
|
562
|
-
/** The state of a "select all" box, over the items that are not locked; `null` when every
|
|
643
|
+
/** The state of a "select all" box, over the items that are not locked (and shown); `null` when every one is locked. */
|
|
563
644
|
allState() {
|
|
564
645
|
let open = this.openNodes;
|
|
565
646
|
let checked = this.openCheckedNodes;
|
|
@@ -567,8 +648,19 @@ export class TreeStore {
|
|
|
567
648
|
open = 0;
|
|
568
649
|
checked = 0;
|
|
569
650
|
for (const root of this.roots) {
|
|
570
|
-
open += root.
|
|
571
|
-
checked += root.
|
|
651
|
+
open += root.shownOpenLeaves;
|
|
652
|
+
checked += root.shownOpenChecked;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
else if (this.match !== null) {
|
|
656
|
+
open = 0;
|
|
657
|
+
checked = 0;
|
|
658
|
+
for (const node of this.byKey.values()) {
|
|
659
|
+
if (!node.shown || node.locked)
|
|
660
|
+
continue;
|
|
661
|
+
open++;
|
|
662
|
+
if (node.checked)
|
|
663
|
+
checked++;
|
|
572
664
|
}
|
|
573
665
|
}
|
|
574
666
|
if (open === 0)
|
|
@@ -581,9 +673,9 @@ export class TreeStore {
|
|
|
581
673
|
return state !== null && this.checkAll(state !== "checked");
|
|
582
674
|
}
|
|
583
675
|
/**
|
|
584
|
-
* Checks or unchecks every item that is not locked. Returns whether anything changed. In a
|
|
585
|
-
* a node whose items are not all loaded is passed over and the loaded parts under it are
|
|
586
|
-
* items are not loaded stay as they are.
|
|
676
|
+
* Checks or unchecks every item that is not locked, and that the filter shows. Returns whether anything changed. In a
|
|
677
|
+
* lazy tree with `cascade`, a node whose items are not all loaded is passed over and the loaded parts under it are
|
|
678
|
+
* changed; values whose items are not loaded stay as they are.
|
|
587
679
|
*/
|
|
588
680
|
checkAll(checked) {
|
|
589
681
|
let changed = false;
|
|
@@ -593,7 +685,7 @@ export class TreeStore {
|
|
|
593
685
|
}
|
|
594
686
|
else {
|
|
595
687
|
for (const node of preOrder(this.roots))
|
|
596
|
-
changed = (!node.locked && this.setNodeChecked(node, checked)) || changed;
|
|
688
|
+
changed = (!node.locked && node.shown && this.setNodeChecked(node, checked)) || changed;
|
|
597
689
|
}
|
|
598
690
|
return changed;
|
|
599
691
|
}
|
|
@@ -602,6 +694,9 @@ export class TreeStore {
|
|
|
602
694
|
isVisible(node) {
|
|
603
695
|
if (node.status)
|
|
604
696
|
return this.indexOf(node) >= 0;
|
|
697
|
+
// A node the filter hides is no row; a shown node's parents are shown too.
|
|
698
|
+
if (!node.shown)
|
|
699
|
+
return false;
|
|
605
700
|
for (let parent = node.parent; parent; parent = parent.parent)
|
|
606
701
|
if (!parent.expanded)
|
|
607
702
|
return false;
|
|
@@ -888,7 +983,7 @@ export class TreeStore {
|
|
|
888
983
|
const removed = preOrder([node]);
|
|
889
984
|
// A parent that loses its last child becomes a leaf, checked when all of its items were.
|
|
890
985
|
if (parent && this.cascade && siblings.length === 1)
|
|
891
|
-
parent.checked = this.
|
|
986
|
+
parent.checked = this.wholeState(parent) === "checked";
|
|
892
987
|
siblings.splice(index, 1);
|
|
893
988
|
if (parent && parent.children.length === 0 && !this.canExpand(parent))
|
|
894
989
|
parent.expanded = false;
|
|
@@ -1086,6 +1181,12 @@ export class TreeStore {
|
|
|
1086
1181
|
draft: false,
|
|
1087
1182
|
pages: this.lazy ? this.newPages(item) : undefined,
|
|
1088
1183
|
partial: false,
|
|
1184
|
+
shown: true,
|
|
1185
|
+
shownChildren: 0,
|
|
1186
|
+
shownLeaves: 1,
|
|
1187
|
+
shownChecked: 0,
|
|
1188
|
+
shownOpenLeaves: 0,
|
|
1189
|
+
shownOpenChecked: 0,
|
|
1089
1190
|
};
|
|
1090
1191
|
}
|
|
1091
1192
|
/** A lazy node's pages before anything is asked: none to ask for when its item says it has no children. */
|
|
@@ -1134,6 +1235,8 @@ export class TreeStore {
|
|
|
1134
1235
|
}
|
|
1135
1236
|
for (let index = path.length - 1; index >= 0; index--)
|
|
1136
1237
|
this.aggregate(path[index]);
|
|
1238
|
+
if (this.match !== null && added.length > 0)
|
|
1239
|
+
shownPositions(siblings);
|
|
1137
1240
|
this.count(path, 1);
|
|
1138
1241
|
this.count(added, 1);
|
|
1139
1242
|
return added;
|
|
@@ -1287,6 +1390,9 @@ export class TreeStore {
|
|
|
1287
1390
|
stack.push(nodes[index]);
|
|
1288
1391
|
while (stack.length > 0) {
|
|
1289
1392
|
const node = stack.pop();
|
|
1393
|
+
// Hidden by the filter, with everything under it.
|
|
1394
|
+
if (!node.shown)
|
|
1395
|
+
continue;
|
|
1290
1396
|
list.push(node);
|
|
1291
1397
|
if (!node.expanded)
|
|
1292
1398
|
continue;
|
|
@@ -1336,6 +1442,12 @@ export class TreeStore {
|
|
|
1336
1442
|
openChecked: 0,
|
|
1337
1443
|
draft: false,
|
|
1338
1444
|
partial: false,
|
|
1445
|
+
shown: true,
|
|
1446
|
+
shownChildren: 0,
|
|
1447
|
+
shownLeaves: 0,
|
|
1448
|
+
shownChecked: 0,
|
|
1449
|
+
shownOpenLeaves: 0,
|
|
1450
|
+
shownOpenChecked: 0,
|
|
1339
1451
|
};
|
|
1340
1452
|
this.statusRows.set(parentKey, row);
|
|
1341
1453
|
}
|
|
@@ -1371,6 +1483,12 @@ export class TreeStore {
|
|
|
1371
1483
|
}
|
|
1372
1484
|
for (let index = ordered.length - 1; index >= 0; index--)
|
|
1373
1485
|
this.aggregate(ordered[index]);
|
|
1486
|
+
// Under a filter a row's place among its siblings (aria-posinset) counts the shown ones only.
|
|
1487
|
+
if (this.match !== null) {
|
|
1488
|
+
shownPositions(this.roots);
|
|
1489
|
+
for (const node of ordered)
|
|
1490
|
+
shownPositions(node.children);
|
|
1491
|
+
}
|
|
1374
1492
|
this.checkedNodes = 0;
|
|
1375
1493
|
this.openNodes = 0;
|
|
1376
1494
|
this.openCheckedNodes = 0;
|
|
@@ -1381,6 +1499,7 @@ export class TreeStore {
|
|
|
1381
1499
|
* does for every node, and what a page of children does for each node on its parent's path.
|
|
1382
1500
|
*/
|
|
1383
1501
|
aggregate(node) {
|
|
1502
|
+
this.counted++;
|
|
1384
1503
|
const pages = node.pages;
|
|
1385
1504
|
const own = pages !== undefined && (pages.status !== "ready" || pages.hasMore);
|
|
1386
1505
|
if (node.children.length === 0) {
|
|
@@ -1392,6 +1511,14 @@ export class TreeStore {
|
|
|
1392
1511
|
node.checkedLeaves = node.checked ? 1 : 0;
|
|
1393
1512
|
node.openLeaves = fixed ? 0 : 1;
|
|
1394
1513
|
node.openChecked = fixed ? 0 : node.checkedLeaves;
|
|
1514
|
+
// A draft being named shows whatever the filter says: its name box must not vanish.
|
|
1515
|
+
const matched = this.match === null || node.draft || this.match(node.item);
|
|
1516
|
+
node.shown = matched || own;
|
|
1517
|
+
node.shownChildren = 0;
|
|
1518
|
+
node.shownLeaves = matched ? 1 : 0;
|
|
1519
|
+
node.shownChecked = matched ? node.checkedLeaves : 0;
|
|
1520
|
+
node.shownOpenLeaves = matched ? node.openLeaves : 0;
|
|
1521
|
+
node.shownOpenChecked = matched ? node.openChecked : 0;
|
|
1395
1522
|
return;
|
|
1396
1523
|
}
|
|
1397
1524
|
let leaves = 0;
|
|
@@ -1399,7 +1526,13 @@ export class TreeStore {
|
|
|
1399
1526
|
let openLeaves = 0;
|
|
1400
1527
|
let openChecked = 0;
|
|
1401
1528
|
let allLocked = true;
|
|
1529
|
+
let shownLocked = true;
|
|
1402
1530
|
let partial = own;
|
|
1531
|
+
let shownChildren = 0;
|
|
1532
|
+
let shownLeaves = 0;
|
|
1533
|
+
let shownChecked = 0;
|
|
1534
|
+
let shownOpenLeaves = 0;
|
|
1535
|
+
let shownOpenChecked = 0;
|
|
1403
1536
|
for (const child of node.children) {
|
|
1404
1537
|
leaves += child.leaves;
|
|
1405
1538
|
checked += child.checkedLeaves;
|
|
@@ -1407,14 +1540,29 @@ export class TreeStore {
|
|
|
1407
1540
|
openChecked += child.openChecked;
|
|
1408
1541
|
allLocked &&= child.locked;
|
|
1409
1542
|
partial ||= child.partial;
|
|
1543
|
+
if (!child.shown)
|
|
1544
|
+
continue;
|
|
1545
|
+
shownChildren++;
|
|
1546
|
+
shownLeaves += child.shownLeaves;
|
|
1547
|
+
shownChecked += child.shownChecked;
|
|
1548
|
+
shownOpenLeaves += child.shownOpenLeaves;
|
|
1549
|
+
shownOpenChecked += child.shownOpenChecked;
|
|
1550
|
+
shownLocked &&= child.locked;
|
|
1410
1551
|
}
|
|
1411
1552
|
node.leaves = leaves;
|
|
1412
1553
|
node.checkedLeaves = checked;
|
|
1413
1554
|
node.openLeaves = openLeaves;
|
|
1414
1555
|
node.openChecked = openChecked;
|
|
1415
1556
|
node.partial = partial;
|
|
1416
|
-
|
|
1417
|
-
node.
|
|
1557
|
+
node.shown = this.match === null || shownChildren > 0 || own;
|
|
1558
|
+
node.shownChildren = shownChildren;
|
|
1559
|
+
node.shownLeaves = shownLeaves;
|
|
1560
|
+
node.shownChecked = shownChecked;
|
|
1561
|
+
node.shownOpenLeaves = shownOpenLeaves;
|
|
1562
|
+
node.shownOpenChecked = shownOpenChecked;
|
|
1563
|
+
// A parent's box follows its items only with cascade; without it, a parent stays free. Under a filter it follows the
|
|
1564
|
+
// items shown: a parent whose shown items are all locked has nothing a check could change.
|
|
1565
|
+
node.locked = node.disabledPath || (this.cascade && (this.match !== null && shownChildren > 0 ? shownLocked : allLocked));
|
|
1418
1566
|
}
|
|
1419
1567
|
/** Sets every open leaf under a node (the node itself for a leaf), updating counts up the path of each leaf that changes. */
|
|
1420
1568
|
setLeaves(node, checked) {
|
|
@@ -1422,8 +1570,9 @@ export class TreeStore {
|
|
|
1422
1570
|
const stack = [node];
|
|
1423
1571
|
while (stack.length > 0) {
|
|
1424
1572
|
const current = stack.pop();
|
|
1425
|
-
// A subtree that is locked, or already all checked or all unchecked, has nothing to change
|
|
1426
|
-
|
|
1573
|
+
// A subtree that is locked, or whose shown leaves are already all checked or all unchecked, has nothing to change:
|
|
1574
|
+
// a leaf the filter hides counts in no `shown` number, so it is never reached.
|
|
1575
|
+
if (current.locked || current.shownOpenChecked === (checked ? current.shownOpenLeaves : 0))
|
|
1427
1576
|
continue;
|
|
1428
1577
|
if (current.children.length > 0) {
|
|
1429
1578
|
for (const child of current.children)
|
|
@@ -1435,6 +1584,8 @@ export class TreeStore {
|
|
|
1435
1584
|
for (let path = current; path; path = path.parent) {
|
|
1436
1585
|
path.checkedLeaves += step;
|
|
1437
1586
|
path.openChecked += step;
|
|
1587
|
+
path.shownChecked += step;
|
|
1588
|
+
path.shownOpenChecked += step;
|
|
1438
1589
|
}
|
|
1439
1590
|
changed = true;
|
|
1440
1591
|
}
|
|
@@ -1449,10 +1600,16 @@ export class TreeStore {
|
|
|
1449
1600
|
if (!node.locked)
|
|
1450
1601
|
this.openCheckedNodes += step;
|
|
1451
1602
|
if (node.children.length === 0) {
|
|
1603
|
+
const shown = node.shownLeaves === 1;
|
|
1452
1604
|
for (let path = node; path; path = path.parent) {
|
|
1453
1605
|
path.checkedLeaves += step;
|
|
1454
1606
|
if (!node.locked)
|
|
1455
1607
|
path.openChecked += step;
|
|
1608
|
+
if (!shown)
|
|
1609
|
+
continue;
|
|
1610
|
+
path.shownChecked += step;
|
|
1611
|
+
if (!node.locked)
|
|
1612
|
+
path.shownOpenChecked += step;
|
|
1456
1613
|
}
|
|
1457
1614
|
}
|
|
1458
1615
|
return true;
|
|
@@ -1517,6 +1674,13 @@ function valueMap(values) {
|
|
|
1517
1674
|
}
|
|
1518
1675
|
return map;
|
|
1519
1676
|
}
|
|
1677
|
+
/** Numbers siblings among the shown ones (`position`), as a filtered tree's rows are read out. */
|
|
1678
|
+
function shownPositions(siblings) {
|
|
1679
|
+
let position = 0;
|
|
1680
|
+
for (const node of siblings)
|
|
1681
|
+
if (node.shown)
|
|
1682
|
+
node.position = position++;
|
|
1683
|
+
}
|
|
1520
1684
|
/** Nodes in tree order: each node, then its children. No recursion, so even a chain 100,000 levels deep works. */
|
|
1521
1685
|
export function preOrder(nodes) {
|
|
1522
1686
|
const list = [];
|
|
@@ -8,7 +8,9 @@ export type ApiModuleValue = string | number;
|
|
|
8
8
|
* **These numbers are the backend's, not this package's.** A permission is a bitmask of them, so a value that differs
|
|
9
9
|
* from the backend by one bit does not fail loudly: it silently grants or denies the wrong module. `Chat` was added
|
|
10
10
|
* at bit 6 in the backend on 2026-09-25 and every module after it moved up one bit; this list moved with it, which
|
|
11
|
-
* changed the value of `Accounting` through `Portfolio`.
|
|
11
|
+
* changed the value of `Accounting` through `Portfolio`. `Inventory` was added at bit 17 on 2026-11-09, after
|
|
12
|
+
* `Portfolio`: no value moved. `Report` (bit 8) was retired on 2026-11-11 and keeps its bit, which no module may take.
|
|
13
|
+
* A new module takes the next bit at the end, never one in between. See CHANGELOG.md.
|
|
12
14
|
*/
|
|
13
15
|
export declare const NexusModule: {
|
|
14
16
|
/** The app's own API routes (`/api/...`), not a backend module. */
|
|
@@ -21,6 +23,11 @@ export declare const NexusModule: {
|
|
|
21
23
|
readonly Message: number;
|
|
22
24
|
readonly Chat: number;
|
|
23
25
|
readonly Accounting: number;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Retired by the backend on 2026-11-11: its print layouts and print door are the Configuration module's
|
|
28
|
+
* now (`NexusModule.Configuration`). The value stays so that bit 8 is never given to another module, and old
|
|
29
|
+
* permission masks that hold it still read; no backend serves it, so offer it nowhere and call it never.
|
|
30
|
+
*/
|
|
24
31
|
readonly Report: number;
|
|
25
32
|
readonly Requisition: number;
|
|
26
33
|
readonly Tenant: number;
|
|
@@ -30,6 +37,8 @@ export declare const NexusModule: {
|
|
|
30
37
|
readonly Pharmacy: number;
|
|
31
38
|
readonly Staffing: number;
|
|
32
39
|
readonly Portfolio: number;
|
|
40
|
+
/** Stores, items, stock received (Dakhila) and issued (Nikasha), demands, purchase orders, write-off. */
|
|
41
|
+
readonly Inventory: number;
|
|
33
42
|
};
|
|
34
43
|
export type NexusModule = (typeof NexusModule)[keyof typeof NexusModule];
|
|
35
44
|
/**
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* **These numbers are the backend's, not this package's.** A permission is a bitmask of them, so a value that differs
|
|
6
6
|
* from the backend by one bit does not fail loudly: it silently grants or denies the wrong module. `Chat` was added
|
|
7
7
|
* at bit 6 in the backend on 2026-09-25 and every module after it moved up one bit; this list moved with it, which
|
|
8
|
-
* changed the value of `Accounting` through `Portfolio`.
|
|
8
|
+
* changed the value of `Accounting` through `Portfolio`. `Inventory` was added at bit 17 on 2026-11-09, after
|
|
9
|
+
* `Portfolio`: no value moved. `Report` (bit 8) was retired on 2026-11-11 and keeps its bit, which no module may take.
|
|
10
|
+
* A new module takes the next bit at the end, never one in between. See CHANGELOG.md.
|
|
9
11
|
*/
|
|
10
12
|
export const NexusModule = {
|
|
11
13
|
/** The app's own API routes (`/api/...`), not a backend module. */
|
|
@@ -18,6 +20,11 @@ export const NexusModule = {
|
|
|
18
20
|
Message: 1 << 5,
|
|
19
21
|
Chat: 1 << 6,
|
|
20
22
|
Accounting: 1 << 7,
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated Retired by the backend on 2026-11-11: its print layouts and print door are the Configuration module's
|
|
25
|
+
* now (`NexusModule.Configuration`). The value stays so that bit 8 is never given to another module, and old
|
|
26
|
+
* permission masks that hold it still read; no backend serves it, so offer it nowhere and call it never.
|
|
27
|
+
*/
|
|
21
28
|
Report: 1 << 8,
|
|
22
29
|
Requisition: 1 << 9,
|
|
23
30
|
Tenant: 1 << 10,
|
|
@@ -27,4 +34,6 @@ export const NexusModule = {
|
|
|
27
34
|
Pharmacy: 1 << 14,
|
|
28
35
|
Staffing: 1 << 15,
|
|
29
36
|
Portfolio: 1 << 16,
|
|
37
|
+
/** Stores, items, stock received (Dakhila) and issued (Nikasha), demands, purchase orders, write-off. */
|
|
38
|
+
Inventory: 1 << 17,
|
|
30
39
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { DomainCardDefinition } from "./DomainInterfaces.ts";
|
|
1
3
|
import type { DropdownPage, DropdownPageRequest } from "./InputInterfaces.ts";
|
|
2
4
|
/** An id, as the backend hands it out: a number, or text (a GUID, an encrypted id). */
|
|
3
5
|
export type ChatId = string | number;
|
|
@@ -45,9 +47,22 @@ export interface ChatDomainInfo<TDomain extends ChatDomainValue = AppChatDomain>
|
|
|
45
47
|
key: string;
|
|
46
48
|
/** Its name on screen: "Leave request". */
|
|
47
49
|
label: string;
|
|
50
|
+
/**
|
|
51
|
+
* How a message about a record of this kind shows the record: read live from its module and drawn as a card
|
|
52
|
+
* (`DomainCard`; a mail thread draws it at its head). Without one, the message shows the kind and the record's words.
|
|
53
|
+
*/
|
|
54
|
+
card?: DomainCardDefinition<any>;
|
|
55
|
+
/** Its icon where its kind is listed (a mail client's Types): `<Icon name="beach" />`. Default a clipboard. */
|
|
56
|
+
icon?: ReactNode;
|
|
48
57
|
}
|
|
49
58
|
/** `sent`: the server has it. `sending`: on its way. `failed`: it did not arrive, and the text is kept to send again. */
|
|
50
59
|
export type ChatMessageStatus = "sent" | "sending" | "failed";
|
|
60
|
+
/**
|
|
61
|
+
* What a message is. `chat`: a line in a conversation, or a reply. `mail`: the message a mail thread opens with, which a
|
|
62
|
+
* person wrote. `notification`: what an app wrote about an event (approved, sent back), under the name of the person who
|
|
63
|
+
* acted - drawn as a quiet event line, not a bubble, and unread for everyone but its author.
|
|
64
|
+
*/
|
|
65
|
+
export type ChatMessageKind = "chat" | "mail" | "notification";
|
|
51
66
|
/** A file on a message. The chat shows and links it; uploading is the app's `ChatSource`. */
|
|
52
67
|
export interface ChatAttachment {
|
|
53
68
|
id?: ChatId;
|
|
@@ -81,6 +96,8 @@ export interface ChatMessage {
|
|
|
81
96
|
attachments?: readonly ChatAttachment[];
|
|
82
97
|
/** Written by the system, not a person ("Sita approved the request."): a centered note, with no avatar or side. */
|
|
83
98
|
isSystem?: boolean;
|
|
99
|
+
/** What it is. Default `chat`. A `notification` is drawn as an event line: who acted, what happened, when. */
|
|
100
|
+
kind?: ChatMessageKind;
|
|
84
101
|
/** Who it has reached. Two ticks once anyone has it, filled once anyone has read it. */
|
|
85
102
|
deliveredTo?: readonly ChatId[];
|
|
86
103
|
/** Who has read it. */
|
|
@@ -147,6 +164,11 @@ export interface ChatDraft {
|
|
|
147
164
|
files?: readonly File[];
|
|
148
165
|
/** This browser's id for the message, so the answer replaces the right one and Retry sends the same draft. */
|
|
149
166
|
clientId: string;
|
|
167
|
+
/**
|
|
168
|
+
* What the line is. Default: the source's own, a chat line. A mail thread's Reply sends `mail` (a new mail in the
|
|
169
|
+
* thread, drawn as mail), its discussion `chat`.
|
|
170
|
+
*/
|
|
171
|
+
kind?: ChatMessageKind;
|
|
150
172
|
}
|
|
151
173
|
/** Someone in a conversation, or someone who could be added to it. */
|
|
152
174
|
export interface ChatParticipant {
|
|
@@ -113,12 +113,23 @@ export interface CrudFormOptions<TRow extends FormValues = FormValues> {
|
|
|
113
113
|
aside?: ReactNode | ((context: CrudFormAsideContext<TRow>) => ReactNode);
|
|
114
114
|
/** Values of a new record, over each input's default. */
|
|
115
115
|
newValues?: Partial<TRow> | (() => Partial<TRow>);
|
|
116
|
+
/**
|
|
117
|
+
* Values of a new record read when the Add form opens, over `newValues`: who a new message goes to by default, a
|
|
118
|
+
* counter's usual account. The inputs show disabled until they land, and a draft of the form comes back over them.
|
|
119
|
+
* They are a help, not the record: when the read fails the form opens on `newValues` alone, and a toast says why.
|
|
120
|
+
* The values given to `create(values)` (a parent it is added under) still win.
|
|
121
|
+
*/
|
|
122
|
+
createDefaults?: (signal: AbortSignal) => Promise<Partial<TRow> | null | undefined>;
|
|
116
123
|
/**
|
|
117
124
|
* What Edit opens with: the row as the table has it (`row`, the default), the record from the `details` endpoint
|
|
118
125
|
* (`details`, for fields a list leaves out), or a function that loads it.
|
|
119
126
|
*/
|
|
120
127
|
load?: "row" | "details" | ((row: TRow, signal: AbortSignal) => Promise<Partial<TRow> | null | undefined>);
|
|
121
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Shapes what is sent, e.g. to add a parent's id. Default: the values, with the fields the record came with. The row the
|
|
130
|
+
* save leaves is built from what this sends (`crudSavedRecord`): a field it sends as null is empty on the row, whatever
|
|
131
|
+
* the form still holds.
|
|
132
|
+
*/
|
|
122
133
|
transform?: (values: TRow, context: {
|
|
123
134
|
action: "create" | "edit";
|
|
124
135
|
row: TRow | null;
|
|
@@ -132,6 +143,21 @@ export interface CrudFormOptions<TRow extends FormValues = FormValues> {
|
|
|
132
143
|
validate?: FormProps<TRow>["validate"];
|
|
133
144
|
/** Derived values: return values to set after a change. */
|
|
134
145
|
onFieldChange?: FormProps<TRow>["onFieldChange"];
|
|
146
|
+
/**
|
|
147
|
+
* What the inputs worked out from others (`compute`) are handed - the form's own and those of its Tabular and Group
|
|
148
|
+
* rows (`crudInputs.lineAmount`) - worked out from the form's values as they change: a goods receipt hands its lines
|
|
149
|
+
* `{ taxed: values.source === Purchase }`, so a donation's lines count no discount or VAT. It is read after every
|
|
150
|
+
* change and compared field by field with the last answer (a shallow compare), so a fresh object of the same figures
|
|
151
|
+
* redraws nothing; a different one redraws the worked-out cells alone, with nothing remounted and the focus kept. A
|
|
152
|
+
* rule that throws keeps the last answer and says why in the console.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* form={{ computeWith: values => ({ purchase: values.source !== DONATION }) }}
|
|
156
|
+
*/
|
|
157
|
+
computeWith?: (values: TRow, context: {
|
|
158
|
+
action: "create" | "edit";
|
|
159
|
+
row: TRow | null;
|
|
160
|
+
}) => unknown;
|
|
135
161
|
/** Label design of every input that does not set its own. */
|
|
136
162
|
design?: InputDesign;
|
|
137
163
|
/**
|
|
@@ -217,6 +243,33 @@ export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
|
217
243
|
key: string;
|
|
218
244
|
viewer?: string | null;
|
|
219
245
|
};
|
|
246
|
+
/**
|
|
247
|
+
* The Details tab's sections (the runs of columns with one `group`) that change on their own: an Edit button at the
|
|
248
|
+
* end of the section's numbered heading opens a form for that part only - the Crud's own Edit form (`edit: "edit"`)
|
|
249
|
+
* or one of its `rowForms` (by its `id`) - and once it is saved the workspace reads the record again (`refresh()`), so
|
|
250
|
+
* the section shows what was saved. The button is there only where the form itself is offered (Edit: a list that
|
|
251
|
+
* offers Edit, the update endpoint allowed, no `rowActionLock` on it; a row form: `crudRowFormOffered` and no `lock`),
|
|
252
|
+
* and the focus comes back to it when the form closes. Only in a record's workspace (`tabs` or `profile`).
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* sections: [
|
|
256
|
+
* { group: "KYC details", edit: "edit" },
|
|
257
|
+
* { group: "Contact details", edit: "contacts" }, // a rowForms entry's id
|
|
258
|
+
* { group: "Public profile", edit: "public-profile", label: "Edit", hidden: row => row.isLocked },
|
|
259
|
+
* ]
|
|
260
|
+
*/
|
|
261
|
+
sections?: readonly CrudDetailsSection<TRow>[];
|
|
262
|
+
}
|
|
263
|
+
/** A section of a record's Details tab that changes on its own (`CrudDetailsOptions.sections`). */
|
|
264
|
+
export interface CrudDetailsSection<TRow extends FormValues = FormValues> {
|
|
265
|
+
/** The `group` its columns name. */
|
|
266
|
+
group: string;
|
|
267
|
+
/** What its button opens: `"edit"`, the Crud's own Edit form, or the `id` of one of the Crud's `rowForms`. */
|
|
268
|
+
edit?: "edit" | (string & {});
|
|
269
|
+
/** The button's word. Default "Edit". */
|
|
270
|
+
label?: string;
|
|
271
|
+
/** Leaves the button out for this record. */
|
|
272
|
+
hidden?: (row: TRow) => boolean;
|
|
220
273
|
}
|
|
221
274
|
/**
|
|
222
275
|
* A button in the details popup's footer. One that names the endpoint it calls shows only to a user who may call
|
|
@@ -516,7 +569,7 @@ export interface CrudScopes<TRow extends FormValues = FormValues> {
|
|
|
516
569
|
* beside its Edit. The page gives its inputs, where its values are read and where a save goes, and which of the answer's
|
|
517
570
|
* fields change the record; the CRUD offers it, opens it (one popup per record, brought back if open), and writes those
|
|
518
571
|
* fields into the record wherever it holds it - the table, every list kept, the pinned sections - with no list read
|
|
519
|
-
* again.
|
|
572
|
+
* again. With no inputs it is a question alone (a pay run's Lock): it asks over the record's figures and sends.
|
|
520
573
|
*
|
|
521
574
|
* @example
|
|
522
575
|
* {
|
|
@@ -526,6 +579,12 @@ export interface CrudScopes<TRow extends FormValues = FormValues> {
|
|
|
526
579
|
* transform: (values, row) => ({ branchUserId: row.id, permissionKeys: values.permissionKeys ?? [], description: values.description }),
|
|
527
580
|
* writes: ["assignedPermissionCount"],
|
|
528
581
|
* }
|
|
582
|
+
* @example
|
|
583
|
+
* {
|
|
584
|
+
* id: "lock", label: "Lock", icon: <Icon name="lock" />, when: run => run.payRunState === PAY_RUN_DRAFT, submit: PAY_RUN_LOCK,
|
|
585
|
+
* transform: (_values, run) => ({ id: run.id, generatedTime: run.generatedTime }), writes: result => result as Partial<PayRun>,
|
|
586
|
+
* confirm: run => ({ title: `Lock ${run.runCode}?`, message: "It is made again first.", details: () => ({ row: run, columns: TOTALS }) }),
|
|
587
|
+
* }
|
|
529
588
|
*/
|
|
530
589
|
export interface CrudRowForm<TRow extends FormValues = FormValues, TValues extends FormValues = FormValues> {
|
|
531
590
|
/** Unique among the record's actions. */
|
|
@@ -548,8 +607,22 @@ export interface CrudRowForm<TRow extends FormValues = FormValues, TValues exten
|
|
|
548
607
|
lock?: (row: TRow) => string | null | undefined;
|
|
549
608
|
/** What the endpoints are called with in `{id}` (the record the form is about, by its token). Default the record's key. */
|
|
550
609
|
target?: (row: TRow) => string | number;
|
|
551
|
-
/**
|
|
552
|
-
|
|
610
|
+
/**
|
|
611
|
+
* The form's inputs, or a function of the record and of what `load` read (`null` until it has, and without one); it may
|
|
612
|
+
* answer a promise. Without inputs the action is its question alone: no popup, it asks (`confirm`, else "{label}
|
|
613
|
+
* {the record's name}?") and saves on Confirm - the values sent are what `load` read, else the record as the list has
|
|
614
|
+
* it, through `transform`; a refusal shows as the API's message, and nothing is written.
|
|
615
|
+
*/
|
|
616
|
+
inputs?: readonly InputParams[] | ((row: TRow, loaded: TValues | null) => readonly InputParams[] | Promise<readonly InputParams[]>);
|
|
617
|
+
/**
|
|
618
|
+
* Asks before it saves, showing what will be saved in full (as a form's `confirm`, `FormConfirmParams`): after every
|
|
619
|
+
* value has passed its checks, Confirm holding the focus, read out with its details, and a hasty second Enter never
|
|
620
|
+
* answering it. Params, or a function of the record and of what `load` read, for a question that names the record:
|
|
621
|
+
* `confirm: run => ({ title: "Lock " + run.runCode + "?", details: () => ({ row: run, columns: TOTALS }), confirmLabel: "Lock" })`.
|
|
622
|
+
* A form with inputs keeps "Confirm and save" and "Back to the form" unless told; a question alone defaults to its
|
|
623
|
+
* `submitLabel` (else its `label`) and "Cancel".
|
|
624
|
+
*/
|
|
625
|
+
confirm?: FormConfirmParams<TValues> | ((row: TRow, loaded: TValues | null) => FormConfirmParams<TValues>);
|
|
553
626
|
/**
|
|
554
627
|
* Where the form's values are read: an endpoint called with the record's key (`{id}`, as the details are), or a
|
|
555
628
|
* function. Until they are read the form waits, and a read that fails - an answer that is not ok, or no record - shows
|
|
@@ -582,7 +655,7 @@ export type CrudLockableAction = "edit" | "flag" | "trash" | "recover" | "delete
|
|
|
582
655
|
/** A record's own locks, by action: the sentence that says why (`rowActionLock`). Nothing, or null, for an action that may run. */
|
|
583
656
|
export type CrudActionLocks = Partial<Record<CrudLockableAction, string | null | undefined>>;
|
|
584
657
|
/** Table settings a CRUD passes on to its `DataTable`. */
|
|
585
|
-
export type CrudTableOptions<TRow extends FormValues = FormValues> = Pick<DataTableProps<TRow>, "pager" | "pageSize" | "pageSizes" | "defaultSort" | "numbered" | "maxHeight" | "fill" | "excel" | "size" | "searchPlaceholder" | "maxSelection" | "emptyMessage" | "columnsMenu" | "stateKey" | "currency" | "searchDelay" | "filterInputs" | "reorderable" | "resizable" | "pinnable" | "moreActions" | "exportable" | "printable" | "exportName" | "onImport" | "openOnClick">;
|
|
658
|
+
export type CrudTableOptions<TRow extends FormValues = FormValues> = Pick<DataTableProps<TRow>, "pager" | "pageSize" | "pageSizes" | "defaultSort" | "numbered" | "maxHeight" | "fill" | "excel" | "size" | "searchPlaceholder" | "maxSelection" | "emptyMessage" | "columnsMenu" | "stateKey" | "currency" | "searchDelay" | "filterInputs" | "reorderable" | "resizable" | "pinnable" | "moreActions" | "exportable" | "printable" | "exportName" | "onImport" | "openOnClick" | "rowStrong">;
|
|
586
659
|
/** Props of `Crud`. */
|
|
587
660
|
export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
588
661
|
/** The endpoints: `MODULES.controller(NexusModule.Reservation, "Guest", "guest")`, or an object of your own. */
|