nexus-shared 3.1.0 → 4.0.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 +2268 -446
- package/LICENSE +21 -21
- package/README.md +1705 -1084
- package/dist/Client.Index.d.ts +17 -0
- package/dist/Client.Index.js +17 -0
- package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Forms/ApiForm.js +19 -2
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +871 -377
- package/dist/Components/Forms/CrudActions.d.ts +136 -0
- package/dist/Components/Forms/CrudActions.js +184 -0
- package/dist/Components/Forms/CrudHistory.d.ts +66 -0
- package/dist/Components/Forms/CrudHistory.js +140 -0
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +30 -2
- package/dist/Components/Forms/CrudParts.js +56 -5
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +114 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
- package/dist/Components/Forms/CrudWorkspace.js +169 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +4 -4
- package/dist/Components/Forms/Form.js +125 -16
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +195 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/CodeBox.d.ts +69 -0
- package/dist/Components/Inputs/CodeBox.js +316 -0
- package/dist/Components/Inputs/DatePicker.js +4 -1
- package/dist/Components/Inputs/DateRangePicker.js +4 -1
- package/dist/Components/Inputs/DateTimePicker.js +4 -1
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -5
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +10 -11
- package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
- package/dist/Components/Inputs/InputRenderer.js +39 -31
- package/dist/Components/Inputs/NumberBox.js +4 -2
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +70 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +1 -1
- package/dist/Components/Inputs/RowsInput.js +18 -3
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +29 -29
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +14 -2
- package/dist/Components/Inputs/TextBox.js +4 -1
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TimePicker.js +4 -1
- package/dist/Components/Inputs/TreeView.d.ts +107 -6
- package/dist/Components/Inputs/TreeView.js +513 -30
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +13 -6
- package/dist/Components/Layouts/Popup.js +37 -9
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- package/dist/Components/Layouts/ThemePicker.d.ts +3 -2
- package/dist/Components/Layouts/ThemePicker.js +16 -4
- package/dist/Components/Viewers/DataTable.js +205 -61
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +46 -12
- package/dist/Components/Viewers/DataTableParts.js +62 -21
- package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
- package/dist/Components/Viewers/OwnerFiles.js +589 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- package/dist/Helpers/ApiModules.d.ts +3 -34
- package/dist/Helpers/ApiModules.js +3 -3
- package/dist/Helpers/ApiRoutes.d.ts +68 -26
- package/dist/Helpers/ApiRoutes.js +31 -27
- package/dist/Helpers/ArrayHelpers.d.ts +0 -0
- package/dist/Helpers/ArrayHelpers.js +2 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/CodeHelpers.d.ts +97 -0
- package/dist/Helpers/CodeHelpers.js +332 -0
- package/dist/Helpers/CrudBackend.d.ts +114 -6
- package/dist/Helpers/CrudBackend.js +214 -11
- package/dist/Helpers/CrudColumns.d.ts +98 -0
- package/dist/Helpers/CrudColumns.js +160 -0
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +130 -6
- package/dist/Helpers/CrudHelpers.js +304 -12
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudRowForms.d.ts +15 -0
- package/dist/Helpers/CrudRowForms.js +43 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- package/dist/Helpers/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +78 -0
- package/dist/Helpers/FileAdapter.js +102 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +90 -5
- package/dist/Helpers/FormStore.js +318 -22
- package/dist/Helpers/InputConditions.d.ts +50 -0
- package/dist/Helpers/InputConditions.js +168 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +385 -0
- package/dist/Helpers/InputNarrowing.d.ts +18 -0
- package/dist/Helpers/InputNarrowing.js +67 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +162 -71
- package/dist/Helpers/MessageBuilder.js +2 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
- package/dist/Helpers/NexusDriveAdapter.js +207 -0
- package/dist/Helpers/NexusHelpers.d.ts +0 -0
- package/dist/Helpers/NexusHelpers.js +1 -0
- package/dist/Helpers/NumberHelpers.d.ts +95 -0
- package/dist/Helpers/NumberHelpers.js +126 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +47 -0
- package/dist/Helpers/RequestCache.js +132 -0
- package/dist/Helpers/RowsStore.d.ts +37 -2
- package/dist/Helpers/RowsStore.js +128 -10
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +21 -15
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +24 -3
- package/dist/Helpers/TableHelpers.js +58 -12
- package/dist/Helpers/TableJoins.d.ts +83 -0
- package/dist/Helpers/TableJoins.js +284 -0
- package/dist/Helpers/TableStore.d.ts +26 -1
- package/dist/Helpers/TableStore.js +73 -7
- package/dist/Helpers/TreeStore.d.ts +306 -11
- package/dist/Helpers/TreeStore.js +1048 -90
- package/dist/Helpers/UserPreferences.d.ts +8 -0
- package/dist/Helpers/UserPreferences.js +17 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +512 -14
- package/dist/Interfaces/FileInterfaces.d.ts +162 -0
- package/dist/Interfaces/FileInterfaces.js +4 -0
- package/dist/Interfaces/FormInterfaces.d.ts +393 -29
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +39 -3
- package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
- package/dist/Interfaces/PreferenceInterfaces.d.ts +8 -0
- package/dist/Interfaces/PreferenceInterfaces.js +2 -5
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +160 -5
- package/dist/Services/ApiProxy.d.ts +36 -1
- package/dist/Services/ApiProxy.js +182 -52
- package/dist/Services/ApiProxyRules.d.ts +203 -0
- package/dist/Services/ApiProxyRules.js +426 -0
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +80 -0
- package/dist/Services/JoinSources.js +150 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Services/ServerApi.d.ts +13 -2
- package/dist/Services/ServerApi.js +8 -3
- package/dist/Shared.Index.d.ts +20 -0
- package/dist/Shared.Index.js +20 -0
- package/package.json +92 -85
- package/src/Styles/Nexus.Avatar.css +80 -79
- package/src/Styles/Nexus.Base.css +117 -117
- package/src/Styles/Nexus.Button.css +186 -180
- package/src/Styles/Nexus.Calendar.css +273 -273
- package/src/Styles/Nexus.Chat.css +1443 -1443
- package/src/Styles/Nexus.Choice.css +359 -343
- package/src/Styles/Nexus.Crud.css +542 -168
- package/src/Styles/Nexus.DateTime.css +81 -81
- package/src/Styles/Nexus.Dropdown.css +733 -725
- package/src/Styles/Nexus.Editor.css +295 -295
- package/src/Styles/Nexus.Excel.css +389 -389
- package/src/Styles/Nexus.Files.css +410 -0
- package/src/Styles/Nexus.Form.css +282 -279
- package/src/Styles/Nexus.Index.css +41 -37
- package/src/Styles/Nexus.Input.css +238 -4
- package/src/Styles/Nexus.Markdown.css +237 -237
- package/src/Styles/Nexus.Menu.css +246 -237
- package/src/Styles/Nexus.Popup.css +800 -724
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Rows.css +637 -637
- package/src/Styles/Nexus.Slider.css +276 -276
- package/src/Styles/Nexus.Tab.Buttons.css +497 -432
- package/src/Styles/Nexus.Table.css +98 -35
- package/src/Styles/Nexus.Theme.Picker.css +59 -3
- package/src/Styles/Nexus.Themes.css +200 -194
- package/src/Styles/Nexus.Time.css +144 -144
- package/src/Styles/Nexus.Toast.css +263 -263
- package/src/Styles/Nexus.Tokens.css +94 -89
- package/src/Styles/Nexus.Tree.css +530 -431
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode, Ref } from "react";
|
|
2
2
|
import type { PopupSize } from "../Components/Layouts/Popup.tsx";
|
|
3
3
|
import type { PopupHandle } from "../Components/Layouts/PopupHost.tsx";
|
|
4
|
+
import type { OwnerFilesParams } from "../Components/Viewers/OwnerFiles.tsx";
|
|
4
5
|
import type { FormStore } from "../Helpers/FormStore.ts";
|
|
6
|
+
import type { CrudBackend } from "../Helpers/CrudBackend.ts";
|
|
5
7
|
import type { TableStore } from "../Helpers/TableStore.ts";
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
8
|
+
import type { TreeChangeRules, TreeValue } from "../Helpers/TreeStore.ts";
|
|
9
|
+
import type { ApiEndpoint, ApiModuleValue } from "./ApiInterfaces.ts";
|
|
10
|
+
import type { FormConfirmParams, FormProps, FormValues, InputParams } from "./FormInterfaces.ts";
|
|
11
|
+
import type { DropdownPage, DropdownPageRequest, InputDesign, OptionValue } from "./InputInterfaces.ts";
|
|
9
12
|
import type { DataTableProps, TableColumn, TableMassAction, TablePaging, TableRowAction } from "./TableInterfaces.ts";
|
|
13
|
+
import type { Tone } from "./TypeInterfaces.ts";
|
|
10
14
|
/**
|
|
11
15
|
* The lists of a controller: every active record (`normal`), the flagged ones, the trash, and the deleted ones
|
|
12
|
-
* (which can go back to the trash). The records the user pinned are not
|
|
13
|
-
* active list, and in it.
|
|
16
|
+
* (which can go back to the trash). The records the user pinned or made favourites are not lists of their own: they
|
|
17
|
+
* sit in sections above the active list, and in it.
|
|
14
18
|
*/
|
|
15
19
|
export type CrudMode = "normal" | "flag" | "trash" | "deleted";
|
|
16
20
|
/** The built-in actions on one record. */
|
|
17
|
-
export type CrudRowActionName = "view" | "edit" | "flag" | "pin" | "trash" | "recover" | "delete" | "trashFromDelete";
|
|
21
|
+
export type CrudRowActionName = "view" | "edit" | "flag" | "pin" | "favorite" | "history" | "trash" | "recover" | "delete" | "trashFromDelete";
|
|
18
22
|
/** The built-in actions on many records at once. */
|
|
19
|
-
export type CrudMassActionName = "flag" | "pin" | "trash" | "recover" | "delete" | "trashFromDelete" | "description";
|
|
23
|
+
export type CrudMassActionName = "flag" | "pin" | "favorite" | "trash" | "recover" | "delete" | "trashFromDelete" | "description";
|
|
24
|
+
/**
|
|
25
|
+
* A mark a user puts on a record for themselves: pinned, or one of their favourites. Each has a section above the
|
|
26
|
+
* active list and its own limit, and a record may carry both.
|
|
27
|
+
*/
|
|
28
|
+
export type CrudMark = "pin" | "favorite";
|
|
20
29
|
/** Every built-in action: Add, the row actions, and a new description for many records. */
|
|
21
30
|
export type CrudActionName = "create" | CrudRowActionName | "description";
|
|
22
31
|
/**
|
|
@@ -29,8 +38,15 @@ export interface CrudEndpoints {
|
|
|
29
38
|
/** One page of a list, for tables that page on the server: `Room/NormalPagination`, … */
|
|
30
39
|
pages?: Partial<Record<CrudMode, ApiEndpoint>>;
|
|
31
40
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
41
|
+
* This user's marked records, pins and favourites in one list: `Room/BookmarkListing`. Each record says which marks
|
|
42
|
+
* it has (`CrudBackend.marksOf`), and the CRUD splits them into the Pinned and Favourites sections above the active
|
|
43
|
+
* list. It is also where the CRUD learns every record's marks, so a list's own rows need not carry them. Without it
|
|
44
|
+
* (or `pins`) there are no sections, and nothing counts the marks against their limits.
|
|
45
|
+
*/
|
|
46
|
+
bookmarks?: ApiEndpoint;
|
|
47
|
+
/**
|
|
48
|
+
* The records the user pinned, from a backend that keeps no favourites: `Room/PinListing`. A Pinned section and no
|
|
49
|
+
* Favourites. `bookmarks` wins when both are given.
|
|
34
50
|
*/
|
|
35
51
|
pins?: ApiEndpoint;
|
|
36
52
|
/** One record by its key: `{id}` in the path. */
|
|
@@ -38,14 +54,34 @@ export interface CrudEndpoints {
|
|
|
38
54
|
create?: ApiEndpoint;
|
|
39
55
|
update?: ApiEndpoint;
|
|
40
56
|
flag?: ApiEndpoint;
|
|
41
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Sets this user's marks on a record: `Room/SwitchBookmarks/{id}`, sent the whole set the record is to have, never a
|
|
59
|
+
* toggle (`CrudBackend.marksRequest`). Pin and Favourite both use it.
|
|
60
|
+
*/
|
|
61
|
+
bookmark?: ApiEndpoint;
|
|
62
|
+
/** Pins a record or unpins it, on a backend without `bookmark`: `Room/Pin/{id}`, a toggle. `bookmark` wins when both are given. */
|
|
42
63
|
pin?: ApiEndpoint;
|
|
64
|
+
/**
|
|
65
|
+
* A record's history, 50 entries a call: `Room/AuditListing/{ownerId}/{skipCount}`. With it every row offers History
|
|
66
|
+
* and the details View history, to a person who may call it.
|
|
67
|
+
*/
|
|
68
|
+
audit?: ApiEndpoint;
|
|
43
69
|
trash?: ApiEndpoint;
|
|
44
70
|
recover?: ApiEndpoint;
|
|
45
71
|
delete?: ApiEndpoint;
|
|
46
72
|
trashFromDelete?: ApiEndpoint;
|
|
47
|
-
/**
|
|
48
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Actions on many records, sent the selected keys. `bookmark` puts one mark on every record sent or takes it off, and
|
|
75
|
+
* leaves their other marks alone (`Room/MassSwitchBookmarks`): Pin and Favourite both use it, and it wins over `pin`.
|
|
76
|
+
*/
|
|
77
|
+
mass?: Partial<Record<Exclude<CrudMassActionName, "favorite"> | "bookmark", ApiEndpoint>>;
|
|
78
|
+
/**
|
|
79
|
+
* Where other pages look this controller's records up: by key (`Room/FindListing`, a table's client join and a
|
|
80
|
+
* picker's saved values) and as a picker's list (`Room/ActiveListing`). The CRUD calls neither; a change it makes marks
|
|
81
|
+
* what was looked up through them as old, so a name or a status shown elsewhere is read again.
|
|
82
|
+
*/
|
|
83
|
+
findListing?: ApiEndpoint;
|
|
84
|
+
activeListing?: ApiEndpoint;
|
|
49
85
|
}
|
|
50
86
|
/** Inputs of the Add and Edit forms: one list for both, a list for each, or a function of the action and the row. */
|
|
51
87
|
export type CrudInputs<TRow extends FormValues = FormValues> = readonly InputParams[] | {
|
|
@@ -87,6 +123,11 @@ export interface CrudFormOptions<TRow extends FormValues = FormValues> {
|
|
|
87
123
|
action: "create" | "edit";
|
|
88
124
|
row: TRow | null;
|
|
89
125
|
}) => unknown;
|
|
126
|
+
/**
|
|
127
|
+
* Add and Edit ask before they save, showing what will be saved in full (`FormConfirmParams`): `body` is what
|
|
128
|
+
* `transform` made. Default: they save at once.
|
|
129
|
+
*/
|
|
130
|
+
confirm?: FormConfirmParams<TRow>;
|
|
90
131
|
/** The form's own rule over its values. */
|
|
91
132
|
validate?: FormProps<TRow>["validate"];
|
|
92
133
|
/** Derived values: return values to set after a change. */
|
|
@@ -116,7 +157,7 @@ export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
|
116
157
|
columns?: readonly TableColumn<TRow>[];
|
|
117
158
|
/** Draws the details instead. */
|
|
118
159
|
render?: (row: TRow) => ReactNode;
|
|
119
|
-
/** Default `md`. */
|
|
160
|
+
/** Default `md`; `full` with `tabs`. */
|
|
120
161
|
size?: PopupSize;
|
|
121
162
|
/**
|
|
122
163
|
* A panel beside the details, in the same popup: what the record generates, a chart, a preview. It is laid out as
|
|
@@ -124,6 +165,13 @@ export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
|
124
165
|
* beside it are a short list of fields. Pair it with a wide `size` (`xl` or `full`).
|
|
125
166
|
*/
|
|
126
167
|
aside?: ReactNode | ((context: CrudDetailsAsideContext<TRow>) => ReactNode);
|
|
168
|
+
/**
|
|
169
|
+
* Where `aside` goes: `"beside"` the fields where the popup is 1000px wide or more (stacked under them below that),
|
|
170
|
+
* or `"below"` them at every width - the fields across the whole width, then the panel across the whole width under
|
|
171
|
+
* them, as tall as it needs, scrolled with them. Default `"below"` in a record's workspace (`tabs` or `profile`), whose
|
|
172
|
+
* Details tab reads as the record first, then what belongs to it (its notes); `"beside"` in a plain details popup.
|
|
173
|
+
*/
|
|
174
|
+
asidePlace?: "beside" | "below";
|
|
127
175
|
/**
|
|
128
176
|
* What the popup is called, as a noun: the row's menu offers "View {label}" and the popup's title is
|
|
129
177
|
* "{Subject} {label}". Default `details`. A popup whose panel is the point says so: `schema`, `preview`.
|
|
@@ -131,6 +179,44 @@ export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
|
131
179
|
label?: string;
|
|
132
180
|
/** Buttons of the page's own in the footer, before Edit and Close: "View history". */
|
|
133
181
|
actions?: readonly CrudDetailsAction<TRow>[];
|
|
182
|
+
/**
|
|
183
|
+
* A record's workspace: the popup opens wide (`full`, unless `size` says otherwise) with a column on the left - the
|
|
184
|
+
* record's `profile` (picture, name, a line, tags) over a list of tabs - and the chosen tab on the right, scrolling
|
|
185
|
+
* inside itself. Each tab is a component of the page's own (a form that records something, a list, documents),
|
|
186
|
+
* handed the record and a `refresh()` that reads the record again and writes it into every list the CRUD keeps, so a
|
|
187
|
+
* stay recorded in a tab changes the row's visits and dates with no list read again. Only the chosen tab is drawn. A
|
|
188
|
+
* `SubmitForm` or `ApiForm` in a tab with changes asks "Discard your changes?" before another tab is chosen or the
|
|
189
|
+
* popup closes (Close, Edit, its X, Esc, its bar on the dock).
|
|
190
|
+
*
|
|
191
|
+
* The built-in tab `details` (the fields, with `aside` beside them) comes first. To move it, list `{ id: "details" }`
|
|
192
|
+
* where it goes (a `label` or `icon` there renames it); to leave it out, give that entry `hidden: () => true`; a
|
|
193
|
+
* `render` there replaces it. Below 720px of popup width (a phone) the profile is one row and the tabs one strip.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* details={{
|
|
197
|
+
* profile: row => ({ picture: { ...GUEST_PICTURE, ownerId: String(row.id) }, title: row.fullName, subtitle: row.email }),
|
|
198
|
+
* tabs: [
|
|
199
|
+
* { id: "stays", label: "Stays", icon: <InlineIcon icon={iconCalendar} />, endpoint: STAYS.create, render: ({ row, refresh }) => <StayForm guest={row} onSaved={refresh} /> },
|
|
200
|
+
* { id: "bills", label: "Bills", icon: <InlineIcon icon={iconReceipt} />, render: ({ row }) => <Bills guestId={row.id} /> },
|
|
201
|
+
* ],
|
|
202
|
+
* remember: { key: "guest-tab", viewer },
|
|
203
|
+
* }}
|
|
204
|
+
*/
|
|
205
|
+
tabs?: readonly CrudDetailsTab<TRow>[];
|
|
206
|
+
/**
|
|
207
|
+
* The record's picture, name, a line under it, its tags and a few of its fields (`fields`, left out of the Details
|
|
208
|
+
* tab), at the top of the left column (with or without `tabs`), from the record as it is now: the row, then the
|
|
209
|
+
* `details` endpoint's record, then each `refresh()`. Or anything to draw there instead.
|
|
210
|
+
*/
|
|
211
|
+
profile?: (row: TRow) => CrudDetailsProfile<TRow> | ReactNode;
|
|
212
|
+
/**
|
|
213
|
+
* Keeps the tab last chosen in this browser, per viewer (`nx-crud-tab:{key}:{viewer}`), and opens on it next time
|
|
214
|
+
* while it is still offered to the person; otherwise the first tab. Without a viewer nothing is kept.
|
|
215
|
+
*/
|
|
216
|
+
remember?: {
|
|
217
|
+
key: string;
|
|
218
|
+
viewer?: string | null;
|
|
219
|
+
};
|
|
134
220
|
}
|
|
135
221
|
/**
|
|
136
222
|
* A button in the details popup's footer. One that names the endpoint it calls shows only to a user who may call
|
|
@@ -148,15 +234,355 @@ export interface CrudDetailsAction<TRow extends FormValues = FormValues> {
|
|
|
148
234
|
/** On a click, with the record and the popup (close it, or leave it open beside what this opens). */
|
|
149
235
|
onSelect: (row: TRow, popup: PopupHandle) => void;
|
|
150
236
|
}
|
|
237
|
+
/** A record's main picture in a details popup's profile, by the params the picture column and input take. */
|
|
238
|
+
export interface CrudDetailsPicture extends Omit<OwnerFilesParams<ApiModuleValue>, "onChange"> {
|
|
239
|
+
/** Whose picture it is: without one, their initials stand in. Default: the profile's `title`, when it is text. */
|
|
240
|
+
name?: string | null;
|
|
241
|
+
}
|
|
242
|
+
/** The top of a details popup's left column: who the record is. */
|
|
243
|
+
export interface CrudDetailsProfile<TRow extends FormValues = FormValues> {
|
|
244
|
+
/** The owner's main picture by its Drive params (a click opens the pictures), or anything to draw in its place. */
|
|
245
|
+
picture?: CrudDetailsPicture | ReactNode;
|
|
246
|
+
/** The record's name. */
|
|
247
|
+
title: ReactNode;
|
|
248
|
+
/** A line under the name: a number, a phone. */
|
|
249
|
+
subtitle?: ReactNode;
|
|
250
|
+
/** Short tags under it, a status with its tone: `{ label: "Due", tone: "warning" }`. */
|
|
251
|
+
tags?: readonly {
|
|
252
|
+
label: ReactNode;
|
|
253
|
+
tone?: Tone;
|
|
254
|
+
}[];
|
|
255
|
+
/**
|
|
256
|
+
* A few of the record's fields under the tags, each as label and value, the value as the table draws it: its format
|
|
257
|
+
* in the person's settings (a date in their date format), an option's tone, the column's `tag` after it. A column given
|
|
258
|
+
* whole, or a key of the details' `columns` (the table's, unless the details name their own). A field with no value
|
|
259
|
+
* for the record is left out. The built-in Details tab leaves out every key named here, so no field shows twice.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* fields: [EXPIRY_COLUMN, "inactiveDate"]
|
|
263
|
+
*/
|
|
264
|
+
fields?: readonly (TableColumn<TRow> | string)[];
|
|
265
|
+
}
|
|
266
|
+
/** What a details tab is handed. */
|
|
267
|
+
export interface CrudDetailsTabContext<TRow extends FormValues = FormValues> {
|
|
268
|
+
/** The record as the list has it now: after a `refresh()`, the record as it was read again. */
|
|
269
|
+
row: TRow;
|
|
270
|
+
/**
|
|
271
|
+
* The record, in the `FormStore` the details' `aside` is handed: the row at once, then the `details` endpoint's record
|
|
272
|
+
* when it lands, then each `refresh()`'s.
|
|
273
|
+
*/
|
|
274
|
+
form: FormStore<TRow>;
|
|
275
|
+
/** The popup: close it, or open another over it. */
|
|
276
|
+
popup: PopupHandle;
|
|
277
|
+
/** The tab shown: this one. */
|
|
278
|
+
tab: string;
|
|
279
|
+
/** Shows another tab ("Take a payment" in the details opening the cash counter). Ignored for a tab not offered. */
|
|
280
|
+
select: (tab: string) => void;
|
|
281
|
+
/**
|
|
282
|
+
* Reads the record again from the `details` endpoint (without one, keeps the row) and writes it into the table and
|
|
283
|
+
* every list and tree the CRUD keeps, as a save's answer is: never a list read again. Answers the record, or `null`
|
|
284
|
+
* when it could not be read. Call it after a tab changed the record on the server: it lands even when the popup was
|
|
285
|
+
* closed before the record came back.
|
|
286
|
+
*/
|
|
287
|
+
refresh: () => Promise<TRow | null>;
|
|
288
|
+
}
|
|
289
|
+
/** A tab of a details popup (`CrudDetailsOptions.tabs`). */
|
|
290
|
+
export interface CrudDetailsTab<TRow extends FormValues = FormValues> {
|
|
291
|
+
/** Unique in the popup; what `select()` and `remember` keep. `details` is the built-in tab. */
|
|
292
|
+
id: string;
|
|
293
|
+
/** What the tab is called. Needed for every tab but `details` (whose default is "Details"). */
|
|
294
|
+
label?: string;
|
|
295
|
+
icon?: ReactNode;
|
|
296
|
+
/** What it calls: the tab shows only to a person who may call it, by the rule of `CrudDetailsAction`. */
|
|
297
|
+
endpoint?: ApiEndpoint;
|
|
298
|
+
/** Hidden for this record, by a rule of the page's own. */
|
|
299
|
+
hidden?: (row: TRow) => boolean;
|
|
300
|
+
/** A count or short tag at the end of the tab: `3`. */
|
|
301
|
+
badge?: ReactNode | ((row: TRow) => ReactNode);
|
|
302
|
+
/** The tab's content, drawn only while it is the chosen tab. Without one on `details`, the built-in fields. */
|
|
303
|
+
render?: (context: CrudDetailsTabContext<TRow>) => ReactNode;
|
|
304
|
+
}
|
|
151
305
|
/** What changed, for `onChange`. */
|
|
152
306
|
export interface CrudChange<TRow extends FormValues = FormValues> {
|
|
153
307
|
action: CrudActionName;
|
|
154
308
|
/** The records as they are now; for actions that took them out of the list, as they were. */
|
|
155
309
|
rows: TRow[];
|
|
156
310
|
mode: CrudMode;
|
|
311
|
+
/** The same records before the change, in the order of `rows`, where the CRUD held them; `undefined` for a new one. */
|
|
312
|
+
before?: (TRow | undefined)[];
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Where a controller's records sit in a tree: for a `CrudTree` of them, and for a `Crud` on another page whose changes
|
|
316
|
+
* that tree must follow (`trees`). One definition for both, so a change made on either page is written where the tree
|
|
317
|
+
* keeps its pages, and nothing is read again for it.
|
|
318
|
+
*/
|
|
319
|
+
export interface CrudTreePlacement<TRow extends FormValues = FormValues, TItem extends object = object> {
|
|
320
|
+
/** The tree's `cacheKey`: where its pages are kept, and what the trees on screen that show them listen on. */
|
|
321
|
+
cacheKey: string;
|
|
322
|
+
/** The tree's value and has-children keys, and the order of siblings (`compare`) as the server sorts them. */
|
|
323
|
+
rules: TreeChangeRules<TItem>;
|
|
324
|
+
/** The tree item that shows a record. */
|
|
325
|
+
itemOf: (row: TRow) => TItem;
|
|
326
|
+
/** The value of the item a record sits under (`null`: the top level). */
|
|
327
|
+
parentOf: (row: TRow) => TreeValue | null;
|
|
328
|
+
/**
|
|
329
|
+
* The record an item shows, with the item's count (`rules.hasChildrenKey`) written back into it - a save counts a
|
|
330
|
+
* record's children, and its parents' on a data table, through it - or `null` for an item that is not one of these
|
|
331
|
+
* records (a country above its regions).
|
|
332
|
+
*
|
|
333
|
+
* @example item => (item.region ? { ...item.region, childCount: item.childCount } : null)
|
|
334
|
+
*/
|
|
335
|
+
recordOf: (item: TItem) => TRow | null;
|
|
336
|
+
/** A new record's values when it is added under an item: the parent's key, its country. */
|
|
337
|
+
childValues?: (parent: TItem) => Partial<TRow>;
|
|
338
|
+
/**
|
|
339
|
+
* The fields that say where a record sits (its parent, its country). Given, a `CrudTree` fixes them in every form it
|
|
340
|
+
* opens - read-only, saying why: an Add inside an item takes them from `childValues` (a field it leaves out is fixed
|
|
341
|
+
* empty), an Edit keeps the record's own - and every Add goes inside an item: the toolbar's Add New inside the selected
|
|
342
|
+
* row. A record then moves only where these fields are offered, such as a data table's form. Without them a tree's forms
|
|
343
|
+
* leave every field free.
|
|
344
|
+
*/
|
|
345
|
+
placeKeys?: readonly string[];
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* A list narrowed to the records of one record picked in another list - a person's places beside the people, a hotel's
|
|
349
|
+
* rooms beside the hotels - which the page feeds from that list's `onPick`. Every behaviour is the `Crud`'s: the page
|
|
350
|
+
* passes the picked record and names the fields.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* const [person, setPerson] = useState<CompanyUser | null>(null);
|
|
354
|
+
* <AdminSplit>
|
|
355
|
+
* <Crud<CompanyUser> api={COMPANY_USERS} ... onPick={setPerson} />
|
|
356
|
+
* <Crud<BranchUser> api={BRANCH_USERS} ... narrow={{ picked: person, key: "userId", labelKey: "fullname" }} />
|
|
357
|
+
* </AdminSplit>
|
|
358
|
+
*/
|
|
359
|
+
export interface CrudNarrow<TPicked extends FormValues = FormValues> {
|
|
360
|
+
/** The record picked in the other list, or `null` while none is: the list then shows nothing, and Add waits. */
|
|
361
|
+
picked: TPicked | null;
|
|
362
|
+
/**
|
|
363
|
+
* The field of these records that names the picked one (`"userId"`): the list shows the records whose field holds its
|
|
364
|
+
* value, and a new record is given that value, fixed. A server-paged list sends it as a filter of that name.
|
|
365
|
+
*/
|
|
366
|
+
key: string;
|
|
367
|
+
/** The picked record's field holding that value. Default `key`. */
|
|
368
|
+
valueKey?: string;
|
|
369
|
+
/** The picked record's field that names it in words: "Places of: Ram Thapa". Default the value itself. */
|
|
370
|
+
labelKey?: string;
|
|
371
|
+
/**
|
|
372
|
+
* Why nothing can be added for the picked record, or nothing when something can: Add stays in the bar, switched off,
|
|
373
|
+
* and says this on a press. Default: the backend's rule (`CrudBackend.isPickable`), so a record a picker would not
|
|
374
|
+
* offer takes no new records - in the Nexus bands one pending (30-39), in the trash or deleted.
|
|
375
|
+
*
|
|
376
|
+
* @example addLock: person => (getCrudBackend().isPickable(person) ? null : `${person.fullname} has not joined yet.`)
|
|
377
|
+
*/
|
|
378
|
+
addLock?: (picked: TPicked) => string | null | undefined;
|
|
379
|
+
/**
|
|
380
|
+
* What the list says while no record is picked. Default "Select a row in the other list to see its {plural}.", or with
|
|
381
|
+
* a `narrowPicker` "Pick a {label} in the {Label} box to see its {plural}."
|
|
382
|
+
*/
|
|
383
|
+
emptyMessage?: string;
|
|
384
|
+
/**
|
|
385
|
+
* Why Add is switched off while no record is picked, on its tooltip and on a press. Default "Select a row in the other
|
|
386
|
+
* list to add a new {subject} for it.", or with a `narrowPicker` "Pick a {label} in the {Label} box to add a new {subject} for it."
|
|
387
|
+
*/
|
|
388
|
+
addMessage?: string;
|
|
389
|
+
/**
|
|
390
|
+
* What the Add form's fixed field says, given the picked record's name. Default "Added for {name}: pick another row in
|
|
391
|
+
* the other list to add elsewhere.", or with a `narrowPicker` "Added for {name}: pick another {label} in the {Label} box to add elsewhere."
|
|
392
|
+
*
|
|
393
|
+
* @example fixedMessage: name => `Added to ${name}. Close and pick another group to add elsewhere.`
|
|
394
|
+
*/
|
|
395
|
+
fixedMessage?: (pickedName: string) => string;
|
|
396
|
+
}
|
|
397
|
+
/** The box a `narrowPicker` draws: a picker's params, as `crudPicker` gives them. */
|
|
398
|
+
export interface CrudNarrowPickerSource {
|
|
399
|
+
loadOptions: (request: DropdownPageRequest) => Promise<DropdownPage<Readonly<Record<string, unknown>>>>;
|
|
400
|
+
resolveOptions?: (values: OptionValue[]) => Promise<readonly Readonly<Record<string, unknown>>[]>;
|
|
401
|
+
peekOption?: (key: string, aged?: boolean) => Readonly<Record<string, unknown>> | undefined;
|
|
402
|
+
valueKey?: string;
|
|
403
|
+
labelKey?: string;
|
|
404
|
+
descriptionKey?: string;
|
|
405
|
+
isOptionInactive?: (item: Readonly<Record<string, unknown>>) => boolean;
|
|
157
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* A list narrowed by a record picked in a box in the CRUD's own toolbar (`Crud` `narrowPicker`): the picker's params, the
|
|
409
|
+
* box's words, and where this browser remembers the pick. Every field of `CrudNarrow` but `picked`, which the CRUD keeps.
|
|
410
|
+
*
|
|
411
|
+
* As its page opens the box settles - a spinner in it, the list waiting - on the remembered pick while it is offered, or
|
|
412
|
+
* else on the one record the box offers (picked by itself, and remembered as a person's pick is). With neither it asks:
|
|
413
|
+
* required, drawn with a field's invalid edge, the list's `emptyMessage` read with it, the cursor in it unless the person
|
|
414
|
+
* is already at work on the page, a popup is open, it is below the fold or the screen is touch-only; the empty list has a
|
|
415
|
+
* button to it, and Add pressed meanwhile sends the cursor there (nudged, with Add's `addMessage` read) instead of a note.
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* narrowPicker={{
|
|
419
|
+
* picker: crudPicker(GROUPS, { labelKey: "groupName" }),
|
|
420
|
+
* label: "Group",
|
|
421
|
+
* key: "subscriptionGroupId",
|
|
422
|
+
* labelKey: "groupName",
|
|
423
|
+
* emptyMessage: "Pick a group to see its subscribers.",
|
|
424
|
+
* remember: { key: "subscribers-group", viewer },
|
|
425
|
+
* onPick: group => setGroupId(group?.id ?? null),
|
|
426
|
+
* }}
|
|
427
|
+
*/
|
|
428
|
+
export interface CrudNarrowPicker<TPicked extends FormValues = FormValues> extends Omit<CrudNarrow<TPicked>, "picked"> {
|
|
429
|
+
/** The box's options: `crudPicker(GROUPS, { labelKey: "groupName" })`. Its value field is the narrowing's `valueKey`. */
|
|
430
|
+
picker: CrudNarrowPickerSource;
|
|
431
|
+
/** What the box picks, for a screen reader and its placeholder: "Group" says "Pick a group". */
|
|
432
|
+
label: string;
|
|
433
|
+
/** The box's text while nothing is picked. Default "Pick a {label}". */
|
|
434
|
+
placeholder?: string;
|
|
435
|
+
/**
|
|
436
|
+
* Remembers the pick in this browser (local storage), under `key` and the viewer: pass who is looking (a short hash of
|
|
437
|
+
* the person and the company the session works in), so a PC shared by two people, or one person in two companies,
|
|
438
|
+
* never opens on another's pick; without a viewer nothing is remembered. Opened again only while the box still offers
|
|
439
|
+
* it (a record trashed since is asked for again), looked up by its key alone (`resolveOptions` when the picker has it).
|
|
440
|
+
* Only the record's key is stored: never its name.
|
|
441
|
+
*/
|
|
442
|
+
remember?: {
|
|
443
|
+
key: string;
|
|
444
|
+
viewer?: string | null;
|
|
445
|
+
};
|
|
446
|
+
/** After a pick, a remembered one opened, or the pick cleared: the page's inputs or filters can follow it. */
|
|
447
|
+
onPick?: (picked: TPicked | null) => void;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Another list a CRUD can show in place of its own (`Crud` `scopes`): the same kind of records, widened - every branch's
|
|
451
|
+
* places beside the branch signed in at - through endpoints of their own, and whatever else differs on it. The CRUD's own
|
|
452
|
+
* props are its own list; a field left out here is the CRUD's.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* { id: "all", label: "All branches", description: "Every branch's people", api: ALL_BRANCH_USERS, columns: EVERY_BRANCH_COLUMNS, inputs: everyBranchInputs }
|
|
456
|
+
*/
|
|
457
|
+
export interface CrudScope<TRow extends FormValues = FormValues> {
|
|
458
|
+
/** Unique among the scopes: what `remember` keeps, `onChange` names and `CrudHandle.scope` answers. */
|
|
459
|
+
id: string;
|
|
460
|
+
/** What the toolbar's switch says ("All branches"), or this scope's tab beside two or more. */
|
|
461
|
+
label: string;
|
|
462
|
+
/** The switch's (or tab's) tooltip, read after the label by a screen reader: "Every branch's people". */
|
|
463
|
+
description?: string;
|
|
464
|
+
/** Its endpoints: `MODULES.controller(NexusModule.Enterprise, "AllBranchUser", "place")`. */
|
|
465
|
+
api: CrudEndpoints;
|
|
466
|
+
/**
|
|
467
|
+
* Whether it is offered, by a rule of the page's own. It is offered only to a person who may call one of its lists
|
|
468
|
+
* anyway (the rule every list follows), again whenever their rights change; a scope kept for a person who may no longer
|
|
469
|
+
* see it opens the CRUD's own list.
|
|
470
|
+
*/
|
|
471
|
+
when?: () => boolean;
|
|
472
|
+
/** The table's columns on this list, when they differ: the Branch column shown. */
|
|
473
|
+
columns?: readonly TableColumn<TRow>[];
|
|
474
|
+
/** The Add and Edit forms' inputs on this list, when they differ: a branch to pick. */
|
|
475
|
+
inputs?: CrudInputs<TRow>;
|
|
476
|
+
/** The forms' options on this list, over the CRUD's own `form`, field by field: other `newValues`. */
|
|
477
|
+
form?: CrudFormOptions<TRow>;
|
|
478
|
+
/**
|
|
479
|
+
* The table's settings on this list, over the CRUD's own `table`, field by field. Each list keeps its own columns,
|
|
480
|
+
* widths, sort and page size: its `stateKey` is the CRUD's (or `crud:{name}`) and `:{id}` unless given here.
|
|
481
|
+
*/
|
|
482
|
+
table?: CrudTableOptions<TRow>;
|
|
483
|
+
/** The table's name for screen readers on this list. */
|
|
484
|
+
"aria-label"?: string;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* The lists a CRUD can be widened to, and how the person picks one (`Crud` `scopes`): with one, a switch in the toolbar,
|
|
488
|
+
* after Add ("All branches": on shows that list); with two or more, tabs, the CRUD's own list first. Changing it reads the
|
|
489
|
+
* new list on its endpoints; the CRUD stays (its toolbar keeps the focus, popups stay open), and each list keeps what it
|
|
490
|
+
* read while the page is open, so going back shows it at once.
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* scopes={{ options: [{ id: "all", label: "All branches", api: ALL_BRANCH_USERS }], remember: { key: "branch-users-scope", viewer } }}
|
|
494
|
+
*/
|
|
495
|
+
export interface CrudScopes<TRow extends FormValues = FormValues> {
|
|
496
|
+
/** The other lists, in the order their tabs show. */
|
|
497
|
+
options: readonly CrudScope<TRow>[];
|
|
498
|
+
/** The CRUD's own list's tab beside two scopes or more: "This branch". Default "Own". */
|
|
499
|
+
label?: string;
|
|
500
|
+
/** That tab's tooltip. */
|
|
501
|
+
description?: string;
|
|
502
|
+
/**
|
|
503
|
+
* Keeps the choice in this browser (`nx-crud-scope:{key}:{viewer}`), so the next visit opens the same list for the same
|
|
504
|
+
* viewer while it is offered to them: pass who is looking (a short hash of the person and the company), so a PC shared
|
|
505
|
+
* by two people never opens on the other's choice. Without a viewer nothing is kept.
|
|
506
|
+
*/
|
|
507
|
+
remember?: {
|
|
508
|
+
key: string;
|
|
509
|
+
viewer?: string | null;
|
|
510
|
+
};
|
|
511
|
+
/** After the list shown changed - a pick, a kept choice opened, a scope no longer offered: its id, `null` for the CRUD's own. */
|
|
512
|
+
onChange?: (scope: string | null) => void;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* A row action that opens a form of its own for the record, in a popup (`Crud` `rowForms`): a record's permission set
|
|
516
|
+
* 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
|
+
* fields change the record; the CRUD offers it, opens it (one popup per record, brought back if open), and writes those
|
|
518
|
+
* fields into the record wherever it holds it - the table, every list kept, the pinned sections - with no list read
|
|
519
|
+
* again.
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* {
|
|
523
|
+
* id: "permissions", label: "Permissions", icon: <InlineIcon icon={iconLock} />, inline: true, modes: ["normal"], size: "lg",
|
|
524
|
+
* load: BRANCH_USER_PERMISSIONS.details, submit: BRANCH_USER_PERMISSIONS.update,
|
|
525
|
+
* inputs: (row, loaded) => permissionSetInputs("user", tree, loaded),
|
|
526
|
+
* transform: (values, row) => ({ branchUserId: row.id, permissionKeys: values.permissionKeys ?? [], description: values.description }),
|
|
527
|
+
* writes: ["assignedPermissionCount"],
|
|
528
|
+
* }
|
|
529
|
+
*/
|
|
530
|
+
export interface CrudRowForm<TRow extends FormValues = FormValues, TValues extends FormValues = FormValues> {
|
|
531
|
+
/** Unique among the record's actions. */
|
|
532
|
+
id: string;
|
|
533
|
+
/** The action's name in the row's menu (and on its icon button), and the popup's title unless `title` says otherwise. */
|
|
534
|
+
label: string;
|
|
535
|
+
/** A line under the label in the row's menu. */
|
|
536
|
+
description?: string;
|
|
537
|
+
icon?: ReactNode;
|
|
538
|
+
/** An icon button on the row, beside Edit, rather than a line of its menu. Needs an `icon`. */
|
|
539
|
+
inline?: boolean;
|
|
540
|
+
/** The lists that offer it. Default the lists that offer Edit (the active and the flagged). */
|
|
541
|
+
modes?: readonly CrudMode[];
|
|
542
|
+
/**
|
|
543
|
+
* Offered on this record, by a rule of the page's own. It is offered only to a person who may call `submit` and, when
|
|
544
|
+
* it is an endpoint, `load` - again whenever their rights change.
|
|
545
|
+
*/
|
|
546
|
+
when?: (row: TRow, mode: CrudMode) => boolean;
|
|
547
|
+
/** Why it cannot run on this record now: listed switched off with this sentence, as `rowActionLock` does. */
|
|
548
|
+
lock?: (row: TRow) => string | null | undefined;
|
|
549
|
+
/** What the endpoints are called with in `{id}` (the record the form is about, by its token). Default the record's key. */
|
|
550
|
+
target?: (row: TRow) => string | number;
|
|
551
|
+
/** The form's inputs, or a function of the record and of what `load` read (`null` until it has, and without one); it may answer a promise. */
|
|
552
|
+
inputs: readonly InputParams[] | ((row: TRow, loaded: TValues | null) => readonly InputParams[] | Promise<readonly InputParams[]>);
|
|
553
|
+
/**
|
|
554
|
+
* Where the form's values are read: an endpoint called with the record's key (`{id}`, as the details are), or a
|
|
555
|
+
* function. Until they are read the form waits, and a read that fails - an answer that is not ok, or no record - shows
|
|
556
|
+
* "Could not load the values" with Retry and no Save, so an empty form is never saved over the record. A function must
|
|
557
|
+
* reject (throw) for a failure. Without it, the form opens on the record as the list has it.
|
|
558
|
+
*/
|
|
559
|
+
load?: ApiEndpoint | ((row: TRow, signal: AbortSignal) => Promise<TValues | null | undefined>);
|
|
560
|
+
/** Where a save goes. */
|
|
561
|
+
submit: ApiEndpoint;
|
|
562
|
+
/** What a save sends, from the values, the record and what was read. Default the values. */
|
|
563
|
+
transform?: (values: TValues, row: TRow, loaded: TValues | null) => unknown;
|
|
564
|
+
/**
|
|
565
|
+
* What the answer changes on the record: the answer's fields by name (`["assignedPermissionCount"]`, each one it
|
|
566
|
+
* carries), or a function of the answer. Only those fields and the record's key are written, over the record as the
|
|
567
|
+
* CRUD holds it when the answer lands, so a change made meanwhile (the same record edited while this popup was
|
|
568
|
+
* minimized) stays. Without it nothing is written.
|
|
569
|
+
*/
|
|
570
|
+
writes?: readonly string[] | ((result: unknown, row: TRow, values: TValues) => Partial<TRow> | null | undefined);
|
|
571
|
+
/** The popup's title. Default the `label`. */
|
|
572
|
+
title?: string;
|
|
573
|
+
/** The popup's width. Default `md`. */
|
|
574
|
+
size?: PopupSize;
|
|
575
|
+
/** The save button's label. Default "Save". */
|
|
576
|
+
submitLabel?: string;
|
|
577
|
+
/** After a save: the answer, the record as it is now, and the values sent (before `transform`). */
|
|
578
|
+
onSaved?: (result: unknown, row: TRow, values: TValues) => void;
|
|
579
|
+
}
|
|
580
|
+
/** The actions that change a record, which `rowActionLock` can switch off one by one. */
|
|
581
|
+
export type CrudLockableAction = "edit" | "flag" | "trash" | "recover" | "delete" | "trashFromDelete" | "description";
|
|
582
|
+
/** A record's own locks, by action: the sentence that says why (`rowActionLock`). Nothing, or null, for an action that may run. */
|
|
583
|
+
export type CrudActionLocks = Partial<Record<CrudLockableAction, string | null | undefined>>;
|
|
158
584
|
/** Table settings a CRUD passes on to its `DataTable`. */
|
|
159
|
-
export type CrudTableOptions<TRow extends FormValues = FormValues> = Pick<DataTableProps<TRow>, "pageSize" | "pageSizes" | "defaultSort" | "numbered" | "maxHeight" | "fill" | "excel" | "size" | "searchPlaceholder" | "maxSelection" | "emptyMessage" | "columnsMenu" | "stateKey" | "searchDelay" | "filterInputs" | "reorderable" | "resizable" | "pinnable" | "moreActions" | "exportable" | "printable" | "exportName" | "onImport" | "openOnClick">;
|
|
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">;
|
|
160
586
|
/** Props of `Crud`. */
|
|
161
587
|
export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
162
588
|
/** The endpoints: `MODULES.controller(NexusModule.Reservation, "Guest", "guest")`, or an object of your own. */
|
|
@@ -185,8 +611,35 @@ export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
|
185
611
|
actions?: readonly CrudActionName[];
|
|
186
612
|
/** More actions on a row, after the built-in ones. */
|
|
187
613
|
rowActions?: (row: TRow, mode: CrudMode) => readonly TableRowAction<TRow>[];
|
|
614
|
+
/**
|
|
615
|
+
* Row actions that each open a form of their own for the record, in a popup, after the built-in actions and before
|
|
616
|
+
* `rowActions`: what a record's permission set, its duties or its limits are edited through. Each is offered to a
|
|
617
|
+
* person who may call its endpoints, on the lists that offer Edit unless it says otherwise, and its save writes the
|
|
618
|
+
* fields its answer names into the record wherever the CRUD holds it.
|
|
619
|
+
*/
|
|
620
|
+
rowForms?: readonly CrudRowForm<TRow, any>[];
|
|
188
621
|
/** More actions on selected rows, or `false` for no selection at all. */
|
|
189
622
|
massActions?: false | ((rows: TRow[], mode: CrudMode) => readonly TableMassAction<TRow>[]);
|
|
623
|
+
/**
|
|
624
|
+
* Why a record may not be changed here, or nothing when it may: one the backend keeps read-only, for everybody (a
|
|
625
|
+
* seeded system record) or for this user. Its Edit, Flag, Move to trash, Recover, Delete permanently and Move back to
|
|
626
|
+
* trash are then listed switched off with this sentence under them, so its menu says why rather than looking short;
|
|
627
|
+
* its details offer no Edit, and the handle's `edit` opens its details instead; a selection that holds it has the
|
|
628
|
+
* mass actions that change records switched off. View, History, Pin and Favourite stay: they change nothing on it.
|
|
629
|
+
* The page's own `rowActions` are the page's to switch off. The backend still decides; this says so first.
|
|
630
|
+
*
|
|
631
|
+
* @example rowLock={row => (row.isLocked ? row.lockReason : null)}
|
|
632
|
+
*/
|
|
633
|
+
rowLock?: (row: TRow) => string | null | undefined;
|
|
634
|
+
/**
|
|
635
|
+
* Why single actions may not run on a record, each with its sentence, when the rest may: a branch that is the head office
|
|
636
|
+
* keeps Edit but has no Move to trash. Each named action is listed switched off with its sentence under it, a selection
|
|
637
|
+
* that holds the record has that mass action switched off, and a locked `edit` opens the details instead, as with
|
|
638
|
+
* `rowLock` (which, when it locks the record, locks every action at once). The backend still decides; this says so first.
|
|
639
|
+
*
|
|
640
|
+
* @example rowActionLock={row => (row.isHeadOffice ? { trash: "The head office stays open: make another branch the head office first." } : null)}
|
|
641
|
+
*/
|
|
642
|
+
rowActionLock?: (row: TRow) => CrudActionLocks | null | undefined;
|
|
190
643
|
/** The Add and Edit forms. */
|
|
191
644
|
form?: CrudFormOptions<TRow>;
|
|
192
645
|
/** The details popup, or `false` for none. */
|
|
@@ -206,6 +659,45 @@ export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
|
206
659
|
table?: CrudTableOptions<TRow>;
|
|
207
660
|
/** After every change the CRUD made: a record added, saved, trashed, deleted. */
|
|
208
661
|
onChange?: (change: CrudChange<TRow>) => void;
|
|
662
|
+
/**
|
|
663
|
+
* Rows the user picks one of, for a list beside this one that shows the picked record's own (its `narrow`): a click
|
|
664
|
+
* on a row picks it - a double-click still opens its details - and the arrow keys move the pick (`DataTable`
|
|
665
|
+
* `onPick`). Called with the picked record, with its new copy after an edit here, and with `null` once it leaves the
|
|
666
|
+
* list on screen (trashed, deleted, another list shown).
|
|
667
|
+
*/
|
|
668
|
+
onPick?: (row: TRow | null) => void;
|
|
669
|
+
/**
|
|
670
|
+
* The list narrowed to the records of one picked in another list, fed from that list's `onPick`: only those records
|
|
671
|
+
* show, under a line naming the picked one ("Places of: Ram Thapa"); Add fixes its value in the new record and waits
|
|
672
|
+
* while none is picked or it may take none (`addLock`); a record saved here lands in the list of the record it names.
|
|
673
|
+
*/
|
|
674
|
+
narrow?: CrudNarrow<any>;
|
|
675
|
+
/**
|
|
676
|
+
* The list narrowed by a record picked in a box of its own, in the toolbar: a group first, then that group's
|
|
677
|
+
* subscribers. The CRUD draws the box, keeps the pick (as `narrow` does with one passed in), and, with `remember`,
|
|
678
|
+
* opens it again on the next visit in this browser for the same viewer, while the box still offers it. Use it in place
|
|
679
|
+
* of `narrow`, not beside it.
|
|
680
|
+
*/
|
|
681
|
+
narrowPicker?: CrudNarrowPicker<any>;
|
|
682
|
+
/**
|
|
683
|
+
* Other lists the CRUD can show in place of its own - every branch's places beside the branch signed in at - each on
|
|
684
|
+
* endpoints of its own, with its own columns, inputs, form values and table settings where they differ: a switch in
|
|
685
|
+
* the toolbar picks one (tabs for two or more), offered only to a person who may call its lists and passes its `when`,
|
|
686
|
+
* and, with `remember`, kept in this browser per viewer.
|
|
687
|
+
*/
|
|
688
|
+
scopes?: CrudScopes<TRow>;
|
|
689
|
+
/**
|
|
690
|
+
* Trees on other pages that show these records (the placement their `CrudTree` is given): every change made here is
|
|
691
|
+
* written into the pages they keep - a record added goes under its parent, an edit is written in place or moved, one
|
|
692
|
+
* sent to the trash leaves - so the tree shows it next time without reading again. A spreadsheet import, which writes
|
|
693
|
+
* records nobody here has seen, drops their pages instead. Pins and favourites change no tree.
|
|
694
|
+
*/
|
|
695
|
+
trees?: readonly CrudTreePlacement<TRow, any>[];
|
|
696
|
+
/**
|
|
697
|
+
* The conventions of this controller's backend, when they differ from the app's (`defineCrudBackend`): an app whose
|
|
698
|
+
* pages sit on two backends gives the other one here. Default the app's, else the Nexus backend's.
|
|
699
|
+
*/
|
|
700
|
+
backend?: CrudBackend;
|
|
209
701
|
/** The table's name for screen readers. Default: the plural of the subject ("Guests"). */
|
|
210
702
|
"aria-label"?: string;
|
|
211
703
|
"aria-labelledby"?: string;
|
|
@@ -221,6 +713,10 @@ export interface CrudHandle<TRow extends FormValues = FormValues> {
|
|
|
221
713
|
/** The list shown. */
|
|
222
714
|
mode: CrudMode;
|
|
223
715
|
setMode: (mode: CrudMode) => void;
|
|
716
|
+
/** The scope shown (`scopes`): its id, `null` for the CRUD's own list. */
|
|
717
|
+
scope: string | null;
|
|
718
|
+
/** Shows a scope, as the toolbar's switch does (kept with `remember`); one not offered now shows the CRUD's own list. */
|
|
719
|
+
setScope: (scope: string | null) => void;
|
|
224
720
|
/** Loads the list again, past the cache. A last resort: `apply` writes a change in without a request. */
|
|
225
721
|
reload: () => Promise<void>;
|
|
226
722
|
/**
|
|
@@ -248,4 +744,6 @@ export interface CrudHandle<TRow extends FormValues = FormValues> {
|
|
|
248
744
|
edit: (row: TRow) => void;
|
|
249
745
|
/** Opens the details popup of a record. */
|
|
250
746
|
view: (row: TRow) => void;
|
|
747
|
+
/** Opens one of `rowForms` for a record, by its id, wherever it is offered. */
|
|
748
|
+
openRowForm: (id: string, row: TRow) => void;
|
|
251
749
|
}
|