nav
This commit is contained in:
@@ -2,6 +2,7 @@ import { requireArray, requireDefined, requireObject } from '../utils/apiShape';
|
|||||||
import { notifyError } from '../utils/notify';
|
import { notifyError } from '../utils/notify';
|
||||||
import { parseCategoryTreeResponse } from '../store/categorySlice';
|
import { parseCategoryTreeResponse } from '../store/categorySlice';
|
||||||
import { ApiClient } from './ApiClient';
|
import { ApiClient } from './ApiClient';
|
||||||
|
import articleAutomat from '../store/articleAutomat';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stale-while-revalidate cache: readers keep the current promise; update()
|
* Stale-while-revalidate cache: readers keep the current promise; update()
|
||||||
@@ -102,6 +103,10 @@ export class StorageManager extends ApiClient {
|
|||||||
requireDefined(article.sku, 'article.sku');
|
requireDefined(article.sku, 'article.sku');
|
||||||
const key = String(article.sku);
|
const key = String(article.sku);
|
||||||
this.#articlesBySku.set(key, article);
|
this.#articlesBySku.set(key, article);
|
||||||
|
|
||||||
|
// Update automat as well
|
||||||
|
articleAutomat.actions.setArticle(article);
|
||||||
|
|
||||||
const watchers = this.#articleWatchers.get(key);
|
const watchers = this.#articleWatchers.get(key);
|
||||||
if (watchers) {
|
if (watchers) {
|
||||||
for (const onArticle of watchers) onArticle(article);
|
for (const onArticle of watchers) onArticle(article);
|
||||||
@@ -329,6 +334,10 @@ export class StorageManager extends ApiClient {
|
|||||||
|
|
||||||
#onArticleSignal = (event) => {
|
#onArticleSignal = (event) => {
|
||||||
const sku = event.type.slice('article-'.length);
|
const sku = event.type.slice('article-'.length);
|
||||||
|
|
||||||
|
// Update automat on SSE article events
|
||||||
|
void articleAutomat.actions.loadArticle(sku).catch((err) => notifyError(err));
|
||||||
|
|
||||||
if (this.#articleWatchers.has(sku)) {
|
if (this.#articleWatchers.has(sku)) {
|
||||||
void this.loadArticle(sku).catch((err) => notifyError(err));
|
void this.loadArticle(sku).catch((err) => notifyError(err));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export * from './deliveryEditorAutomat';
|
export * from './deliveryEditorAutomat';
|
||||||
|
export { default as navigationAutomat } from './navigationAutomat';
|
||||||
|
export { setLastDeliveryPath, setLastArticleSku, clearNavigation } from './navigationAutomat';
|
||||||
|
|||||||
54
src/automats/navigationAutomat.js
Normal file
54
src/automats/navigationAutomat.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Automat } from 'react-automat';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation automat to store last visited delivery and article paths.
|
||||||
|
* Used to return users to their previous context when clicking main menu items.
|
||||||
|
*
|
||||||
|
* State shape:
|
||||||
|
* {
|
||||||
|
* lastDeliveryPath: string | null, // Last visited delivery path (e.g., '/lieferung/19' or '/lieferung/neu')
|
||||||
|
* lastArticleSku: string | null, // Last visited article SKU
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const initialState = {
|
||||||
|
lastDeliveryPath: null,
|
||||||
|
lastArticleSku: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigationAutomat = new Automat(
|
||||||
|
initialState,
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Set the last visited delivery path.
|
||||||
|
* @param {string|null} path - Full path like '/lieferung/19' or '/lieferung/neu'
|
||||||
|
*/
|
||||||
|
setLastDeliveryPath(path) {
|
||||||
|
navigationAutomat.setState({ lastDeliveryPath: path });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the last visited article SKU.
|
||||||
|
* @param {string|null} sku
|
||||||
|
*/
|
||||||
|
setLastArticleSku(sku) {
|
||||||
|
navigationAutomat.setState({ lastArticleSku: sku });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all navigation history.
|
||||||
|
*/
|
||||||
|
clearNavigation() {
|
||||||
|
navigationAutomat.setState({
|
||||||
|
lastDeliveryPath: null,
|
||||||
|
lastArticleSku: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'navigation',
|
||||||
|
persist: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default navigationAutomat;
|
||||||
|
export const { setLastDeliveryPath, setLastArticleSku, clearNavigation } = navigationAutomat.actions;
|
||||||
@@ -11,13 +11,34 @@ import UserMenu from './UserMenu';
|
|||||||
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
||||||
import { artikelQueryPath } from '../pages/artikel/artikelQuery';
|
import { artikelQueryPath } from '../pages/artikel/artikelQuery';
|
||||||
import { defaultCategoryPath } from '../pages/artikel/categoryRoute';
|
import { defaultCategoryPath } from '../pages/artikel/categoryRoute';
|
||||||
|
import { navigationAutomat } from '../automats';
|
||||||
|
|
||||||
const SIDEBAR_WIDTH = 144;
|
const SIDEBAR_WIDTH = 144;
|
||||||
const SUB_NAV_WIDTH = 88;
|
const SUB_NAV_WIDTH = 88;
|
||||||
|
|
||||||
class AppSidebar extends PureComponent {
|
class AppSidebar extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
lastDeliveryPath: navigationAutomat.state.lastDeliveryPath,
|
||||||
|
lastArticleSku: navigationAutomat.state.lastArticleSku,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.unsub = navigationAutomat.subscribe(this, (state) => ({
|
||||||
|
lastDeliveryPath: state.lastDeliveryPath,
|
||||||
|
lastArticleSku: state.lastArticleSku,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unsub?.();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { activeNav, navigate } = this.props;
|
const { activeNav, navigate } = this.props;
|
||||||
|
const { lastDeliveryPath, lastArticleSku } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -45,7 +66,21 @@ class AppSidebar extends PureComponent {
|
|||||||
selected={active}
|
selected={active}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(path === '/artikel' ? artikelQueryPath() : path);
|
if (path === '/artikel') {
|
||||||
|
if (lastArticleSku) {
|
||||||
|
navigate(`/artikel/${lastArticleSku}`);
|
||||||
|
} else {
|
||||||
|
navigate(artikelQueryPath());
|
||||||
|
}
|
||||||
|
} else if (path === '/lieferung') {
|
||||||
|
if (lastDeliveryPath) {
|
||||||
|
navigate(lastDeliveryPath);
|
||||||
|
} else {
|
||||||
|
navigate(path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigate(path);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
buildCategoryPath,
|
buildCategoryPath,
|
||||||
defaultCategoryPath,
|
defaultCategoryPath,
|
||||||
} from '../../pages/artikel/categoryRoute';
|
} from '../../pages/artikel/categoryRoute';
|
||||||
|
import articleAutomat, { createArticleSelector } from '../../store/articleAutomat';
|
||||||
|
|
||||||
import ArticleTileTab from './ArticleTileTab';
|
import ArticleTileTab from './ArticleTileTab';
|
||||||
import ArticlePhotoTile from './ArticlePhotoTile';
|
import ArticlePhotoTile from './ArticlePhotoTile';
|
||||||
@@ -24,34 +25,52 @@ class ArticleView extends PureComponent {
|
|||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
const { article, unwatch } = context.article.watch(
|
|
||||||
props.sku,
|
const sku = props.sku;
|
||||||
(next) => {
|
const currentArticle = articleAutomat.state[sku] ?? null;
|
||||||
this.setState({ article: next });
|
|
||||||
},
|
this.state = { article: currentArticle };
|
||||||
);
|
|
||||||
this.state = { article };
|
|
||||||
this.unwatchArticle = unwatch;
|
|
||||||
this.handleArticleDeleted = this.handleArticleDeleted.bind(this);
|
this.handleArticleDeleted = this.handleArticleDeleted.bind(this);
|
||||||
}
|
|
||||||
|
if (!currentArticle) {
|
||||||
componentDidUpdate(prevProps) {
|
articleAutomat.actions.loadArticle(sku).catch((err) => {
|
||||||
const { sku } = this.props;
|
console.error('Failed to load article:', err);
|
||||||
|
});
|
||||||
if (prevProps.sku !== sku) {
|
|
||||||
this.unwatchArticle?.();
|
|
||||||
const { article: next, unwatch } = this.context.article.watch(
|
|
||||||
sku,
|
|
||||||
(updated) => this.setState({ article: updated }),
|
|
||||||
);
|
|
||||||
this.unwatchArticle = unwatch;
|
|
||||||
this.setState({ article: next });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.subscribeToArticle();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.sku !== this.props.sku) {
|
||||||
|
const currentArticle = articleAutomat.state[this.props.sku] ?? null;
|
||||||
|
this.setState({ article: currentArticle });
|
||||||
|
|
||||||
|
if (!currentArticle) {
|
||||||
|
articleAutomat.actions.loadArticle(this.props.sku).catch((err) => {
|
||||||
|
console.error('Failed to load article:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscribeToArticle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToArticle() {
|
||||||
|
if (this.unsub) this.unsub();
|
||||||
|
const selector = createArticleSelector(this.props.sku);
|
||||||
|
this.unsub = articleAutomat.subscribe(this, (state) => ({
|
||||||
|
article: selector(state),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unwatchArticle?.();
|
if (this.unsub) {
|
||||||
this.unwatchArticle = null;
|
this.unsub();
|
||||||
|
this.unsub = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleArticleDeleted() {
|
handleArticleDeleted() {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
import { preserveSearchFocus } from './searchFocus';
|
import { preserveSearchFocus } from './searchFocus';
|
||||||
import { artikelDetailPath } from './articleRoute';
|
import { artikelDetailPath } from './articleRoute';
|
||||||
import withSearchParams from './withSearchParams';
|
import withSearchParams from './withSearchParams';
|
||||||
|
import { setLastArticleSku } from '../../automats';
|
||||||
|
|
||||||
const resultRowSx = {
|
const resultRowSx = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -171,6 +172,7 @@ class ArtikelSearchPage extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('mousedown', preserveSearchFocus, true);
|
document.addEventListener('mousedown', preserveSearchFocus, true);
|
||||||
window.addEventListener('resize', this.handleWindowResize);
|
window.addEventListener('resize', this.handleWindowResize);
|
||||||
|
setLastArticleSku(null);
|
||||||
|
|
||||||
// Kick off initial search (empty input = show all products).
|
// Kick off initial search (empty input = show all products).
|
||||||
const { input } = this.state;
|
const { input } = this.state;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import {
|
|||||||
enableTerminalCategory,
|
enableTerminalCategory,
|
||||||
disableTerminalCategory,
|
disableTerminalCategory,
|
||||||
} from '../../store/posterminalSlice';
|
} from '../../store/posterminalSlice';
|
||||||
|
import { setLastArticleSku } from '../../automats';
|
||||||
|
|
||||||
const gridSx = {
|
const gridSx = {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@@ -72,6 +73,7 @@ class ArtikelKategorienPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
setLastArticleSku(null);
|
||||||
this.ensureTreeLoaded();
|
this.ensureTreeLoaded();
|
||||||
this.ensurePosterminalsLoaded();
|
this.ensurePosterminalsLoaded();
|
||||||
this.syncCategoryFromUrl();
|
this.syncCategoryFromUrl();
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { setLastArticleSku } from '../../automats';
|
||||||
|
|
||||||
export default function ArtikelLagerPage() {
|
export default function ArtikelLagerPage() {
|
||||||
|
useEffect(() => {
|
||||||
|
setLastArticleSku(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography color="text.secondary">Lager — Inhalt folgt.</Typography>
|
<Typography color="text.secondary">Lager — Inhalt folgt.</Typography>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Component } from 'react';
|
|||||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||||
import { ArticleView } from '../../components/article';
|
import { ArticleView } from '../../components/article';
|
||||||
import withSkuRoute from './withSkuRoute';
|
import withSkuRoute from './withSkuRoute';
|
||||||
|
import { setLastArticleSku } from '../../automats';
|
||||||
|
|
||||||
class ArtikelSkuPage extends Component {
|
class ArtikelSkuPage extends Component {
|
||||||
static contextType = StorageManagerContext;
|
static contextType = StorageManagerContext;
|
||||||
@@ -17,11 +18,13 @@ class ArtikelSkuPage extends Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.installEscape();
|
this.installEscape();
|
||||||
|
setLastArticleSku(this.props.sku);
|
||||||
if (!this.state.ready) void this.ensureArticle();
|
if (!this.state.ready) void this.ensureArticle();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.sku !== this.props.sku) {
|
if (prevProps.sku !== this.props.sku) {
|
||||||
|
setLastArticleSku(this.props.sku);
|
||||||
const ready =
|
const ready =
|
||||||
this.context.getArticle(this.props.sku) != null &&
|
this.context.getArticle(this.props.sku) != null &&
|
||||||
this.context.getCategoryState() != null;
|
this.context.getCategoryState() != null;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { PureComponent } from 'react';
|
import { PureComponent } from 'react';
|
||||||
import { ManifestList } from '../../components/manifest';
|
import { ManifestList } from '../../components/manifest';
|
||||||
|
import { setLastDeliveryPath } from '../../automats';
|
||||||
|
|
||||||
class LieferungListePage extends PureComponent {
|
class LieferungListePage extends PureComponent {
|
||||||
|
componentDidMount() {
|
||||||
|
setLastDeliveryPath(null);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <ManifestList kind="delivery" />;
|
return <ManifestList kind="delivery" />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { StorageManagerContext } from '../../api/storageManagerContext';
|
|||||||
import { requireDefined, requireObject } from '../../utils/apiShape';
|
import { requireDefined, requireObject } from '../../utils/apiShape';
|
||||||
import { backendErrorFrom } from '../../utils/backendError';
|
import { backendErrorFrom } from '../../utils/backendError';
|
||||||
import { notifyError } from '../../utils/notify';
|
import { notifyError } from '../../utils/notify';
|
||||||
|
import { setLastDeliveryPath } from '../../automats';
|
||||||
|
|
||||||
function sortSuppliers(suppliers) {
|
function sortSuppliers(suppliers) {
|
||||||
return [...suppliers].sort((a, b) =>
|
return [...suppliers].sort((a, b) =>
|
||||||
@@ -48,6 +49,7 @@ class LieferungLieferantenPage extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
setLastDeliveryPath(null);
|
||||||
this._cancelled = false;
|
this._cancelled = false;
|
||||||
void this.loadSuppliers();
|
void this.loadSuppliers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { notifyError } from '../../utils/notify';
|
|||||||
import { generatePdfThumbnail } from '../../utils/pdfThumbnail';
|
import { generatePdfThumbnail } from '../../utils/pdfThumbnail';
|
||||||
import { extractPdfText } from '../../utils/pdfText';
|
import { extractPdfText } from '../../utils/pdfText';
|
||||||
import { getDeliveryEditorAutomat } from '../../automats/deliveryEditorAutomat';
|
import { getDeliveryEditorAutomat } from '../../automats/deliveryEditorAutomat';
|
||||||
|
import { setLastDeliveryPath } from '../../automats';
|
||||||
import { delivery as deliveryApi } from '../../api';
|
import { delivery as deliveryApi } from '../../api';
|
||||||
|
|
||||||
class LieferungNeuPage extends PureComponent {
|
class LieferungNeuPage extends PureComponent {
|
||||||
@@ -85,8 +86,10 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
this.unsubscribe = this.automat.subscribe(this);
|
this.unsubscribe = this.automat.subscribe(this);
|
||||||
this._cancelled = false;
|
this._cancelled = false;
|
||||||
if (this.props.docId != null) {
|
if (this.props.docId != null) {
|
||||||
|
setLastDeliveryPath(`/lieferung/${this.props.docId}`);
|
||||||
void this.loadEdit();
|
void this.loadEdit();
|
||||||
} else {
|
} else {
|
||||||
|
setLastDeliveryPath('/lieferung/neu');
|
||||||
void this.loadPartiesOnly();
|
void this.loadPartiesOnly();
|
||||||
if (this.props.initialDelivery) {
|
if (this.props.initialDelivery) {
|
||||||
this.automat.actions.setDelivery(this.props.initialDelivery);
|
this.automat.actions.setDelivery(this.props.initialDelivery);
|
||||||
|
|||||||
124
src/store/articleAutomat.js
Normal file
124
src/store/articleAutomat.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { Automat, fetchJson } from 'react-automat';
|
||||||
|
import { article } from '../api/index';
|
||||||
|
import { requireObject, requireDefined } from '../utils/apiShape';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Article automat store.
|
||||||
|
* Registered as window.artikel via the name option.
|
||||||
|
* State shape: { [sku: string]: article | null }
|
||||||
|
*
|
||||||
|
* Usage in PureComponent:
|
||||||
|
* ```jsx
|
||||||
|
* import articleAutomat, { createArticleSelector } from '../../store/articleAutomat';
|
||||||
|
*
|
||||||
|
* class ArticleView extends PureComponent {
|
||||||
|
* constructor(props) {
|
||||||
|
* super(props);
|
||||||
|
* const sku = props.sku;
|
||||||
|
* const currentArticle = articleAutomat.state[sku] ?? null;
|
||||||
|
* this.state = { article: currentArticle };
|
||||||
|
*
|
||||||
|
* if (!currentArticle) {
|
||||||
|
* articleAutomat.actions.loadArticle(sku).catch(console.error);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* componentDidMount() {
|
||||||
|
* this.subscribeToArticle();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* componentDidUpdate(prevProps) {
|
||||||
|
* if (prevProps.sku !== this.props.sku) {
|
||||||
|
* const currentArticle = articleAutomat.state[this.props.sku] ?? null;
|
||||||
|
* this.setState({ article: currentArticle });
|
||||||
|
*
|
||||||
|
* if (!currentArticle) {
|
||||||
|
* articleAutomat.actions.loadArticle(this.props.sku).catch(console.error);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* this.subscribeToArticle();
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* subscribeToArticle() {
|
||||||
|
* if (this.unsub) this.unsub();
|
||||||
|
* const selector = createArticleSelector(this.props.sku);
|
||||||
|
* this.unsub = articleAutomat.subscribe(this, (state) => ({
|
||||||
|
* article: selector(state),
|
||||||
|
* }));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* componentWillUnmount() {
|
||||||
|
* if (this.unsub) {
|
||||||
|
* this.unsub();
|
||||||
|
* this.unsub = null;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* render() {
|
||||||
|
* const { article } = this.state;
|
||||||
|
* if (!article) return null;
|
||||||
|
* return <div>{article.name}</div>;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
const articleAutomat = new Automat(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Load article by SKU and update the store.
|
||||||
|
* @param {string} sku
|
||||||
|
*/
|
||||||
|
async loadArticle(sku) {
|
||||||
|
const res = await article.getBySku(sku);
|
||||||
|
requireObject(res, 'GET /api/article/:sku');
|
||||||
|
requireObject(res.article, 'article');
|
||||||
|
requireDefined(res.article.sku, 'article.sku');
|
||||||
|
|
||||||
|
articleAutomat.setState({
|
||||||
|
[sku]: res.article,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.article;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update article state after a successful mutation.
|
||||||
|
* @param {object} article
|
||||||
|
*/
|
||||||
|
setArticle(article) {
|
||||||
|
requireObject(article, 'article');
|
||||||
|
requireDefined(article.sku, 'article.sku');
|
||||||
|
|
||||||
|
articleAutomat.setState({
|
||||||
|
[article.sku]: article,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear article from store (e.g., after deletion).
|
||||||
|
* @param {string} sku
|
||||||
|
*/
|
||||||
|
clearArticle(sku) {
|
||||||
|
articleAutomat.setState({
|
||||||
|
[sku]: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'artikel',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a selector function for a specific SKU.
|
||||||
|
* This avoids closure issues when the SKU changes.
|
||||||
|
* @param {string} sku - The article SKU
|
||||||
|
* @returns {(state: object) => object|null} Selector function
|
||||||
|
*/
|
||||||
|
export function createArticleSelector(sku) {
|
||||||
|
return (state) => state[sku] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default articleAutomat;
|
||||||
Reference in New Issue
Block a user