u
This commit is contained in:
@@ -9,13 +9,11 @@ import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import { article } from '../../api';
|
||||
import { StorageManagerContext } from '../../api/storageManager';
|
||||
import BackendError from '../../components/BackendError';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { formatPrice } from '../../utils/price';
|
||||
import {
|
||||
buildSearchUrl,
|
||||
clearArtikelQuery,
|
||||
queryFromParams,
|
||||
readArtikelQuery,
|
||||
writeArtikelQuery,
|
||||
@@ -82,6 +80,8 @@ function initialSearchState(searchParams) {
|
||||
}
|
||||
|
||||
class ArtikelSearchPage extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
state = initialSearchState(this.props.searchParams);
|
||||
listSlotRef = { current: null };
|
||||
rowMeasureRef = { current: null };
|
||||
@@ -145,7 +145,7 @@ class ArtikelSearchPage extends Component {
|
||||
|
||||
this.setState({ pageSize: next }, () => {
|
||||
const target = this.props.searchParams.get('target') ?? '';
|
||||
if (target.trim().length >= 3) this.fetchResults();
|
||||
if (target) this.fetchResults();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -166,6 +166,16 @@ class ArtikelSearchPage extends Component {
|
||||
componentDidMount() {
|
||||
document.addEventListener('mousedown', preserveSearchFocus, true);
|
||||
window.addEventListener('resize', this.handleWindowResize);
|
||||
|
||||
// Kick off initial search (empty input = show all products).
|
||||
const { input } = this.state;
|
||||
this.context.doSearch({
|
||||
target: input,
|
||||
filter: this.props.searchParams.get('filter') || undefined,
|
||||
offset: 0,
|
||||
maxCount: this.state.pageSize ?? 20,
|
||||
onUpdate: (result) => this.setState(result),
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -206,78 +216,35 @@ class ArtikelSearchPage extends Component {
|
||||
|
||||
handleInputChange = (event) => {
|
||||
const value = event.target.value;
|
||||
const belowMin = value.trim().length < 3;
|
||||
this.setState({ input: value });
|
||||
|
||||
if (belowMin) {
|
||||
clearArtikelQuery();
|
||||
// Clear results in the same update that shortens the input — otherwise the
|
||||
// hint appears one paint before fetchResults (driven by URL) removes the list.
|
||||
this.setState({
|
||||
input: value,
|
||||
categoryFacets: null,
|
||||
totalCount: null,
|
||||
recordset: null,
|
||||
error: null,
|
||||
pageSize: null,
|
||||
});
|
||||
} else {
|
||||
this.setState({ input: value });
|
||||
}
|
||||
|
||||
this.updateParams(value, this.props.searchParams.get('filter') ?? '', 0);
|
||||
this.context.doSearch({
|
||||
target: value,
|
||||
filter: this.props.searchParams.get('filter') || undefined,
|
||||
offset: 0,
|
||||
maxCount: this.state.pageSize ?? 20,
|
||||
onUpdate: (result) => this.setState(result),
|
||||
});
|
||||
};
|
||||
|
||||
fetchResults = async () => {
|
||||
fetchResults = () => {
|
||||
const { target, filter, offset } = queryFromParams(this.props.searchParams);
|
||||
const trimmed = target.trim();
|
||||
const { pageSize } = this.state;
|
||||
|
||||
if (trimmed.length < 3) {
|
||||
const { categoryFacets, totalCount, recordset, error, pageSize: ps } = this.state;
|
||||
if (
|
||||
categoryFacets != null
|
||||
|| totalCount != null
|
||||
|| recordset != null
|
||||
|| error != null
|
||||
|| ps != null
|
||||
) {
|
||||
this.setState({
|
||||
categoryFacets: null,
|
||||
totalCount: null,
|
||||
recordset: null,
|
||||
error: null,
|
||||
pageSize: null,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageSize == null) return;
|
||||
|
||||
writeArtikelQuery({ target, filter, offset });
|
||||
|
||||
try {
|
||||
const data = await article.search({
|
||||
target: trimmed,
|
||||
filter: filter || undefined,
|
||||
offset,
|
||||
maxCount: pageSize,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
categoryFacets: data.categories ?? {},
|
||||
totalCount: data.length ?? 0,
|
||||
recordset: data.recordset ?? [],
|
||||
error: null,
|
||||
});
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
categoryFacets: null,
|
||||
totalCount: null,
|
||||
recordset: null,
|
||||
error: backendErrorFrom(err),
|
||||
});
|
||||
}
|
||||
// Delegate fetch + debounce to the global StorageManager.
|
||||
// The callback pushes results into local component state.
|
||||
this.context.doSearch({
|
||||
target: trimmed,
|
||||
filter: filter || undefined,
|
||||
offset,
|
||||
maxCount: pageSize,
|
||||
onUpdate: (result) => this.setState(result),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -286,7 +253,7 @@ class ArtikelSearchPage extends Component {
|
||||
const offset = Math.max(0, Number(this.props.searchParams.get('offset')) || 0);
|
||||
const categories = parseFilter(filterParam);
|
||||
// Drive UI from input so hint vs results switch in one paint (URL lags one tick).
|
||||
const searchActive = input.trim().length >= 3;
|
||||
const searchActive = input.trim().length > 0 || (recordset ?? []).length > 0;
|
||||
const rows = recordset ?? [];
|
||||
|
||||
const facetEntries = categoryFacets
|
||||
@@ -339,12 +306,6 @@ class ArtikelSearchPage extends Component {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{input.trim().length < 3 && (
|
||||
<Typography color="text.secondary">
|
||||
Mindestens 3 Zeichen eingeben — mehrere Wörter werden mit UND verknüpft.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<BackendError
|
||||
error={error}
|
||||
@@ -420,7 +381,7 @@ class ArtikelSearchPage extends Component {
|
||||
</Box>
|
||||
|
||||
<Box ref={this.setListSlotRef}>
|
||||
{rows.length > 0 && pageSize != null && (
|
||||
{rows.length > 0 && (
|
||||
<List component={Paper} variant="outlined" disablePadding>
|
||||
{rows.map((row, index) => (
|
||||
<ListItemButton
|
||||
|
||||
Reference in New Issue
Block a user