u
This commit is contained in:
108
src/automats/articleSearchAutomat.js
Normal file
108
src/automats/articleSearchAutomat.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import { Automat } from 'react-automat';
|
||||
import articleIndexAutomat from './articleIndexAutomat';
|
||||
|
||||
/**
|
||||
* @typedef {Object} ArticleSearchResult
|
||||
* @property {Record<string,object>} categoryFacets Facet counts per category id.
|
||||
* @property {number} totalCount Number of matching records.
|
||||
* @property {Array<object>} recordset Matching article index records.
|
||||
* @property {Error|null} error Search failure, or `null`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ArticleSearchState
|
||||
* @property {ArticleSearchResult} result Latest search outcome.
|
||||
* @property {string} target Active search term.
|
||||
* @property {boolean} loading Whether a search is in flight.
|
||||
*/
|
||||
|
||||
/** @type {ArticleSearchResult} */
|
||||
const emptyResult = {
|
||||
categoryFacets: {},
|
||||
totalCount: 0,
|
||||
recordset: [],
|
||||
error: null,
|
||||
};
|
||||
|
||||
/** @type {ArticleSearchState} */
|
||||
const initialState = {
|
||||
result: emptyResult,
|
||||
target: '',
|
||||
loading: false,
|
||||
};
|
||||
|
||||
let articleSearchAutomat;
|
||||
|
||||
/**
|
||||
* Matches one index record against a lowercased search term.
|
||||
*
|
||||
* @param {object} row Article index record.
|
||||
* @param {string} query Lowercased search term.
|
||||
* @returns {boolean} Whether the record matches.
|
||||
*/
|
||||
function matches(row, query) {
|
||||
if (String(row.name ?? '').toLowerCase().includes(query)) return true;
|
||||
if (String(row.sku ?? '').toLowerCase().includes(query)) return true;
|
||||
return (row.eans ?? []).some((ean) => String(ean).includes(query));
|
||||
}
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
* Runs a client-side search over the article index.
|
||||
*
|
||||
* The full index is already cached by `articleIndexAutomat`, so this does not
|
||||
* hit the network once the index has loaded.
|
||||
*
|
||||
* @param {string} target Search term; an empty string returns everything.
|
||||
* @returns {Promise<ArticleSearchResult>} Search outcome.
|
||||
*/
|
||||
async search(target) {
|
||||
articleSearchAutomat.setState({ target, loading: true });
|
||||
try {
|
||||
const articles = await articleIndexAutomat.actions.ensureLoaded();
|
||||
const query = String(target ?? '').trim().toLowerCase();
|
||||
const recordset = query
|
||||
? articles.filter((row) => matches(row, query))
|
||||
: articles;
|
||||
const result = {
|
||||
categoryFacets: {},
|
||||
totalCount: recordset.length,
|
||||
recordset,
|
||||
error: null,
|
||||
};
|
||||
articleSearchAutomat.setState({ result, loading: false });
|
||||
return result;
|
||||
} catch (error) {
|
||||
const result = { ...emptyResult, error };
|
||||
articleSearchAutomat.setState({ result, loading: false });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/** Reset the result set. */
|
||||
reset() {
|
||||
articleSearchAutomat.setState(initialState);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Article search state for the `/artikel` overview page.
|
||||
*
|
||||
* Replaces `StorageManager.doSearch()`; components subscribe to `result`
|
||||
* instead of receiving a callback.
|
||||
*
|
||||
* @type {Automat<ArticleSearchState>}
|
||||
*/
|
||||
articleSearchAutomat = new Automat(initialState, actions, { name: 'articleSearch' });
|
||||
|
||||
/**
|
||||
* Selects the latest search result.
|
||||
*
|
||||
* @param {ArticleSearchState} state Automat state.
|
||||
* @returns {ArticleSearchResult} Search outcome.
|
||||
*/
|
||||
export function selectSearchResult(state) {
|
||||
return state.result;
|
||||
}
|
||||
|
||||
export default articleSearchAutomat;
|
||||
Reference in New Issue
Block a user