This commit is contained in:
seb
2026-10-05 10:33:18 +02:00
parent 5b4cf1683a
commit 8c5419c89e
15 changed files with 1613 additions and 320 deletions

View File

@@ -32,23 +32,46 @@ import {
formatCategoryList,
formatStock,
getCategoryDepth,
getCategoryBreadcrumbLabel,
parseFilter,
} from '../components/article/searchFormat';
import { preserveSearchFocus } from '../components/article/searchFocus';
import { artikelDetailPath } from '../components/article/articleRoute';
import { buildCategoryPath } from '../components/category/categoryRoute';
import withSearchParams from '../components/article/withSearchParams';
import { setLastArticleSku, setLastSearchTerm, navigationAutomat } from '../automats';
import {
setLastArticleSku,
setLastSearchTerm,
setLastCategoryId,
navigationAutomat,
categoryAutomat,
} from '../automats';
import { BrandLabel } from '../components/brand';
const resultRowSx = {
alignItems: 'center',
py: 1.5,
minHeight: 64,
maxHeight: 64,
height: 64,
boxSizing: 'border-box',
py: 0,
// Keep every row one line so sample height matches real rows.
'& .MuiListItemText-root': { minWidth: 0 },
'& .MuiListItemText-primary, & .MuiListItemText-secondary': {
'& .MuiListItemText-root': { minWidth: 0, my: 0 },
'& .MuiListItemText-primary': {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
lineHeight: '22px',
height: '22px',
},
'& .MuiListItemText-secondary': {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
lineHeight: '20px',
height: '20px',
display: 'flex',
alignItems: 'center',
},
};
@@ -68,7 +91,7 @@ function ResultRowMeasureSample() {
<ListItemText
primary={'\u00A0'}
secondary={
<Typography component="span" variant="body2" sx={{ visibility: 'hidden' }} noWrap>
<Typography component="span" variant="body2" sx={{ visibility: 'hidden', lineHeight: '20px', height: '20px' }} noWrap>
&nbsp;
</Typography>
}
@@ -111,6 +134,28 @@ function initialSearchState(searchParams) {
};
}
function getCategoryTreeOrder(categoriesMap, rootCategoryId) {
const order = new Map();
let index = 0;
function traverse(parentId) {
const parentKey = String(parentId);
const children = Object.values(categoriesMap || {})
.filter((item) => item && item.parentId != null && String(item.parentId) === parentKey)
.sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0) || Number(a.id) - Number(b.id));
for (const child of children) {
order.set(String(child.id), index++);
traverse(child.id);
}
}
const rootId = rootCategoryId ?? 1;
traverse(rootId);
return order;
}
class ArtikelSearchPage extends Component {
state = initialSearchState(this.props.searchParams);
paginationRef = createRef();
@@ -154,8 +199,22 @@ class ArtikelSearchPage extends Component {
this.unsubSearch = articleSearchAutomat.subscribe(this, (state) => state.result);
// Initial search: kicks off index loading if idle and derives current items
const { filter, offset } = queryFromParams(this.props.searchParams);
const { filter: urlFilter, offset } = queryFromParams(this.props.searchParams);
const hasFilterInUrl = this.props.searchParams.has('filter');
const navCategory = navigationAutomat.state.lastCategoryId;
const filter = hasFilterInUrl ? urlFilter : (navCategory || urlFilter);
const activeTarget = this.state.input;
if (!hasFilterInUrl && navCategory) {
const syncedUrl = buildSearchUrl(activeTarget, filter, offset);
setLastSearchTerm(activeTarget, syncedUrl);
this.props.navigate(syncedUrl, { replace: true });
} else if (hasFilterInUrl) {
const selectedCats = parseFilter(urlFilter);
const primaryCatId = selectedCats.length > 0 ? selectedCats[0] : null;
setLastCategoryId(primaryCatId);
}
setLastSearchTerm(activeTarget, buildSearchUrl(activeTarget, filter, offset));
void articleSearchAutomat.actions.search({
target: activeTarget,
@@ -213,6 +272,20 @@ class ArtikelSearchPage extends Component {
});
const nextUrl = buildSearchUrl(nextTarget, nextFilter, nextOffset);
setLastSearchTerm(nextTarget, nextUrl);
// Synchronize category with navigationAutomat and /kategorien/
const selectedCats = parseFilter(nextFilter);
const primaryCatId = selectedCats.length > 0 ? selectedCats[0] : null;
const categoriesMap = categoryAutomat.state?.categories;
const rootCategoryId = categoryAutomat.state?.rootCategoryId;
const isRoot = !primaryCatId || (rootCategoryId != null && String(primaryCatId) === String(rootCategoryId));
const activeId = isRoot ? null : String(primaryCatId);
const catPath = activeId && categoriesMap && rootCategoryId != null
? buildCategoryPath(categoriesMap, rootCategoryId, activeId)
: '/kategorien';
setLastCategoryId(activeId, catPath);
this.props.navigate(nextUrl, {
replace: true,
});
@@ -275,16 +348,37 @@ class ArtikelSearchPage extends Component {
const categories = parseFilter(filterParam);
const rows = recordset ?? [];
const categoriesMap = categoryAutomat.state?.categories;
const rootCategoryId = categoryAutomat.state?.rootCategoryId;
const treeOrder = getCategoryTreeOrder(categoriesMap, rootCategoryId);
const facetEntries = categoryFacets
? Object.entries(categoryFacets)
.filter(([, meta]) => meta?.name)
.filter(([id, meta]) => {
if (!meta?.name) return false;
if (String(id) === '1' || (rootCategoryId != null && String(id) === String(rootCategoryId))) {
return false;
}
const lower = meta.name.trim().toLowerCase();
if (lower === 'kategorie 1' || lower === 'category 1' || lower === 'home') {
return false;
}
return true;
})
.map(([id, { count, name }]) => ({
id,
count,
name,
depth: getCategoryDepth(id, categoryFacets),
}))
.sort((a, b) => Number(a.id) - Number(b.id))
.sort((a, b) => {
const orderA = treeOrder.get(String(a.id));
const orderB = treeOrder.get(String(b.id));
if (orderA != null && orderB != null) return orderA - orderB;
if (orderA != null) return -1;
if (orderB != null) return 1;
return Number(a.id) - Number(b.id);
})
: [];
const showPagination =
@@ -338,11 +432,16 @@ class ArtikelSearchPage extends Component {
</Button>
)}
{categories.map((catId) => {
const catName = categoryFacets?.[catId]?.name ?? `Kategorie ${catId}`;
const label = getCategoryBreadcrumbLabel(
catId,
categoriesMap,
rootCategoryId,
categoryFacets,
);
return (
<Chip
key={catId}
label={catName}
label={label}
color="primary"
variant="outlined"
onDelete={() => {
@@ -482,7 +581,14 @@ class ArtikelSearchPage extends Component {
variant="body2"
color="text.secondary"
noWrap
sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, maxWidth: '100%' }}
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
maxWidth: '100%',
lineHeight: '20px',
height: '20px',
}}
>
{(row.brand?.name || row.brandName) && (
<>