@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
@@ -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';
@@ -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;
@@ -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
- set({ open: { id, item: row }, form: options.toForm(row), saveError: '' });
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.26.0",
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",