This commit is contained in:
seb
2026-08-01 03:37:40 +02:00
parent 4ea593a0c8
commit 41bc76303a
10 changed files with 904 additions and 68 deletions

134
README.redux.md Normal file
View File

@@ -0,0 +1,134 @@
# Redux in Dashfront
Übersicht, wie Redux Toolkit bisher im Frontend eingesetzt wird.
## Stack
- **@reduxjs/toolkit** — Store, Slices, Async Thunks, Listener Middleware
- **react-redux** — `Provider` in `src/main.jsx`, Anbindung der UI über `connect()` (kein `useSelector` / `useDispatch`)
Einstieg:
```
src/main.jsx → <Provider store={store}>
src/store/index.js → configureStore + Middleware
src/store/*Slice.js → State, Thunks, Selectors
```
## Store-Form
Drei Slices unter `src/store/`:
| Key | Datei | Verantwortung |
|------------|---------------------|----------------------------------------------------|
| `auth` | `authSlice.js` | Session, Login/Logout, Firmenwahl / -anlage |
| `company` | `companySlice.js` | Aktuelles Unternehmen laden / aktualisieren |
| `category` | `categorySlice.js` | Kategoriebaum, Navigation, Artikel je Kategorie |
```js
// src/store/index.js
configureStore({
reducer: { auth, company, category },
middleware: defaults + listenerMiddleware + (DEV: reduxDevLogger),
});
```
## Typische Muster
### Async Thunks als API-Grenze
Seiten- und Dialog-Logik dispatcht Thunks; die Thunks rufen `src/api` auf und schreiben Ergebnisse in den Slice. Komponenten sprechen die API nicht direkt für diese Domänen an.
Beispiele:
- `auth/bootstrap`, `auth/login`, `auth/selectCompany`, `auth/createCompany`, `auth/logout`
- `company/loadCurrent`, `company/update`
- `category/loadTree`, `category/loadArticles`, `category/create`, `category/createArticle`, `category/delete`
Fehler in Category-Thunks laufen über `catchBackendError` / `rejectWithValue` und landen als `BackendError` in `state.category.error`. Tree- und Artikel-Responses werden mit `requireObject` / `requireArray` / `requireDefined` aus `apiShape.js` validiert.
### Selectors
Jeder Slice exportiert `select…`-Funktionen. UI-Komponenten mappen sie in `mapStateToProps`:
```js
const mapStateToProps = (state) => ({
status: selectAuthStatus(state),
needsCompanySetup: selectNeedsCompanySetup(state),
});
export default connect(mapStateToProps)(App);
```
Abgeleitete Category-Selectors (Breadcrumbs, Kinder, „kann zurück“) lesen die Map `categories` und `currentCategoryId` — keine zweite Kopie des Baums in der UI.
### `connect()` statt Hooks
Die Redux-gebundenen Komponenten sind Class- oder Function-Komponenten mit:
```js
connect(mapStateToProps, mapDispatchToProps)(Component)
```
Dispatch-Props wrappen Thunks, oft mit `.unwrap()` in der Komponente für Erfolg/Fehler (Snackbar).
### Listener: Auth → Company
Nach erfolgreichem Bootstrap, Login, Firmenwahl oder Firmenanlage dispatcht ein Listener automatisch `loadCurrentCompany`, wenn `auth.user.selectedCompanyId` gesetzt ist (`companyLoadListener` in `companySlice.js`).
### Cross-Slice-Reset
- `logout.fulfilled` leert `company` und setzt `category` auf `initialState`
- `loadCurrentCompany.fulfilled` mit neuer `companyId` setzt den Category-State zurück (Firmenwechsel)
## Datenfluss (Kurz)
```
App-Start
└─ AuthBootstrap → bootstrapSession
└─ (listener) loadCurrentCompany wenn selectedCompanyId
└─ Category-UI → loadCategoryTree (braucht company.rootCategoryId)
Login / Firmenwahl / -anlage
└─ auth-Thunks → user.selectedCompanyId
└─ (listener) loadCurrentCompany → ggf. Category-Reset
Kategorien-Seite
└─ navigateToCategory / navigateBack (sync)
└─ loadCategoryArticles für Unterkategorien
└─ createCategory / createArticle / deleteCategory (optimistisch im Map-/Listen-State)
```
Status-Felder:
| Slice | Feld(er) | Werte (typisch) |
|----------|----------------------------------|------------------------|
| auth | `status` | `loading` / `anon` / `authed` |
| company | `status` | `idle` / `loading` / `succeeded` / `failed` |
| category | `treeStatus`, `articlesStatus` | `idle` / `loading` / `succeeded` / `failed` |
## Auth-Slice
- **State:** `user`, `googleClientId`, `status`
- **Setup-Gate:** `selectNeedsCompanySetup` → eingeloggt, aber keine `selectedCompanyId` → `CompanySetupDialog`
- `mergeUser` behält beim Partial-Update ggf. die bisherige `selectedCompanyId`
## Company-Slice
- **State:** `current` (normalisiert über `normalizeCompany`), `status`, `error`
- Thunks lesen `auth.user.selectedCompanyId` aus dem Store (`getState`)
- Selectors liefern u. a. Name, `iso639`, Steuersätze, `rootCategoryId` für Category/UI
## Category-Slice
- **Baum:** `categories` als Id→Node-Map, plus `rootCategoryId`, `rootArticles`, `languageId`
- **Navigation:** `currentCategoryId`; Root zeigt `rootArticles`, Unterkategorien laden per `loadCategoryArticles`
- **Sync-Actions:** `navigateToCategory`, `navigateBack`, `resetCategoryState`
- Hilfsfunktionen (`normalizeCategoryNode`, `parseCategoryTreeResponse`, Breadcrumbs, Slugs) leben im Slice und werden u. a. von `categoryRoute.js` für URL-Pfade wiederverwendet
## Dev-Logging
In Development loggt `reduxDevLogger` Actions unter `auth/`, `company/`, `category/` inkl. Payload und kurzem Auth-/Company-Snapshot nach dem Reduce.
## Was (noch) nicht in Redux liegt
Artikel-Detail, Fotos und ähnliche lokale UI-Zustände werden in Komponenten / lokalen Props gehalten, nicht als eigener Slice. Neue Domänen folgen dem bestehenden Muster: API in `src/api`, Thunk + Validierung im Slice, Selectors + `connect` in der UI.