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) && (
<>

View File

@@ -28,7 +28,7 @@ import {
import categoryAutomat from '../../automats/categoryAutomat';
import posterminalAutomat from '../../automats/posterminalAutomat';
import companyAutomat from '../../automats/companyAutomat';
import { setLastArticleSku, setLastCategorySlug } from '../../automats';
import { setLastArticleSku, setLastCategorySlug, setLastCategoryId } from '../../automats';
import {
categoryDisplayName,
getCategoryBreadcrumbs,
@@ -225,8 +225,13 @@ class ArtikelKategorienPage extends PureComponent {
return;
}
const isRoot = rootCategoryId != null && String(resolvedId) === String(rootCategoryId);
const activeCatId = isRoot ? null : String(resolvedId);
const fullCatPath = isRoot ? '/kategorien' : buildCategoryPath(categories, rootCategoryId, resolvedId);
setLastCategoryId(activeCatId, fullCatPath);
if (hasLegacyHomePrefix) {
navigate(buildCategoryPath(categories, rootCategoryId, resolvedId), { replace: true });
navigate(fullCatPath, { replace: true });
}
const nextHandle = categoryAutomat.key(categorySlugPath ?? '');

View File

@@ -97,6 +97,7 @@ class LieferungNeuPage extends PureComponent {
this.handleExternalNumberChange = this.handleExternalNumberChange.bind(this);
this.handleLineChange = this.handleLineChange.bind(this);
this.handleAddLine = this.handleAddLine.bind(this);
this.handleAddArticleLine = this.handleAddArticleLine.bind(this);
this.handleRemoveLine = this.handleRemoveLine.bind(this);
this.handleRemoveAllLines = this.handleRemoveAllLines.bind(this);
this.handleSave = this.handleSave.bind(this);
@@ -506,6 +507,35 @@ class LieferungNeuPage extends PureComponent {
this.automat.actions.addLineitem();
}
handleAddArticleLine(article) {
if (!article) return;
const netVal =
article.price != null
? String(article.price)
: article.vknetto != null
? String(article.vknetto)
: article.net != null
? String(article.net)
: '';
const taxVal =
article.taxrate != null
? String(article.taxrate)
: article.tax != null
? String(article.tax)
: '';
this.automat.actions.addLineitem({
amount: 1,
sku: article.sku || '',
name: article.name || '',
tax: taxVal,
taxrate: taxVal,
net: netVal,
netprice: netVal,
pu: 1,
});
}
handleRemoveLine(key) {
const index = this.findLineIndex(key);
this.automat.actions.removeLineitem(index >= 0 ? index : key);
@@ -1307,6 +1337,7 @@ class LieferungNeuPage extends PureComponent {
onLineChange={this.handleLineChange}
onSkuBlur={this.handleSkuBlur}
onAddLine={this.handleAddLine}
onAddArticleLine={this.handleAddArticleLine}
onRemoveLine={this.handleRemoveLine}
onRemoveAllLines={this.handleRemoveAllLines}
onSave={() => void this.handleSave()}