Files
dashfront/src/routes/artikel/_sku.jsx
2026-09-29 09:30:50 +02:00

119 lines
3.2 KiB
JavaScript

import { Component } from 'react';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import { StorageManagerContext } from '../../api/storageManagerContext';
import BackendError from '../../components/BackendError';
import { ArticleView } from '../../components/article';
import {
artikelAutomat,
createArticleSelector,
getArticle,
setLastArticleSku,
} from '../../automats';
import withSkuRoute from '../../components/article/withSkuRoute';
/** Full-screen loading state while the shared article cache resolves. */
function ArticleLoading() {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
<CircularProgress size={32} />
</Box>
);
}
class ArtikelSkuPage extends Component {
static contextType = StorageManagerContext;
constructor(props, context) {
super(props, context);
this.state = {
article: getArticle(props.sku),
ready: context.getCategoryState() != null,
error: null,
};
}
componentDidMount() {
this._mounted = true;
this.installEscape();
setLastArticleSku(this.props.sku);
this.unsub = artikelAutomat.subscribe(this, (state) => ({
article: createArticleSelector(this.props.sku)(state),
}));
void this.ensureArticle();
}
componentDidUpdate(prevProps) {
if (prevProps.sku !== this.props.sku) {
setLastArticleSku(this.props.sku);
this.setState({ article: getArticle(this.props.sku), error: null });
this.unsub?.();
this.unsub = artikelAutomat.subscribe(this, (state) => ({
article: createArticleSelector(this.props.sku)(state),
}));
void this.ensureArticle();
}
}
componentWillUnmount() {
this._mounted = false;
this.removeEscape();
this.unsub?.();
this.unsub = null;
}
/**
* Delegates the article fetch to the shared artikelAutomat cache; also waits
* for the category tree/company/session the view tiles depend on.
*/
async ensureArticle() {
const sku = this.props.sku;
if (!sku) return;
try {
await Promise.all([
artikelAutomat.actions.loadArticle(sku),
this.context.categoryTreePromise,
this.context.companyPromise,
this.context.authMePromise,
]);
if (this._mounted) this.setState({ ready: true });
} catch (error) {
if (this._mounted) this.setState({ error });
}
}
handleRetry = () => {
this.setState({ error: null });
void this.ensureArticle();
};
installEscape() {
this._onKeyDown = (e) => {
if (e.key !== 'Escape') return;
e.preventDefault();
this.props.navigate(-1);
};
document.addEventListener('keydown', this._onKeyDown);
}
removeEscape() {
if (this._onKeyDown) {
document.removeEventListener('keydown', this._onKeyDown);
this._onKeyDown = null;
}
}
render() {
const { sku } = this.props;
const { article, ready, error } = this.state;
if (error) {
return <BackendError error={error} sx={{ mt: 2 }} onRetry={this.handleRetry} />;
}
if (!article || !ready) return <ArticleLoading />;
return <ArticleView sku={sku} />;
}
}
export default withSkuRoute(ArtikelSkuPage);