@efa-one/sdk 1.26.0 → 1.27.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/README.md
CHANGED
|
@@ -281,6 +281,13 @@ Excel export for such data. No backend or kernel change.
|
|
|
281
281
|
scrollbar sits above the table and scrolls in sync with the bottom one. Not on
|
|
282
282
|
mobile. The border moved to an outer wrapper.
|
|
283
283
|
|
|
284
|
+
**1.27.0 — `useRecords`: edit first, read-only without permission.** No kernel or backend change.
|
|
285
|
+
- `editable` (default `true`): `false` opens records read-only, no pencil, no PUT
|
|
286
|
+
(`editable: can(user, 'write')`).
|
|
287
|
+
- `openIn: 'edit'`: a click on a record opens the form directly (with `editable`); default
|
|
288
|
+
`'read'` keeps read → pencil → edit.
|
|
289
|
+
- `dialogProps` now carries `mode`, `onModeChange` and `readOnly`; do not pass them yourself.
|
|
290
|
+
|
|
284
291
|
**1.26.0 — `useRecords`, `ConfirmDeleteDialog`, `Switch`.** No kernel or backend change.
|
|
285
292
|
- `useRecords({ endpoint, apiBase, emptyForm, toForm, toPayload?, required?, validate? })`
|
|
286
293
|
runs the whole records flow: load the list, open for create (`openCreate`) or read
|
package/frontend/ui/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { Skeleton, SkeletonRow } from './Skeleton.js';
|
|
|
11
11
|
export { RecordDialog, type RecordDialogMode, type RecordDialogProps } from './RecordDialog.js';
|
|
12
12
|
export { ConfirmDeleteDialog, type ConfirmDeleteDialogProps } from './ConfirmDeleteDialog.js';
|
|
13
13
|
export { useRecords, type UseRecordsResult } from './useRecords.js';
|
|
14
|
-
export { createRecordsController, type RecordsController, type RecordsOptions, type RecordsState, type RecordsSaveMode, } from './records.js';
|
|
14
|
+
export { createRecordsController, type RecordsController, type RecordsOptions, type RecordsState, type RecordsSaveMode, type RecordsDialogMode, } from './records.js';
|
|
15
15
|
export { useIsMobile, MOBILE_QUERY } from '../useIsMobile.js';
|
|
16
16
|
export { DataTable, DEFAULT_VIEW_VERSION, type ColumnDef, type FilterDef, type DataTableSelectionProps, } from './DataTable.js';
|
|
17
17
|
export { toCsv, toXlsx, downloadFile, type ExportCell, type ExportTable } from './tableExport.js';
|
package/frontend/ui/records.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type RecordsSaveMode = 'create' | 'edit';
|
|
2
|
+
export type RecordsDialogMode = 'read' | 'edit' | 'create';
|
|
2
3
|
export interface RecordsOptions<T, F> {
|
|
3
4
|
endpoint: string;
|
|
4
5
|
apiBase: string | (() => string);
|
|
@@ -8,6 +9,8 @@ export interface RecordsOptions<T, F> {
|
|
|
8
9
|
required?: ReadonlyArray<keyof F>;
|
|
9
10
|
validate?: (form: F, mode: RecordsSaveMode) => string | null | undefined;
|
|
10
11
|
getId?: (row: T) => string;
|
|
12
|
+
editable?: boolean;
|
|
13
|
+
openIn?: 'read' | 'edit';
|
|
11
14
|
updateMethod?: 'PUT' | 'PATCH';
|
|
12
15
|
fetchImpl?: typeof fetch;
|
|
13
16
|
}
|
|
@@ -19,6 +22,7 @@ export interface RecordsState<T, F> {
|
|
|
19
22
|
id: string;
|
|
20
23
|
item?: T;
|
|
21
24
|
} | null;
|
|
25
|
+
mode: RecordsDialogMode;
|
|
22
26
|
form: F;
|
|
23
27
|
saving: boolean;
|
|
24
28
|
saveError: string;
|
|
@@ -35,6 +39,7 @@ export interface RecordsController<T, F> {
|
|
|
35
39
|
openCreate(): void;
|
|
36
40
|
openRecord(row: T): void;
|
|
37
41
|
close(): void;
|
|
42
|
+
setMode(mode: RecordsDialogMode): void;
|
|
38
43
|
setForm(next: F | ((prev: F) => F)): void;
|
|
39
44
|
setField<K extends keyof F>(key: K, value: F[K]): void;
|
|
40
45
|
isComplete(): boolean;
|
package/frontend/ui/records.js
CHANGED
|
@@ -30,6 +30,7 @@ export function createRecordsController(initial) {
|
|
|
30
30
|
loading: true,
|
|
31
31
|
loadError: '',
|
|
32
32
|
open: null,
|
|
33
|
+
mode: 'read',
|
|
33
34
|
form: initial.emptyForm(),
|
|
34
35
|
saving: false,
|
|
35
36
|
saveError: '',
|
|
@@ -110,6 +111,10 @@ export function createRecordsController(initial) {
|
|
|
110
111
|
const open = state.open;
|
|
111
112
|
if (!open)
|
|
112
113
|
return;
|
|
114
|
+
if (mode === 'edit' && options.editable === false) {
|
|
115
|
+
set({ saveError: 'Keine Berechtigung zum Ändern.' });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
113
118
|
if (!isComplete()) {
|
|
114
119
|
set({ saveError: 'Bitte alle Pflichtfelder ausfüllen.' });
|
|
115
120
|
return;
|
|
@@ -195,7 +200,7 @@ export function createRecordsController(initial) {
|
|
|
195
200
|
set({ loadError: '' });
|
|
196
201
|
},
|
|
197
202
|
openCreate() {
|
|
198
|
-
set({ open: { id: NEW_ID }, form: options.emptyForm(), saveError: '' });
|
|
203
|
+
set({ open: { id: NEW_ID }, mode: 'create', form: options.emptyForm(), saveError: '' });
|
|
199
204
|
},
|
|
200
205
|
openRecord(row) {
|
|
201
206
|
const id = tryId(row);
|
|
@@ -204,11 +209,17 @@ export function createRecordsController(initial) {
|
|
|
204
209
|
set({ loadError: NO_ID });
|
|
205
210
|
return;
|
|
206
211
|
}
|
|
207
|
-
|
|
212
|
+
const mode = options.openIn === 'edit' && options.editable !== false ? 'edit' : 'read';
|
|
213
|
+
set({ open: { id, item: row }, mode, form: options.toForm(row), saveError: '' });
|
|
208
214
|
},
|
|
209
215
|
close() {
|
|
210
216
|
set({ open: null, saveError: '' });
|
|
211
217
|
},
|
|
218
|
+
setMode(mode) {
|
|
219
|
+
if (mode !== 'edit' || options.editable === false || !state.open?.item || state.mode === 'edit')
|
|
220
|
+
return;
|
|
221
|
+
set({ mode: 'edit' });
|
|
222
|
+
},
|
|
212
223
|
setForm(next) {
|
|
213
224
|
const form = typeof next === 'function' ? next(state.form) : next;
|
|
214
225
|
set({ form });
|
|
@@ -14,7 +14,7 @@ export interface UseRecordsResult<T, F> {
|
|
|
14
14
|
openCreate(): void;
|
|
15
15
|
openRecord(row: T): void;
|
|
16
16
|
close(): void;
|
|
17
|
-
dialogProps: Pick<RecordDialogProps, 'open' | 'onOpenChange' | 'onSave' | 'saving' | 'saveDisabled' | 'error' | 'onErrorDismiss'>;
|
|
17
|
+
dialogProps: Pick<RecordDialogProps, 'open' | 'onOpenChange' | 'mode' | 'onModeChange' | 'readOnly' | 'onSave' | 'saving' | 'saveDisabled' | 'error' | 'onErrorDismiss'>;
|
|
18
18
|
requestDelete(row: T): void;
|
|
19
19
|
deleteTarget: T | null;
|
|
20
20
|
deleteDialogProps: Pick<ConfirmDeleteDialogProps, 'open' | 'deleting' | 'error' | 'onConfirm' | 'onCancel' | 'onErrorDismiss'>;
|
|
@@ -10,6 +10,7 @@ export function useRecords(options) {
|
|
|
10
10
|
}, [controller, options.endpoint]);
|
|
11
11
|
const state = useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
|
|
12
12
|
const complete = requiredFilled(state.form, options.required);
|
|
13
|
+
const readOnly = options.editable === false;
|
|
13
14
|
return useMemo(() => ({
|
|
14
15
|
rows: state.rows,
|
|
15
16
|
loading: state.loading,
|
|
@@ -29,6 +30,9 @@ export function useRecords(options) {
|
|
|
29
30
|
if (!next)
|
|
30
31
|
controller.close();
|
|
31
32
|
},
|
|
33
|
+
mode: readOnly && state.mode === 'edit' ? 'read' : state.mode,
|
|
34
|
+
onModeChange: controller.setMode,
|
|
35
|
+
readOnly,
|
|
32
36
|
onSave: controller.save,
|
|
33
37
|
saving: state.saving,
|
|
34
38
|
saveDisabled: !complete,
|
|
@@ -45,5 +49,5 @@ export function useRecords(options) {
|
|
|
45
49
|
onCancel: controller.cancelDelete,
|
|
46
50
|
onErrorDismiss: controller.dismissDeleteError,
|
|
47
51
|
},
|
|
48
|
-
}), [controller, state, complete]);
|
|
52
|
+
}), [controller, state, complete, readOnly]);
|
|
49
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efa-one/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "Offizielles SDK für efa-one-Apps: Auth/Session, Service-Discovery, Gateway-Client, Audit/Reporting/Mail/Notifications, Permission- und Capability-Registrierung sowie das Frontend-IPC-/i18n-Toolkit.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "commonjs",
|