119 lines
3.2 KiB
JavaScript
119 lines
3.2 KiB
JavaScript
import { Component } from 'react';
|
|
import Box from '@mui/material/Box';
|
|
import CircularProgress from '@mui/material/CircularProgress';
|
|
import { getCategoryState, whenCategoryTreeReady } from '../../automats/categoryAutomat';
|
|
import companyAutomat from '../../automats/companyAutomat';
|
|
import { bootstrapAuthMe } from '../../automats/authAutomat';
|
|
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 {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
article: getArticle(props.sku),
|
|
ready: 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),
|
|
whenCategoryTreeReady(),
|
|
companyAutomat.actions.load(),
|
|
bootstrapAuthMe(),
|
|
]);
|
|
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);
|