169 lines
4.6 KiB
Markdown
169 lines
4.6 KiB
Markdown
# `brandAutomat`
|
|
|
|
> **Source File**: [`src/automats/brandAutomat.js`](file:///home/seb/src/dashfront/src/automats/brandAutomat.js)
|
|
> **Registration Key**: `'brand'` (`Automat.get('brand')` / `window.__AUTOMATS__.get('brand')`)
|
|
|
|
---
|
|
|
|
## 1. Overview & Purpose
|
|
|
|
`brandAutomat` manages the active company's catalog brands (manufacturers). Brands are referenced by articles for catalog organization, filtering, and GPSR (General Product Safety Regulation) manufacturer compliance info.
|
|
|
|
### Core Responsibilities
|
|
- **Company Scoping**: Caches all brands for the currently selected company.
|
|
- **Alphabetical Sorting**: Automatically sorts brands by name using German (`'de'`) collation.
|
|
- **Autonomous Bootstrap**: Automatically triggers `load()` when `authAutomat` enters `'authed'` with a selected company, and calls `reset()` when the session ends.
|
|
- **Upsert Mutation**: Handles creating or updating a brand with immediate in-memory cache synchronization.
|
|
|
|
---
|
|
|
|
## 2. State Structure
|
|
|
|
```ts
|
|
interface Brand {
|
|
id: number | string;
|
|
name: string;
|
|
gsprpinfo?: string | null;
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface BrandState {
|
|
/** Alphabetically sorted list of company brands. */
|
|
brands: Array<Brand>;
|
|
/** Async lifecycle status. */
|
|
status: 'idle' | 'loading' | 'succeeded' | 'failed';
|
|
/** Error message if loading failed. */
|
|
error: string | null;
|
|
}
|
|
```
|
|
|
|
### Initial State
|
|
```js
|
|
const initialState = {
|
|
brands: [],
|
|
status: 'idle',
|
|
error: null,
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Configuration & Options
|
|
|
|
```js
|
|
new Automat(initialState, actions, { name: 'brand' });
|
|
```
|
|
|
|
- **`name`**: `'brand'`
|
|
- **`persist`**: `false`
|
|
|
|
---
|
|
|
|
## 4. Actions & Methods
|
|
|
|
Access actions via `brandAutomat.actions.<method>()`.
|
|
|
|
### `load(): Promise<Array<Brand>>`
|
|
Fetches all brands for the current company via `GET /api/brand`. Sorts them by name, updates `status` to `'succeeded'`, and returns the array. If the request fails, sets `status` to `'failed'` and records `error`.
|
|
|
|
### `upsert(body: { name: string, gsprpinfo?: string }): Promise<Brand>`
|
|
Creates or updates a brand via `POST /api/brand/upsert`. Merges the updated brand into `state.brands`, re-sorts the list, updates state, and returns the modified brand object.
|
|
|
|
### `reset(): void`
|
|
Resets the state back to `initialState` (`brands: []`, `status: 'idle'`, `error: null`).
|
|
|
|
---
|
|
|
|
## 5. Cross-Automat Subscriptions
|
|
|
|
`brandAutomat` subscribes to `authAutomat` at module initialization:
|
|
|
|
```js
|
|
authAutomat.subscribe((auth) => {
|
|
const companyId = auth.user?.selectedCompanyId;
|
|
if (auth.status === 'authed' && companyId) {
|
|
void brandAutomat.actions.load();
|
|
} else if (auth.status === 'anon') {
|
|
brandAutomat.actions.reset();
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Usage Examples
|
|
|
|
### A. React `PureComponent` Brand Selector Dropdown
|
|
|
|
```jsx
|
|
import { PureComponent } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import brandAutomat from '../automats/brandAutomat';
|
|
|
|
export class BrandSelect extends PureComponent {
|
|
static propTypes = {
|
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
onChange: PropTypes.func.isRequired,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
brands: brandAutomat.state.brands,
|
|
status: brandAutomat.state.status,
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.unsub = brandAutomat.subscribe(this, ['brands', 'status']);
|
|
if (this.state.status === 'idle') {
|
|
brandAutomat.actions.load().catch(console.error);
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.unsub?.();
|
|
}
|
|
|
|
render() {
|
|
const { brands, status } = this.state;
|
|
const { value, onChange } = this.props;
|
|
|
|
if (status === 'loading') {
|
|
return <select disabled><option>Marken laden...</option></select>;
|
|
}
|
|
|
|
return (
|
|
<select value={value ?? ''} onChange={(e) => onChange(e.target.value)}>
|
|
<option value="">-- Keine Marke --</option>
|
|
{brands.map((b) => (
|
|
<option key={b.id} value={b.id}>{b.name}</option>
|
|
))}
|
|
</select>
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### B. Creating or Updating a Brand
|
|
|
|
```js
|
|
import brandAutomat from '../automats/brandAutomat';
|
|
|
|
async function handleSaveBrand(name, gsprpinfo) {
|
|
try {
|
|
const brand = await brandAutomat.actions.upsert({ name, gsprpinfo });
|
|
console.log('Saved brand:', brand);
|
|
} catch (err) {
|
|
console.error('Failed to save brand:', err);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Best Practices & Gotchas
|
|
|
|
1. **Automatic Loading**: In normal usage, you rarely need to invoke `load()` manually; `authAutomat` triggers it as soon as the user logs in or switches company.
|
|
2. **Alphabetical Guarantee**: The `brands` array in state is guaranteed to be sorted alphabetically (`localeCompare` with German rules).
|