nexus-shared 2.0.0 → 3.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 +262 -0
- package/README.md +122 -25
- package/dist/Client.Index.d.ts +11 -0
- package/dist/Client.Index.js +11 -0
- package/dist/Components/Chats/Chat.d.ts +28 -0
- package/dist/Components/Chats/Chat.js +19 -0
- package/dist/Components/Chats/ChatButton.d.ts +26 -0
- package/dist/Components/Chats/ChatButton.js +41 -0
- package/dist/Components/Chats/ChatComposer.d.ts +41 -0
- package/dist/Components/Chats/ChatComposer.js +177 -0
- package/dist/Components/Chats/ChatConversations.d.ts +35 -0
- package/dist/Components/Chats/ChatConversations.js +38 -0
- package/dist/Components/Chats/ChatPanel.d.ts +66 -0
- package/dist/Components/Chats/ChatPanel.js +94 -0
- package/dist/Components/Chats/ChatParts.d.ts +87 -0
- package/dist/Components/Chats/ChatParts.js +100 -0
- package/dist/Components/Chats/ChatThread.d.ts +22 -0
- package/dist/Components/Chats/ChatThread.js +96 -0
- package/dist/Components/Documents/Menu.js +24 -20
- package/dist/Components/Documents/SplitButton.js +5 -3
- package/dist/Components/Documents/TabButtons.d.ts +24 -6
- package/dist/Components/Documents/TabButtons.js +23 -4
- package/dist/Components/Forms/ApiForm.d.ts +6 -4
- package/dist/Components/Forms/ApiForm.js +15 -14
- package/dist/Components/Forms/Crud.js +202 -50
- package/dist/Components/Forms/ExcelImport.d.ts +42 -0
- package/dist/Components/Forms/ExcelImport.js +190 -0
- package/dist/Components/Forms/Form.js +5 -1
- package/dist/Components/Inputs/DateTimePicker.js +2 -1
- package/dist/Components/Inputs/GroupForm.js +4 -2
- package/dist/Components/Inputs/InputRenderer.d.ts +2 -0
- package/dist/Components/Inputs/InputRenderer.js +1 -1
- package/dist/Components/Inputs/ReadOnlyNotice.js +2 -1
- package/dist/Components/Inputs/RowsInput.d.ts +12 -1
- package/dist/Components/Inputs/RowsInput.js +53 -4
- package/dist/Components/Inputs/TabularForm.d.ts +7 -3
- package/dist/Components/Inputs/TabularForm.js +85 -20
- package/dist/Components/Inputs/TimePicker.js +2 -1
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +2 -2
- package/dist/Components/Layouts/ThemeSwitcher.js +35 -15
- package/dist/Components/Viewers/DataTable.js +88 -34
- package/dist/Components/Viewers/DataTableColumns.d.ts +28 -0
- package/dist/Components/Viewers/DataTableColumns.js +243 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +6 -2
- package/dist/Components/Viewers/DataTableParts.js +21 -9
- package/dist/Helpers/ApiClient.d.ts +22 -0
- package/dist/Helpers/ApiClient.js +4 -1
- package/dist/Helpers/ApiFormHelpers.d.ts +27 -4
- package/dist/Helpers/ApiFormHelpers.js +73 -9
- package/dist/Helpers/ApiModules.d.ts +0 -4
- package/dist/Helpers/ApiModules.js +1 -0
- package/dist/Helpers/ApiResponses.d.ts +14 -4
- package/dist/Helpers/ApiResponses.js +116 -25
- package/dist/Helpers/ApiRoutes.d.ts +0 -10
- package/dist/Helpers/ApiRoutes.js +2 -10
- package/dist/Helpers/ChatBackend.d.ts +134 -0
- package/dist/Helpers/ChatBackend.js +332 -0
- package/dist/Helpers/ChatHelpers.d.ts +189 -0
- package/dist/Helpers/ChatHelpers.js +486 -0
- package/dist/Helpers/ChatHooks.d.ts +81 -0
- package/dist/Helpers/ChatHooks.js +175 -0
- package/dist/Helpers/ChatStore.d.ts +132 -0
- package/dist/Helpers/ChatStore.js +394 -0
- package/dist/Helpers/ChatSummaries.d.ts +39 -0
- package/dist/Helpers/ChatSummaries.js +118 -0
- package/dist/Helpers/CrudBackend.d.ts +47 -5
- package/dist/Helpers/CrudBackend.js +99 -4
- package/dist/Helpers/CrudHelpers.d.ts +39 -4
- package/dist/Helpers/CrudHelpers.js +76 -11
- package/dist/Helpers/ExcelBackend.d.ts +106 -0
- package/dist/Helpers/ExcelBackend.js +290 -0
- package/dist/Helpers/ExcelHelpers.d.ts +98 -0
- package/dist/Helpers/ExcelHelpers.js +302 -0
- package/dist/Helpers/FormStore.d.ts +2 -0
- package/dist/Helpers/FormStore.js +19 -1
- package/dist/Helpers/MessageBuilder.js +0 -2
- package/dist/Helpers/PopoverHelpers.d.ts +31 -6
- package/dist/Helpers/PopoverHelpers.js +116 -12
- package/dist/Helpers/RowsStore.d.ts +7 -1
- package/dist/Helpers/RowsStore.js +22 -0
- package/dist/Helpers/TableColumns.d.ts +7 -0
- package/dist/Helpers/TableColumns.js +41 -13
- package/dist/Helpers/TableHelpers.d.ts +9 -1
- package/dist/Helpers/TableHelpers.js +20 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +29 -4
- package/dist/Interfaces/ApiInterfaces.js +16 -10
- package/dist/Interfaces/ChatInterfaces.d.ts +240 -0
- package/dist/Interfaces/ChatInterfaces.js +1 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +73 -12
- package/dist/Interfaces/ExcelInterfaces.d.ts +215 -0
- package/dist/Interfaces/ExcelInterfaces.js +45 -0
- package/dist/Interfaces/FormInterfaces.d.ts +52 -2
- package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
- package/dist/Interfaces/TableInterfaces.d.ts +62 -2
- package/dist/Services/BrowserApi.d.ts +18 -1
- package/dist/Services/BrowserApi.js +18 -0
- package/dist/Services/ChatApi.d.ts +37 -0
- package/dist/Services/ChatApi.js +98 -0
- package/dist/Services/ExcelApi.d.ts +46 -0
- package/dist/Services/ExcelApi.js +196 -0
- package/dist/Services/ServerApi.d.ts +6 -1
- package/dist/Services/ServerApi.js +3 -0
- package/dist/Shared.Index.d.ts +8 -0
- package/dist/Shared.Index.js +8 -0
- package/package.json +6 -3
- package/src/Styles/Nexus.Button.css +2 -0
- package/src/Styles/Nexus.Chat.css +1489 -0
- package/src/Styles/Nexus.Crud.css +77 -0
- package/src/Styles/Nexus.Excel.css +370 -0
- package/src/Styles/Nexus.Form.css +23 -0
- package/src/Styles/Nexus.Index.css +2 -0
- package/src/Styles/Nexus.Menu.css +12 -2
- package/src/Styles/Nexus.Rows.css +78 -48
- package/src/Styles/Nexus.Tab.Buttons.css +55 -0
- package/src/Styles/Nexus.Table.css +286 -4
- package/src/Styles/Nexus.Theme.Picker.css +75 -2
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode, Ref } from "react";
|
|
2
2
|
import type { PopupSize } from "../Components/Layouts/Popup.tsx";
|
|
3
|
+
import type { FormStore } from "../Helpers/FormStore.ts";
|
|
3
4
|
import type { TableStore } from "../Helpers/TableStore.ts";
|
|
4
5
|
import type { ApiEndpoint } from "./ApiInterfaces.ts";
|
|
5
6
|
import type { FormProps, FormValues, InputParams } from "./FormInterfaces.ts";
|
|
6
7
|
import type { InputDesign } from "./InputInterfaces.ts";
|
|
7
8
|
import type { DataTableProps, TableColumn, TableMassAction, TablePaging, TableRowAction } from "./TableInterfaces.ts";
|
|
8
9
|
/**
|
|
9
|
-
* The lists of a controller: every active record (`normal`), the flagged ones, the
|
|
10
|
-
*
|
|
10
|
+
* The lists of a controller: every active record (`normal`), the flagged ones, the trash, and the deleted ones
|
|
11
|
+
* (which can go back to the trash). The records the user pinned are not a list of their own: they sit above the
|
|
11
12
|
* active list, and in it.
|
|
12
13
|
*/
|
|
13
|
-
export type CrudMode = "normal" | "flag" | "
|
|
14
|
+
export type CrudMode = "normal" | "flag" | "trash" | "deleted";
|
|
14
15
|
/** The built-in actions on one record. */
|
|
15
|
-
export type CrudRowActionName = "view" | "edit" | "flag" | "pin" | "
|
|
16
|
+
export type CrudRowActionName = "view" | "edit" | "flag" | "pin" | "trash" | "recover" | "delete" | "trashFromDelete";
|
|
16
17
|
/** The built-in actions on many records at once. */
|
|
17
|
-
export type CrudMassActionName = "flag" | "pin" | "
|
|
18
|
+
export type CrudMassActionName = "flag" | "pin" | "trash" | "recover" | "delete" | "trashFromDelete" | "description";
|
|
18
19
|
/** Every built-in action: Add, the row actions, and a new description for many records. */
|
|
19
20
|
export type CrudActionName = "create" | CrudRowActionName | "description";
|
|
20
21
|
/**
|
|
@@ -38,8 +39,6 @@ export interface CrudEndpoints {
|
|
|
38
39
|
flag?: ApiEndpoint;
|
|
39
40
|
/** Pins a record, or unpins it: `Room/Pin/{id}`. */
|
|
40
41
|
pin?: ApiEndpoint;
|
|
41
|
-
archive?: ApiEndpoint;
|
|
42
|
-
restore?: ApiEndpoint;
|
|
43
42
|
trash?: ApiEndpoint;
|
|
44
43
|
recover?: ApiEndpoint;
|
|
45
44
|
delete?: ApiEndpoint;
|
|
@@ -52,10 +51,29 @@ export type CrudInputs<TRow extends FormValues = FormValues> = readonly InputPar
|
|
|
52
51
|
create?: readonly InputParams[];
|
|
53
52
|
edit?: readonly InputParams[];
|
|
54
53
|
} | ((action: "create" | "edit", row: TRow | null) => readonly InputParams[] | Promise<readonly InputParams[]>);
|
|
54
|
+
/** What a form popup's side panel is handed: the live form, and which record it was opened on. */
|
|
55
|
+
export interface CrudFormAsideContext<TRow extends FormValues = FormValues> {
|
|
56
|
+
/**
|
|
57
|
+
* The form of this popup. Read it with `getValue()` and follow it with `subscribeValue()`: the panel then updates
|
|
58
|
+
* itself without the form around it re-rendering, which is what keeps a long form typeable while the panel works.
|
|
59
|
+
*/
|
|
60
|
+
form: FormStore<TRow>;
|
|
61
|
+
action: "create" | "edit";
|
|
62
|
+
/** The record being edited, or null for a new one. */
|
|
63
|
+
row: TRow | null;
|
|
64
|
+
}
|
|
55
65
|
/** The Add and Edit popups and their forms. */
|
|
56
66
|
export interface CrudFormOptions<TRow extends FormValues = FormValues> {
|
|
57
67
|
/** Width of the popups. Default `md`, or `lg` for forms of more than 8 inputs. */
|
|
58
68
|
size?: PopupSize;
|
|
69
|
+
/**
|
|
70
|
+
* A panel beside the form, in the same popup: a preview of what will be written, a summary, a help panel. It is
|
|
71
|
+
* given the popup's own `FormStore`, so it can follow what is typed.
|
|
72
|
+
*
|
|
73
|
+
* Side by side where there is room; under about 1000px the two stack and the **form comes first**, since that is
|
|
74
|
+
* what the popup is for. Pair it with a wide `size` (`xl` or `full`): a panel in a 560px popup leaves the form none.
|
|
75
|
+
*/
|
|
76
|
+
aside?: ReactNode | ((context: CrudFormAsideContext<TRow>) => ReactNode);
|
|
59
77
|
/** Values of a new record, over each input's default. */
|
|
60
78
|
newValues?: Partial<TRow> | (() => Partial<TRow>);
|
|
61
79
|
/**
|
|
@@ -80,6 +98,17 @@ export interface CrudFormOptions<TRow extends FormValues = FormValues> {
|
|
|
80
98
|
*/
|
|
81
99
|
draftKey?: string;
|
|
82
100
|
}
|
|
101
|
+
/** What a details popup's side panel is handed: the record, in the shape a form's side panel is handed it. */
|
|
102
|
+
export interface CrudDetailsAsideContext<TRow extends FormValues = FormValues> {
|
|
103
|
+
/**
|
|
104
|
+
* The record, in a `FormStore` no input writes to: the row as the list has it at once, then the `details`
|
|
105
|
+
* endpoint's record when that lands. Read it with `getValue()` and follow it with `subscribeValue()`, exactly as a
|
|
106
|
+
* form's panel does — so the panel beside the Edit form can be the panel beside the details, unchanged.
|
|
107
|
+
*/
|
|
108
|
+
form: FormStore<TRow>;
|
|
109
|
+
/** The row the popup was opened on, as the list has it. */
|
|
110
|
+
row: TRow;
|
|
111
|
+
}
|
|
83
112
|
/** The details popup that View opens. */
|
|
84
113
|
export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
85
114
|
/** The fields to show. Default: every column, hidden ones too. */
|
|
@@ -88,6 +117,17 @@ export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
|
88
117
|
render?: (row: TRow) => ReactNode;
|
|
89
118
|
/** Default `md`. */
|
|
90
119
|
size?: PopupSize;
|
|
120
|
+
/**
|
|
121
|
+
* A panel beside the details, in the same popup: what the record generates, a chart, a preview. It is laid out as
|
|
122
|
+
* the Add and Edit popups' `form.aside` is, except that here the panel takes the larger share, since the details
|
|
123
|
+
* beside it are a short list of fields. Pair it with a wide `size` (`xl` or `full`).
|
|
124
|
+
*/
|
|
125
|
+
aside?: ReactNode | ((context: CrudDetailsAsideContext<TRow>) => ReactNode);
|
|
126
|
+
/**
|
|
127
|
+
* What the popup is called, as a noun: the row's menu offers "View {label}" and the popup's title is
|
|
128
|
+
* "{Subject} {label}". Default `details`. A popup whose panel is the point says so: `schema`, `preview`.
|
|
129
|
+
*/
|
|
130
|
+
label?: string;
|
|
91
131
|
}
|
|
92
132
|
/** What changed, for `onChange`. */
|
|
93
133
|
export interface CrudChange<TRow extends FormValues = FormValues> {
|
|
@@ -97,7 +137,7 @@ export interface CrudChange<TRow extends FormValues = FormValues> {
|
|
|
97
137
|
mode: CrudMode;
|
|
98
138
|
}
|
|
99
139
|
/** Table settings a CRUD passes on to its `DataTable`. */
|
|
100
|
-
export type CrudTableOptions<TRow extends FormValues = FormValues> = Pick<DataTableProps<TRow>, "pageSize" | "pageSizes" | "defaultSort" | "numbered" | "maxHeight" | "size" | "searchPlaceholder" | "maxSelection" | "emptyMessage" | "columnsMenu" | "stateKey" | "searchDelay" | "reorderable" | "resizable" | "pinnable" | "moreActions" | "exportable" | "printable" | "exportName" | "onImport">;
|
|
140
|
+
export type CrudTableOptions<TRow extends FormValues = FormValues> = Pick<DataTableProps<TRow>, "pageSize" | "pageSizes" | "defaultSort" | "numbered" | "maxHeight" | "fill" | "excel" | "size" | "searchPlaceholder" | "maxSelection" | "emptyMessage" | "columnsMenu" | "stateKey" | "searchDelay" | "reorderable" | "resizable" | "pinnable" | "moreActions" | "exportable" | "printable" | "exportName" | "onImport" | "openOnClick">;
|
|
101
141
|
/** Props of `Crud`. */
|
|
102
142
|
export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
103
143
|
/** The endpoints: `MODULES.controller(NexusModule.Reservation, "Guest", "guest")`, or an object of your own. */
|
|
@@ -133,8 +173,10 @@ export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
|
133
173
|
/** The details popup, or `false` for none. */
|
|
134
174
|
details?: false | CrudDetailsOptions<TRow>;
|
|
135
175
|
/**
|
|
136
|
-
* Keeps lists this long (milliseconds) while the page is open, so going back to a list shows it at once
|
|
137
|
-
*
|
|
176
|
+
* Keeps lists this long (milliseconds) while the page is open, so going back to a list shows it at once. A change is
|
|
177
|
+
* **written into** every list kept, from the record the answer carried: a record flagged joins the flagged list and
|
|
178
|
+
* one trashed joins the trash, with no request on either. Only a list the record cannot answer for is dropped, and
|
|
179
|
+
* that one loads when it is shown. Default 5 minutes; `false` loads every time. Cleared when the page closes.
|
|
138
180
|
*/
|
|
139
181
|
cache?: false | {
|
|
140
182
|
lifetime?: number;
|
|
@@ -143,7 +185,7 @@ export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
|
143
185
|
toolbar?: ReactNode;
|
|
144
186
|
/** Settings of the table. */
|
|
145
187
|
table?: CrudTableOptions<TRow>;
|
|
146
|
-
/** After every change the CRUD made: a record added, saved,
|
|
188
|
+
/** After every change the CRUD made: a record added, saved, trashed, deleted. */
|
|
147
189
|
onChange?: (change: CrudChange<TRow>) => void;
|
|
148
190
|
/** The table's name for screen readers. Default: the plural of the subject ("Guests"). */
|
|
149
191
|
"aria-label"?: string;
|
|
@@ -160,8 +202,27 @@ export interface CrudHandle<TRow extends FormValues = FormValues> {
|
|
|
160
202
|
/** The list shown. */
|
|
161
203
|
mode: CrudMode;
|
|
162
204
|
setMode: (mode: CrudMode) => void;
|
|
163
|
-
/** Loads the list again, past the cache. */
|
|
205
|
+
/** Loads the list again, past the cache. A last resort: `apply` writes a change in without a request. */
|
|
164
206
|
reload: () => Promise<void>;
|
|
207
|
+
/**
|
|
208
|
+
* Records a page's **own** action changed, written where the built-in actions write theirs: into the table, the
|
|
209
|
+
* pinned band and every list kept in the cache - no request, on any list. Each record is merged over the one the
|
|
210
|
+
* table holds, so the key and the fields that changed are enough:
|
|
211
|
+
*
|
|
212
|
+
* ```ts
|
|
213
|
+
* const answer = await api.send(REGION_MARKS.flag, { params: { id: row.id } });
|
|
214
|
+
* if (answer.ok) handle.current?.apply([{ ...row, recordStatus: answer.result }]);
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* Which list each record now belongs in comes from the record itself (`CrudBackend.listMembership`), so one that
|
|
218
|
+
* no longer belongs to the list on screen leaves it, and the other lists are patched behind it. `gone` is for an
|
|
219
|
+
* action that ends a record: it is taken out of every list.
|
|
220
|
+
*
|
|
221
|
+
* It does not call `onChange`, since the page calling it is the one that made the change.
|
|
222
|
+
*/
|
|
223
|
+
apply: (rows: readonly Partial<TRow>[], options?: {
|
|
224
|
+
gone?: boolean;
|
|
225
|
+
}) => void;
|
|
165
226
|
/** Opens the Add popup, with these values over the new record's. */
|
|
166
227
|
create: (values?: Partial<TRow>) => void;
|
|
167
228
|
/** Opens the Edit popup of a record. */
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { ApiEndpoint, ApiModuleValue, AppModule } from "./ApiInterfaces.ts";
|
|
2
|
+
/**
|
|
3
|
+
* What one column of a sheet holds. It decides the cell's format in the file, the check the spreadsheet makes while
|
|
4
|
+
* the person types, and the input the review editors draw. The numbers are the backend's (`EExcelDataTypes`); an
|
|
5
|
+
* object, not an enum, so a test that strips types can import it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ExcelDataType: {
|
|
8
|
+
/** One line of text: a name, a code. */
|
|
9
|
+
readonly Text: 0;
|
|
10
|
+
/** Many lines of text: a description, a note. */
|
|
11
|
+
readonly Note: 1;
|
|
12
|
+
/** A whole number, no decimals. */
|
|
13
|
+
readonly WholeNumber: 2;
|
|
14
|
+
/** A number with decimals. */
|
|
15
|
+
readonly Decimal: 3;
|
|
16
|
+
/** Money: decimals fixed at the backend's places. */
|
|
17
|
+
readonly Amount: 4;
|
|
18
|
+
/** A date with no time, as an ISO date: `2026-09-29`. */
|
|
19
|
+
readonly Date: 5;
|
|
20
|
+
/** A date and a time, zone-less, as `2026-09-29 14:30`. */
|
|
21
|
+
readonly DateTime: 6;
|
|
22
|
+
/** A time of day with no date: `14:30`. */
|
|
23
|
+
readonly Time: 7;
|
|
24
|
+
/** Yes or No. */
|
|
25
|
+
readonly YesNo: 8;
|
|
26
|
+
/** A choice from a list the backend owns, picked in the sheet and travelling as its key. */
|
|
27
|
+
readonly List: 9;
|
|
28
|
+
};
|
|
29
|
+
export type ExcelDataType = (typeof ExcelDataType)[keyof typeof ExcelDataType];
|
|
30
|
+
/** What one row of an upload turned out to be. The backend's `EExcelRowKinds`. */
|
|
31
|
+
export declare const ExcelRowKind: {
|
|
32
|
+
/** Nothing is wrong with it and it is not a record yet: this is what an import writes. */
|
|
33
|
+
readonly New: 0;
|
|
34
|
+
/** It is already a record, or repeats a row earlier in the file. Nothing updates it. */
|
|
35
|
+
readonly Duplicate: 1;
|
|
36
|
+
/** Something in it has to be fixed before it can be written. */
|
|
37
|
+
readonly Invalid: 2;
|
|
38
|
+
};
|
|
39
|
+
export type ExcelRowKind = (typeof ExcelRowKind)[keyof typeof ExcelRowKind];
|
|
40
|
+
/** One column of a sheet: enough to draw a heading, know what the cell holds, and check it before anything is sent. */
|
|
41
|
+
export interface ExcelColumnInfo {
|
|
42
|
+
/** The property behind it, and the key every row's values are held under: `CityName`. */
|
|
43
|
+
field: string;
|
|
44
|
+
/** The heading the person reads: "City name". */
|
|
45
|
+
header: string;
|
|
46
|
+
dataType: ExcelDataType;
|
|
47
|
+
/** A row with this cell empty is refused. */
|
|
48
|
+
isRequired?: boolean;
|
|
49
|
+
/** The file fills it, not the person. */
|
|
50
|
+
isReadOnly?: boolean;
|
|
51
|
+
/** It is a choice: it travels as a key with its words beside it, and is picked in the sheet. */
|
|
52
|
+
isReference?: boolean;
|
|
53
|
+
/** The list it picks from. */
|
|
54
|
+
list?: string | null;
|
|
55
|
+
minLength?: number;
|
|
56
|
+
maxLength?: number;
|
|
57
|
+
/** Least value of a number. Only a number has one. */
|
|
58
|
+
minimum?: number | null;
|
|
59
|
+
/** Most value of a number. */
|
|
60
|
+
maximum?: number | null;
|
|
61
|
+
/** What the person reads about this column, in their words. */
|
|
62
|
+
note?: string | null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The limits and the shape of a sheet, asked for before either side is offered: so an export that will be cut short is
|
|
66
|
+
* said before it is written, and the editors know what they are drawing. It is the same for everybody and changes once
|
|
67
|
+
* a release, so it is kept.
|
|
68
|
+
*/
|
|
69
|
+
export interface ExcelSettings {
|
|
70
|
+
/** What one row is: "City". */
|
|
71
|
+
subject: string;
|
|
72
|
+
/** What the downloaded template is called. */
|
|
73
|
+
templateFileName?: string;
|
|
74
|
+
/** Rows the template offers to fill. */
|
|
75
|
+
sheetRows?: number;
|
|
76
|
+
/** The most rows one export writes. Over this, the person is warned first. */
|
|
77
|
+
maxExportRows: number;
|
|
78
|
+
/** The most rows one import takes. */
|
|
79
|
+
maxImportRows: number;
|
|
80
|
+
/** The columns, in the order they read. */
|
|
81
|
+
columns: ExcelColumnInfo[];
|
|
82
|
+
/**
|
|
83
|
+
* How each column should be drawn as an input. **Empty** until the backend can describe an input, and `columns` is
|
|
84
|
+
* what the editors draw from until it is not.
|
|
85
|
+
*/
|
|
86
|
+
inputs?: readonly unknown[];
|
|
87
|
+
}
|
|
88
|
+
/** One thing wrong with one cell, told so the person can find it. */
|
|
89
|
+
export interface ExcelCellProblem {
|
|
90
|
+
/** The row of the sheet, the number down the side of the file. */
|
|
91
|
+
row: number;
|
|
92
|
+
/** The column, counting from 1. */
|
|
93
|
+
column: number;
|
|
94
|
+
/** The cell as a spreadsheet names it: `B7`. */
|
|
95
|
+
cell: string;
|
|
96
|
+
/** The column's heading, in the person's words. */
|
|
97
|
+
header: string;
|
|
98
|
+
/** The property behind it, which is what an editor binds to. */
|
|
99
|
+
field: string;
|
|
100
|
+
/** What to tell the person, in plain words. */
|
|
101
|
+
message: string;
|
|
102
|
+
/** The unique code of the line that found it, for a developer. */
|
|
103
|
+
messageCode?: string;
|
|
104
|
+
/** What kind of failure it is, as a number. */
|
|
105
|
+
errorCode?: number;
|
|
106
|
+
}
|
|
107
|
+
/** One row of the review: what it holds, what it turned out to be, and what is wrong with it. */
|
|
108
|
+
export interface ExcelReviewRow {
|
|
109
|
+
/** The row of the sheet, so the person can find it in their own file. */
|
|
110
|
+
number: number;
|
|
111
|
+
kind: ExcelRowKind;
|
|
112
|
+
/** What each column held, by the property's name. A choice holds its key, never a plain id. */
|
|
113
|
+
values: Record<string, string | null>;
|
|
114
|
+
/** What the person picked in each choice, for the review to show. */
|
|
115
|
+
labels?: Record<string, string | null>;
|
|
116
|
+
/** Everything wrong with the row, one per cell. */
|
|
117
|
+
problems?: ExcelCellProblem[];
|
|
118
|
+
/** Why it is a duplicate, in the person's words. */
|
|
119
|
+
duplicate?: string | null;
|
|
120
|
+
}
|
|
121
|
+
/** What a verify answers: the whole upload, counted and told row by row. Nothing was written. */
|
|
122
|
+
export interface ExcelVerifyAnswer {
|
|
123
|
+
/** What one row is. */
|
|
124
|
+
subject: string;
|
|
125
|
+
/** Rows that had something in them. Empty rows of the template are not counted. */
|
|
126
|
+
read: number;
|
|
127
|
+
/** Rows an import would write. */
|
|
128
|
+
new: number;
|
|
129
|
+
/** Rows that are already records, or repeat one earlier in the file. */
|
|
130
|
+
duplicates: number;
|
|
131
|
+
/** Rows with something to fix. */
|
|
132
|
+
invalid: number;
|
|
133
|
+
/** An import would write something and nothing is waiting on a fix. */
|
|
134
|
+
isReady: boolean;
|
|
135
|
+
/** The upload in one sentence, for above the review. */
|
|
136
|
+
note: string;
|
|
137
|
+
/** The columns, in the order they read. */
|
|
138
|
+
columns: ExcelColumnInfo[];
|
|
139
|
+
/** Every row that had something in it. */
|
|
140
|
+
rows: ExcelReviewRow[];
|
|
141
|
+
/** Empty until the backend can describe an input. */
|
|
142
|
+
inputs?: readonly unknown[];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* One row as it travels, in text and nothing else. The same shape in both directions, which is the point: a verify
|
|
146
|
+
* reads it out of the file and answers with it, the person fixes what they need to, and the import sends the same rows
|
|
147
|
+
* back to be read again by the same code. A reference column holds its key.
|
|
148
|
+
*/
|
|
149
|
+
export interface ExcelSheetRow {
|
|
150
|
+
/** The row of the sheet this came from, so every problem can name where it is. */
|
|
151
|
+
number: number;
|
|
152
|
+
/** What each column held, by the property's name. A reference holds its key. */
|
|
153
|
+
values: Record<string, string | null>;
|
|
154
|
+
/** What the person picked in each reference column: "Nepal (NP)". */
|
|
155
|
+
labels?: Record<string, string | null>;
|
|
156
|
+
}
|
|
157
|
+
/** What an import did: what it wrote, and what it did not, in the person's words. */
|
|
158
|
+
export interface ExcelImportAnswer {
|
|
159
|
+
/** Rows the request carried. */
|
|
160
|
+
sent: number;
|
|
161
|
+
/** Records written. */
|
|
162
|
+
created: number;
|
|
163
|
+
/** Rows that were already records. Nothing was updated. */
|
|
164
|
+
duplicates: number;
|
|
165
|
+
/** Rows that still had something to fix. */
|
|
166
|
+
invalid: number;
|
|
167
|
+
/** The rows nothing was written for, so the person sees which and why without asking again. */
|
|
168
|
+
refused?: ExcelReviewRow[];
|
|
169
|
+
/** What happened, in one sentence. */
|
|
170
|
+
note: string;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The endpoints of one model's spreadsheets. `excelEndpoints` builds the Nexus ones; an app on another backend writes
|
|
174
|
+
* its own object of the same four or five.
|
|
175
|
+
*
|
|
176
|
+
* `export` is the model's own action, because only the model knows what the person has on screen - the search, the
|
|
177
|
+
* filters, which list they are looking at, and whose rows they may see. Without one, no export is offered.
|
|
178
|
+
*/
|
|
179
|
+
export interface ExcelEndpoints<TModule extends ApiModuleValue = AppModule> {
|
|
180
|
+
/** The limits and the shape of the sheet. The one answer a browser may keep. */
|
|
181
|
+
settings: ApiEndpoint<TModule>;
|
|
182
|
+
/** The template to fill, as a file. */
|
|
183
|
+
template: ApiEndpoint<TModule>;
|
|
184
|
+
/** A filled file read and judged, with nothing written. */
|
|
185
|
+
verify: ApiEndpoint<TModule>;
|
|
186
|
+
/** Writes the rows a verify answered with, as the person left them. */
|
|
187
|
+
import: ApiEndpoint<TModule>;
|
|
188
|
+
/** The model's own export, with the search on screen. */
|
|
189
|
+
export?: ApiEndpoint<TModule>;
|
|
190
|
+
}
|
|
191
|
+
/** What the person has on screen, as an export asks for it. The model's own filters go in `filters`. */
|
|
192
|
+
export interface ExcelExportQuery {
|
|
193
|
+
/** The search box's text. */
|
|
194
|
+
search?: string | null;
|
|
195
|
+
/** The sorted column, as the server names it. */
|
|
196
|
+
sort?: string | null;
|
|
197
|
+
/** Which way it is sorted. */
|
|
198
|
+
direction?: "asc" | "desc";
|
|
199
|
+
/** The filters the page holds, sent as they are: a list, a date range, a status. */
|
|
200
|
+
filters?: Record<string, unknown>;
|
|
201
|
+
}
|
|
202
|
+
/** A file an endpoint answered with, and what it says about itself. */
|
|
203
|
+
export interface ExcelDownload {
|
|
204
|
+
blob: Blob;
|
|
205
|
+
/** The name the answer gave it (`Content-Disposition`), else the one that was asked for. */
|
|
206
|
+
fileName: string;
|
|
207
|
+
/** Rows written into the file, when the answer says. */
|
|
208
|
+
rows?: number;
|
|
209
|
+
/** Rows asked for that did not fit under the limit. */
|
|
210
|
+
skipped?: number;
|
|
211
|
+
/** The most rows one export writes, as the backend has it. */
|
|
212
|
+
limit?: number;
|
|
213
|
+
/** What to tell the person when rows were left out. */
|
|
214
|
+
note?: string;
|
|
215
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Types of the spreadsheet side: what a model's sheet is made of, what a verify answers, what an import sends back, and
|
|
3
|
+
* the endpoints that serve all three. One shape travels both ways (`ExcelSheetRow`), so a verify and an import can
|
|
4
|
+
* never disagree about a row.
|
|
5
|
+
*
|
|
6
|
+
* A reference column travels as its **key** - an encrypted id made for the person who downloaded the file - with the
|
|
7
|
+
* words they picked beside it. The plain id in the sheet is theirs to read and is never read back, so a value copied
|
|
8
|
+
* out of a spreadsheet, or out of a verify answer, cannot be posted to an endpoint as an id.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* What one column of a sheet holds. It decides the cell's format in the file, the check the spreadsheet makes while
|
|
12
|
+
* the person types, and the input the review editors draw. The numbers are the backend's (`EExcelDataTypes`); an
|
|
13
|
+
* object, not an enum, so a test that strips types can import it.
|
|
14
|
+
*/
|
|
15
|
+
export const ExcelDataType = {
|
|
16
|
+
/** One line of text: a name, a code. */
|
|
17
|
+
Text: 0,
|
|
18
|
+
/** Many lines of text: a description, a note. */
|
|
19
|
+
Note: 1,
|
|
20
|
+
/** A whole number, no decimals. */
|
|
21
|
+
WholeNumber: 2,
|
|
22
|
+
/** A number with decimals. */
|
|
23
|
+
Decimal: 3,
|
|
24
|
+
/** Money: decimals fixed at the backend's places. */
|
|
25
|
+
Amount: 4,
|
|
26
|
+
/** A date with no time, as an ISO date: `2026-09-29`. */
|
|
27
|
+
Date: 5,
|
|
28
|
+
/** A date and a time, zone-less, as `2026-09-29 14:30`. */
|
|
29
|
+
DateTime: 6,
|
|
30
|
+
/** A time of day with no date: `14:30`. */
|
|
31
|
+
Time: 7,
|
|
32
|
+
/** Yes or No. */
|
|
33
|
+
YesNo: 8,
|
|
34
|
+
/** A choice from a list the backend owns, picked in the sheet and travelling as its key. */
|
|
35
|
+
List: 9,
|
|
36
|
+
};
|
|
37
|
+
/** What one row of an upload turned out to be. The backend's `EExcelRowKinds`. */
|
|
38
|
+
export const ExcelRowKind = {
|
|
39
|
+
/** Nothing is wrong with it and it is not a record yet: this is what an import writes. */
|
|
40
|
+
New: 0,
|
|
41
|
+
/** It is already a record, or repeats a row earlier in the file. Nothing updates it. */
|
|
42
|
+
Duplicate: 1,
|
|
43
|
+
/** Something in it has to be fixed before it can be written. */
|
|
44
|
+
Invalid: 2,
|
|
45
|
+
};
|
|
@@ -21,6 +21,8 @@ import type { InputDesign, OptionValue } from "./InputInterfaces.ts";
|
|
|
21
21
|
import type { Size } from "./TypeInterfaces.ts";
|
|
22
22
|
/** Where a form places an input. The input itself ignores these keys. */
|
|
23
23
|
export interface InputLayoutParams {
|
|
24
|
+
/** Optional heading before this field when the section changes (Form only). */
|
|
25
|
+
section?: string;
|
|
24
26
|
/** Columns the input spans in a form's 12-column grid, and in a group form's section. Default 12, the full width. */
|
|
25
27
|
span?: number;
|
|
26
28
|
/** The column's width in a tabular form: a CSS length ("9rem") or pixels. Default by type, e.g. 10rem for text. */
|
|
@@ -29,6 +31,17 @@ export interface InputLayoutParams {
|
|
|
29
31
|
grow?: number;
|
|
30
32
|
/** Leaves the input out, e.g. a field only some roles may see. Its value stays in the data as it was. */
|
|
31
33
|
hidden?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Other inputs of the same form this one follows (Form only). When any of their values changes, this input draws
|
|
36
|
+
* again — without mounting again, so nothing typed in it is lost — and its params are read afresh: a getter for
|
|
37
|
+
* `hidden`, `readOnly`, `readOnlyMessage`, `label`, `hint` or `options` answers for the values as they are now. Its
|
|
38
|
+
* own rule is checked again too, so a `required` that a getter just switched on or off counts at once. Name the
|
|
39
|
+
* input itself to follow its own typing (a hint that says what the typed text will become).
|
|
40
|
+
*
|
|
41
|
+
* An input that names any is asked `hidden` every time it draws, so it can come and go; one that names none is
|
|
42
|
+
* asked once, when the form draws.
|
|
43
|
+
*/
|
|
44
|
+
dependsOn?: readonly string[];
|
|
32
45
|
}
|
|
33
46
|
/** What every input's params have. */
|
|
34
47
|
export interface InputParamsBase<TType extends string = string> extends InputLayoutParams {
|
|
@@ -140,6 +153,42 @@ export interface RowsInputProps<TRow extends RowValue = RowValue> {
|
|
|
140
153
|
removable?: boolean;
|
|
141
154
|
/** Rows can be moved up and down. Default false. */
|
|
142
155
|
sortable?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Values a row's **position** decides rather than the user: a bitwise enum's value, a line number, a running total.
|
|
158
|
+
* It is asked after every change — a value typed, a row added, removed, duplicated or **moved** — with every row,
|
|
159
|
+
* and returns one patch per row (or nothing at all, or nothing for a row).
|
|
160
|
+
*
|
|
161
|
+
* The patches are written as if code had set them, so only the cells whose value really changed are drawn again:
|
|
162
|
+
* moving a row renumbers the column in front of the user and leaves focus where they put it. It runs after every
|
|
163
|
+
* change, so keep it quick, and pair it with `cellReadOnly` when the user must not type what it writes.
|
|
164
|
+
*/
|
|
165
|
+
deriveRows?: (rows: TRow[]) => (Partial<TRow> | null | undefined)[] | null | undefined;
|
|
166
|
+
/**
|
|
167
|
+
* Which cells hold a **generated** value rather than a typed one: an enum's bitwise value, a line's total. It is
|
|
168
|
+
* asked for every cell as it renders, so a column can lock and unlock while the form around it changes — params are
|
|
169
|
+
* fixed once a form opens, and this is the one thing about a cell that is not.
|
|
170
|
+
*
|
|
171
|
+
* Return the reason to lock the cell and say why (the input wears its read-only lock and shows the note), `true` to
|
|
172
|
+
* lock it without a reason, or nothing to leave the cell as its params have it. Locking a cell never stops code from
|
|
173
|
+
* writing it: that is the point.
|
|
174
|
+
*/
|
|
175
|
+
cellReadOnly?: (row: TRow, name: string, index: number) => string | boolean | null | undefined;
|
|
176
|
+
/**
|
|
177
|
+
* Rows the user did not write and may not change, such as the fields a class inherits from its parent: every cell of
|
|
178
|
+
* such a row is read-only with the reason returned, and in a tabular form its menu offers nothing and no row can be
|
|
179
|
+
* moved above it. Return the reason, `true` for no reason, or nothing. Keep such rows first: nothing reorders them.
|
|
180
|
+
*/
|
|
181
|
+
rowReadOnly?: (row: TRow, index: number) => string | boolean | null | undefined;
|
|
182
|
+
/**
|
|
183
|
+
* Params of one cell that differ from its column's: a picker whose choices depend on the row, such as a type argument
|
|
184
|
+
* that takes a key type in one row and a model in the next. Asked as the row draws, which is when one of its values
|
|
185
|
+
* changes; return the keys to change (`{ options, selectedOptions }`), or nothing for the column's own params.
|
|
186
|
+
*
|
|
187
|
+
* Return the **same object** for the same answer (keep them in a map by what decides them): a new object draws the
|
|
188
|
+
* cell again, and a picker indexes its options once per array. Rules still come from the column's params — a row's
|
|
189
|
+
* value is checked the same way in every row — so change what is offered here, not what is required.
|
|
190
|
+
*/
|
|
191
|
+
cellParams?: (row: TRow, name: string, index: number) => Partial<InputParams> | null | undefined;
|
|
143
192
|
/** Every input is read-only, and rows cannot be added, removed, or moved. */
|
|
144
193
|
readOnly?: boolean;
|
|
145
194
|
/** What the rows say when someone tries to change them. Default "Read-only: these rows cannot be changed." */
|
|
@@ -333,8 +382,9 @@ export interface ApiFormProps<TValues extends FormValues = FormValues> extends O
|
|
|
333
382
|
/** Shapes what is sent, e.g. `{ items: [values] }` for a batch endpoint. May be async. Default: the values. */
|
|
334
383
|
transform?: (values: TValues, context: FormSubmitContext<TValues>) => unknown;
|
|
335
384
|
/**
|
|
336
|
-
* Reads the server's answer. Default
|
|
337
|
-
* ASP.NET Core problem details (`{ title, errors }`), and
|
|
385
|
+
* Reads the server's answer. Default: the app's reader (`configureApi({ read })`), else `readApiResponse`: Nexus API
|
|
386
|
+
* responses (`{ isSuccess, message, inputName, result }`), ASP.NET Core problem details (`{ title, errors }`), and
|
|
387
|
+
* any JSON by its HTTP status.
|
|
338
388
|
*/
|
|
339
389
|
readResponse?: (body: unknown, status: number | undefined) => ApiResult;
|
|
340
390
|
/** After a save, once the saved values are the form's own, with what the server sent back: close a popup, update a table row. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The actions the builder knows by name (`MESSAGE_ACTION_NAMES`); `actionWords(name)` gives their words. */
|
|
2
|
-
export type MessageActionName = "load" | "save" | "create" | "update" | "delete" | "trash" | "
|
|
2
|
+
export type MessageActionName = "load" | "save" | "create" | "update" | "delete" | "trash" | "restore" | "recover" | "flag" | "unflag" | "pin" | "unpin" | "send" | "submit" | "upload" | "download" | "import" | "export" | "copy" | "approve" | "reject" | "cancel";
|
|
3
3
|
/** The words of an action. */
|
|
4
4
|
export interface ActionWords {
|
|
5
5
|
/** "save", or with the object inside: "move {object} to the trash". */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode, Ref } from "react";
|
|
2
2
|
import type { TableStore } from "../Helpers/TableStore.ts";
|
|
3
|
+
import type { ExcelEndpoints } from "./ExcelInterfaces.ts";
|
|
3
4
|
import type { Size, Tone } from "./TypeInterfaces.ts";
|
|
4
5
|
/** A row's id: its `rowKey` field, such as `id`. */
|
|
5
6
|
export type TableKey = string | number;
|
|
@@ -20,6 +21,15 @@ export interface TableColumn<TRow extends TableRow = TableRow> {
|
|
|
20
21
|
key: string;
|
|
21
22
|
/** The heading, and the column's name in the Columns menu and the details view. */
|
|
22
23
|
label: string;
|
|
24
|
+
/**
|
|
25
|
+
* The heading of the band above this column: "Amount details" over the quantity, the price, and the total. Columns
|
|
26
|
+
* next to each other that name the same group share one heading spanning them all, and the columns with no group keep
|
|
27
|
+
* one heading of their own, as tall as both rows. A table whose columns name no group has one header row, as before.
|
|
28
|
+
*
|
|
29
|
+
* The bands are read off the order on screen, so a column the user drags out of its run takes its group's heading
|
|
30
|
+
* with it and the run it left is drawn with the same heading twice.
|
|
31
|
+
*/
|
|
32
|
+
group?: string;
|
|
23
33
|
/** Default `text`. Numbers, money, and percents line up at the end; dates and times follow the user's settings. */
|
|
24
34
|
format?: TableColumnFormat;
|
|
25
35
|
/** Digits after the decimal point of a number (money: 2). */
|
|
@@ -144,6 +154,32 @@ export interface TableViewColumn<TRow extends TableRow = TableRow> {
|
|
|
144
154
|
/** Its width in pixels, once the user has set one. */
|
|
145
155
|
width?: number;
|
|
146
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* One cell of the band above the headings: a group's heading over the columns next to each other that name it, or a
|
|
159
|
+
* single column with no group, whose own heading stands in the band row and is as tall as both rows.
|
|
160
|
+
*/
|
|
161
|
+
export interface TableHeadBand<TRow extends TableRow = TableRow> {
|
|
162
|
+
/** The heading above the columns, or `null` for a column with no group. */
|
|
163
|
+
group: string | null;
|
|
164
|
+
/** The columns under it, in the order they are drawn. One, for a column with no group. */
|
|
165
|
+
columns: TableViewColumn<TRow>[];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* What a table needs to work a spreadsheet: the model's Excel endpoints, and what the page knows that the table does
|
|
169
|
+
* not. The search, the sort, and how many rows the search leaves are the table's own and are sent for it.
|
|
170
|
+
*/
|
|
171
|
+
export interface TableExcel {
|
|
172
|
+
/** The model's endpoints, from `excelEndpoints(controller)`. */
|
|
173
|
+
endpoints: ExcelEndpoints;
|
|
174
|
+
/** What one row is, for the questions and the messages: "city". Default: what the backend's settings say. */
|
|
175
|
+
subject?: string;
|
|
176
|
+
/** Values the endpoints' own params need, such as a parent's id. */
|
|
177
|
+
params?: Record<string, string | number>;
|
|
178
|
+
/** The filters the page holds, beside the table's search and sort, read when an export is asked for. */
|
|
179
|
+
filters?: () => Record<string, unknown>;
|
|
180
|
+
/** After an import wrote something. The table reloads itself either way. */
|
|
181
|
+
onImported?: () => void;
|
|
182
|
+
}
|
|
147
183
|
/** An item of the table's options menu (⋮): Import, Print, a report of the page's own. */
|
|
148
184
|
export interface TableMenuAction {
|
|
149
185
|
/** Tells items apart; a test or a style can find it by `data-action`. */
|
|
@@ -196,7 +232,7 @@ export interface DataTableProps<TRow extends TableRow = TableRow> {
|
|
|
196
232
|
load?: TableLoader<TRow>;
|
|
197
233
|
/** Default `client`. */
|
|
198
234
|
paging?: TablePaging;
|
|
199
|
-
/** Loads again, from the first page, when it changes: another list behind the same table, such as a CRUD's
|
|
235
|
+
/** Loads again, from the first page, when it changes: another list behind the same table, such as a CRUD's trash. */
|
|
200
236
|
loadKey?: string | number;
|
|
201
237
|
/** The field that tells rows apart. Default `id`. */
|
|
202
238
|
rowKey?: string | ((row: TRow) => TableKey);
|
|
@@ -215,8 +251,17 @@ export interface DataTableProps<TRow extends TableRow = TableRow> {
|
|
|
215
251
|
toolbar?: ReactNode;
|
|
216
252
|
/** At the end of the bar: an Add button. */
|
|
217
253
|
actions?: ReactNode;
|
|
218
|
-
/** The Columns
|
|
254
|
+
/** The Columns panel, where columns are shown, hidden, and dragged into another order. Default true. */
|
|
219
255
|
columnsMenu?: boolean;
|
|
256
|
+
/**
|
|
257
|
+
* The spreadsheet side of this table, against the backend's Excel controller. The options menu (⋮) then offers
|
|
258
|
+
* **Download template**, **Import from a spreadsheet** (the file is verified, the person goes over the rows, and the
|
|
259
|
+
* table reloads once they are written) and, where the endpoints have one, **Export to Excel** - which asks the
|
|
260
|
+
* server with the search and the sort on screen instead of writing a file of the rows this browser happens to hold.
|
|
261
|
+
*
|
|
262
|
+
* It replaces `onImport`, which is the hook for a page that imports its own way.
|
|
263
|
+
*/
|
|
264
|
+
excel?: TableExcel;
|
|
220
265
|
/** A Reload button. Default true when rows are loaded. */
|
|
221
266
|
reloadable?: boolean;
|
|
222
267
|
/** When the user presses Reload, before the rows load again: drop a cache of your own. */
|
|
@@ -249,6 +294,12 @@ export interface DataTableProps<TRow extends TableRow = TableRow> {
|
|
|
249
294
|
rowActions?: (row: TRow) => readonly TableRowAction<TRow>[];
|
|
250
295
|
/** A double-click on a row, such as to open its details. */
|
|
251
296
|
onRowOpen?: (row: TRow) => void;
|
|
297
|
+
/**
|
|
298
|
+
* A single click opens a row, not only a double-click: for tables whose rows are opened to be read more than they
|
|
299
|
+
* are selected. A click on a control in the row (its menu, a link, a checkbox) still does only what that control
|
|
300
|
+
* does, and so does a click that ends a text selection, so a value can still be copied out of a cell.
|
|
301
|
+
*/
|
|
302
|
+
openOnClick?: boolean;
|
|
252
303
|
/**
|
|
253
304
|
* Rows kept in a section above the page, which the user folds open: the records they pinned. They are the rows as
|
|
254
305
|
* they came from their own endpoint, and the same records also show in the list below. Leave it out for no section.
|
|
@@ -282,6 +333,15 @@ export interface DataTableProps<TRow extends TableRow = TableRow> {
|
|
|
282
333
|
emptyAction?: ReactNode;
|
|
283
334
|
/** The tallest the table grows before its rows scroll under a header that stays: pixels or a CSS length. */
|
|
284
335
|
maxHeight?: number | string;
|
|
336
|
+
/**
|
|
337
|
+
* The table takes the whole height it is given instead of growing with its rows: the bar stays on top, the rows
|
|
338
|
+
* scroll inside the frame under a header that stays, and the footer sits at the bottom of the page. The page around
|
|
339
|
+
* it never scrolls, so the footer never moves and no scrollbar comes and goes as rows load.
|
|
340
|
+
*
|
|
341
|
+
* It needs a parent with a height. An `AdminPage` gives it one: the admin shell is exactly the window, and a page
|
|
342
|
+
* holding a filling table stretches it to the room under the heading. `maxHeight` is ignored while it is on.
|
|
343
|
+
*/
|
|
344
|
+
fill?: boolean;
|
|
285
345
|
/** Row height and text size. Default `md`. */
|
|
286
346
|
size?: Extract<Size, "sm" | "md">;
|
|
287
347
|
id?: string;
|