This commit is contained in:
seb
2026-09-23 08:16:14 +02:00
parent 49057dc0d6
commit c94ce20b7a
7 changed files with 306 additions and 437 deletions

View File

@@ -24,7 +24,7 @@ import {
resolveCategoryIdFromSlugPath,
ROOT_CATEGORY_NAME,
} from './categoryRoute';
import categoryAutomat from '../../automats/categoryAutomat';
import categoryAutomat, { getOrCreateCategoryAutomat } from '../../automats/categoryAutomat';
import posterminalAutomat from '../../automats/posterminalAutomat';
import companyAutomat from '../../automats/companyAutomat';
import { setLastArticleSku } from '../../automats';
@@ -37,29 +37,13 @@ import {
// ─── Helper selectors for this page ────────────────────────────────────────────
function selectFromCategory(s) {
const { categories, rootCategoryId, currentCategoryId, articles, articlesTotal, renameMode, treeStatus, articlesStatus, error } = s;
const breadcrumbs = getCategoryBreadcrumbs(categories, currentCategoryId, rootCategoryId);
const childCategories = getCategoryChildren(categories, currentCategoryId);
const canGoBack =
currentCategoryId != null &&
rootCategoryId != null &&
String(currentCategoryId) !== String(rootCategoryId) &&
categories?.[String(currentCategoryId)]?.parentId != null &&
categories[String(currentCategoryId)].parentId !== 0;
const { categories, rootCategoryId, renameMode, treeStatus, error } = s;
return {
categories,
rootCategoryId,
treeStatus,
articlesStatus,
error,
currentCategoryId,
breadcrumbs,
childCategories,
articles,
articlesTotal,
canGoBack,
renameMode,
treeStatus,
error,
};
}
@@ -76,9 +60,30 @@ const gridSx = {
class ArtikelKategorienPage extends PureComponent {
constructor(props) {
super(props);
const catState = selectFromCategory(categoryAutomat.state);
const { categories, rootCategoryId, treeStatus } = catState;
let resolvedCategoryId = null;
if (treeStatus === 'succeeded' && categories && rootCategoryId != null) {
resolvedCategoryId = resolveCategoryIdFromSlugPath(
categories,
rootCategoryId,
props.categorySlugPath,
);
}
this.routeAutomat = resolvedCategoryId != null
? getOrCreateCategoryAutomat({ slugPath: props.categorySlugPath, categoryId: resolvedCategoryId })
: null;
this.state = {
drafts: {},
...selectFromCategory(categoryAutomat.state),
...catState,
resolvedCategoryId,
articles: this.routeAutomat?.state?.articles ?? [],
loaded: this.routeAutomat?.state?.loaded ?? false,
loading: this.routeAutomat?.state?.loading ?? false,
routeError: this.routeAutomat?.state?.error ?? null,
...selectTerminals(posterminalAutomat.state),
companyId: companyAutomat.state.current?.id ?? null,
moveCategory: null,
@@ -99,31 +104,28 @@ class ArtikelKategorienPage extends PureComponent {
this.ensureTreeLoaded();
this.ensurePosterminalsLoaded();
this.syncCategoryFromUrl();
this.ensureArticlesLoaded();
this.subscribeRouteAutomat();
this.syncSortable();
}
componentDidUpdate(prevState) {
// Note: PureComponent — comparisons use this.state not this.props
componentDidUpdate(prevProps, prevState) {
if (this.state.companyId !== prevState.companyId) {
this.ensureTreeLoaded();
}
if (this.state.treeStatus === 'succeeded' && prevState.treeStatus !== 'succeeded') {
this.syncCategoryFromUrl();
this.ensureArticlesLoaded();
}
if (
this.state.treeStatus === 'succeeded' &&
this.props.categorySlugPath !== (this._prevSlugPath ?? this.props.categorySlugPath)
this.props.categorySlugPath !== prevProps.categorySlugPath ||
this.state.categories !== prevState.categories ||
this.state.rootCategoryId !== prevState.rootCategoryId
) {
this.syncCategoryFromUrl();
}
this._prevSlugPath = this.props.categorySlugPath;
if (this.state.currentCategoryId !== prevState.currentCategoryId) {
this.ensureArticlesLoaded();
}
if (this.state.renameMode !== prevState.renameMode) {
if (
this.state.renameMode !== prevState.renameMode ||
(this.state.renameMode && this.state.resolvedCategoryId !== prevState.resolvedCategoryId)
) {
this.syncSortable();
}
}
@@ -132,16 +134,33 @@ class ArtikelKategorienPage extends PureComponent {
this._unsubCat?.();
this._unsubTerm?.();
this._unsubCompany?.();
this._unsubRoute?.();
this.sortableModule?.destroy();
}
subscribeRouteAutomat() {
this._unsubRoute?.();
this._unsubRoute = null;
if (this.routeAutomat) {
this._unsubRoute = this.routeAutomat.subscribe(this, (s) => ({
articles: s.articles,
loaded: s.loaded,
loading: s.loading,
routeError: s.error,
}));
}
}
#ensureModules = () => {
const el = this.gridRef.current;
if (!el) return;
if (!this.sortableModule) {
this.sortableModule = new CategorySortable(el, {
onReorder: ({ categoryIds }) =>
categoryAutomat.actions.reorderCategories({ categoryIds }),
categoryAutomat.actions.reorderCategories({
parentId: this.state.resolvedCategoryId,
categoryIds,
}),
});
}
};
@@ -168,19 +187,12 @@ class ArtikelKategorienPage extends PureComponent {
void posterminalAutomat.actions.load();
};
ensureArticlesLoaded = () => {
const { currentCategoryId, articlesStatus, rootCategoryId, treeStatus } = this.state;
if (treeStatus !== 'succeeded' || !currentCategoryId || articlesStatus !== 'idle') return;
if (String(currentCategoryId) === String(rootCategoryId)) return;
void categoryAutomat.actions.loadArticles(currentCategoryId);
};
syncCategoryFromUrl = () => {
const {
categories,
rootCategoryId,
currentCategoryId,
treeStatus,
resolvedCategoryId,
} = this.state;
const { categorySlugPath, navigate } = this.props;
@@ -204,8 +216,23 @@ class ArtikelKategorienPage extends PureComponent {
navigate(buildCategoryPath(categories, rootCategoryId, resolvedId), { replace: true });
}
if (resolvedId !== currentCategoryId) {
categoryAutomat.actions.navigateTo(resolvedId);
const cleanSlug = (categorySlugPath ?? '').replace(/^\/+|\/+$/g, '');
const currentRouteSlug = this.routeAutomat?.state?.slug;
if (resolvedId !== resolvedCategoryId || cleanSlug !== currentRouteSlug || !this.routeAutomat) {
this._unsubRoute?.();
this.routeAutomat = getOrCreateCategoryAutomat({
slugPath: categorySlugPath,
categoryId: resolvedId,
});
this.setState({
resolvedCategoryId: resolvedId,
articles: this.routeAutomat?.state?.articles ?? [],
loaded: this.routeAutomat?.state?.loaded ?? false,
loading: this.routeAutomat?.state?.loading ?? false,
routeError: this.routeAutomat?.state?.error ?? null,
});
this.subscribeRouteAutomat();
}
};
@@ -266,14 +293,20 @@ class ArtikelKategorienPage extends PureComponent {
};
handleRetryArticles = () => {
const { currentCategoryId } = this.state;
if (currentCategoryId) {
void categoryAutomat.actions.loadArticles(currentCategoryId);
if (this.routeAutomat) {
if (typeof this.routeAutomat.retry === 'function') {
void this.routeAutomat.retry();
} else if (typeof this.routeAutomat.actions?.retry === 'function') {
void this.routeAutomat.actions.retry();
} else {
void this.routeAutomat.setDirty({ keep: false, now: true });
}
}
};
handleNavigateBack = () => {
const { breadcrumbs } = this.state;
const { categories, resolvedCategoryId, rootCategoryId } = this.state;
const breadcrumbs = getCategoryBreadcrumbs(categories, resolvedCategoryId, rootCategoryId);
if (breadcrumbs.length < 2) return;
this.navigateToCategoryById(breadcrumbs[breadcrumbs.length - 2].id);
};
@@ -289,13 +322,12 @@ class ArtikelKategorienPage extends PureComponent {
categories,
rootCategoryId,
treeStatus,
articlesStatus,
error,
canGoBack,
breadcrumbs,
currentCategoryId,
childCategories,
resolvedCategoryId,
articles,
loaded,
loading,
routeError,
renameMode,
terminals,
drafts,
@@ -335,7 +367,16 @@ class ArtikelKategorienPage extends PureComponent {
);
}
const showArticlesLoading = articlesStatus === 'loading';
const breadcrumbs = getCategoryBreadcrumbs(categories, resolvedCategoryId, rootCategoryId);
const childCategories = getCategoryChildren(categories, resolvedCategoryId);
const canGoBack =
resolvedCategoryId != null &&
rootCategoryId != null &&
String(resolvedCategoryId) !== String(rootCategoryId) &&
categories?.[String(resolvedCategoryId)]?.parentId != null &&
categories[String(resolvedCategoryId)].parentId !== 0;
const showArticlesLoading = loading;
return (
<>
@@ -344,9 +385,9 @@ class ArtikelKategorienPage extends PureComponent {
onNavigate={this.navigateToCategoryById}
/>
{error && articlesStatus === 'failed' && (
{routeError && (
<BackendError
error={error}
error={routeError}
sx={{ mb: 2 }}
onRetry={this.handleRetryArticles}
/>
@@ -356,12 +397,12 @@ class ArtikelKategorienPage extends PureComponent {
{canGoBack && (
<BackTile
onClick={this.handleNavigateBack}
data-id={String(breadcrumbs[breadcrumbs.length - 2].id)}
data-id={breadcrumbs.length >= 2 ? String(breadcrumbs[breadcrumbs.length - 2].id) : undefined}
className="sortable-item not-sortable"
/>
)}
{childCategories.map((item, index) => {
{childCategories.map((item) => {
const label = categoryDisplayName(item.name, item.id, rootCategoryId);
const draftName =
drafts[item.id] !== undefined
@@ -469,10 +510,10 @@ class ArtikelKategorienPage extends PureComponent {
/>
))}
{renameMode && <NewCategoryTile parentId={currentCategoryId} />}
{renameMode && articlesStatus === 'succeeded' && (
{renameMode && <NewCategoryTile parentId={resolvedCategoryId} />}
{renameMode && loaded && (
<NewArticleTile
parentId={currentCategoryId}
parentId={resolvedCategoryId}
onOpenArticle={this.handleArticleClick}
sx={{ gridColumnStart: 1 }}
/>
@@ -505,3 +546,4 @@ class ArtikelKategorienPage extends PureComponent {
}
export default withCategoryRoute(ArtikelKategorienPage);