202 lines
6.9 KiB
JavaScript
202 lines
6.9 KiB
JavaScript
/**
|
|
* @file navigationAutomat.js
|
|
* @description Stores the user's most recently visited delivery document path,
|
|
* article SKU, article search term/path, and category slug/path to preserve
|
|
* navigation context across primary and secondary section switches.
|
|
*
|
|
* Upstream Triggers:
|
|
* - UI / Routes: Set by article search, category navigation, and delivery views/routes
|
|
* via `setLastDeliveryPath(path)`, `setLastArticleSku(sku)`, `setLastSearchTerm(term, path)`,
|
|
* `setLastCategorySlug(slug)`, and `clearNavigation()`.
|
|
*/
|
|
|
|
import { Automat } from 'react-automat';
|
|
import categoryAutomat from './categoryAutomat';
|
|
import { buildCategoryPath, resolveCategoryIdFromSlugPath } from '../components/category/categoryRoute';
|
|
import { buildSearchUrl } from '../components/article/artikelQuery';
|
|
|
|
/**
|
|
* @typedef {Object} NavigationState
|
|
* @property {string|null} lastDeliveryPath Last delivery path visited by the user.
|
|
* @property {string|null} lastArticleSku Last article SKU visited by the user.
|
|
* @property {string|null} lastSearchTerm Last article search query term.
|
|
* @property {string|null} lastArticleSearchPath Full last search path (e.g. `/artikel?target=apfel`).
|
|
* @property {string|null} lastCategoryId Last visited category ID.
|
|
* @property {string|null} lastCategorySlug Last visited category slug (e.g. `obst-gemuese/obst`).
|
|
* @property {string|null} lastCategoryPath Full last category path (e.g. `/kategorien/obst-gemuese/obst`).
|
|
*/
|
|
|
|
/** @type {NavigationState} */
|
|
const initialState = {
|
|
lastDeliveryPath: null,
|
|
lastArticleSku: null,
|
|
lastSearchTerm: null,
|
|
lastArticleSearchPath: null,
|
|
lastCategoryId: null,
|
|
lastCategorySlug: null,
|
|
lastCategoryPath: null,
|
|
};
|
|
|
|
/**
|
|
* Stores the user's most recently visited delivery, article, search term, and category
|
|
* so main-menu and sub-menu navigation can restore the previous context.
|
|
*
|
|
* @type {Automat<NavigationState>}
|
|
*/
|
|
const navigationAutomat = new Automat(
|
|
initialState,
|
|
{
|
|
/**
|
|
* Set the last visited delivery path.
|
|
*
|
|
* @param {string|null} path Full path such as `/lieferung/19` or `/lieferung/neu`.
|
|
* @returns {void}
|
|
*/
|
|
setLastDeliveryPath(path) {
|
|
navigationAutomat.setState({ lastDeliveryPath: path });
|
|
},
|
|
|
|
/**
|
|
* Set the last visited article SKU.
|
|
*
|
|
* @param {string|null} sku
|
|
* @returns {void}
|
|
*/
|
|
setLastArticleSku(sku) {
|
|
navigationAutomat.setState({ lastArticleSku: sku });
|
|
},
|
|
|
|
/**
|
|
* Set the last active article search term and optional search URL.
|
|
*
|
|
* @param {string|null} term Search input term.
|
|
* @param {string|null} [path] Full search URL.
|
|
* @returns {void}
|
|
*/
|
|
setLastSearchTerm(term, path = null) {
|
|
const trimmed = term != null ? String(term) : '';
|
|
const currentCatId = navigationAutomat.state.lastCategoryId ?? '';
|
|
const searchPath = path ?? buildSearchUrl(trimmed, currentCatId, 0);
|
|
navigationAutomat.setState({
|
|
lastSearchTerm: trimmed || null,
|
|
lastArticleSearchPath: searchPath,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set the full last article search URL path.
|
|
*
|
|
* @param {string|null} path Full search URL path (e.g. `/artikel?target=apfel`).
|
|
* @param {string|null} [term] Search term.
|
|
* @returns {void}
|
|
*/
|
|
setLastArticleSearchPath(path, term = null) {
|
|
navigationAutomat.setState({
|
|
lastArticleSearchPath: path,
|
|
...(term !== null ? { lastSearchTerm: term } : {}),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set the active category ID and keep categoryPath & articleSearchPath in sync.
|
|
*
|
|
* @param {string|number|null} categoryId Category ID or null for root.
|
|
* @param {string|null} [path] Full category path if already known.
|
|
* @returns {void}
|
|
*/
|
|
setLastCategoryId(categoryId, path = null) {
|
|
const id = categoryId != null ? String(categoryId) : null;
|
|
const categories = categoryAutomat.state?.categories;
|
|
const rootCategoryId = categoryAutomat.state?.rootCategoryId;
|
|
const isRoot = !id || (rootCategoryId != null && id === String(rootCategoryId));
|
|
|
|
let categoryPath = path;
|
|
if (!categoryPath) {
|
|
if (!isRoot && categories && rootCategoryId != null) {
|
|
categoryPath = buildCategoryPath(categories, rootCategoryId, id);
|
|
} else {
|
|
categoryPath = '/kategorien';
|
|
}
|
|
}
|
|
|
|
const cleanSlug = categoryPath.replace(/^\/kategorien\/?/, '').replace(/\/+$/, '') || null;
|
|
const currentTerm = navigationAutomat.state.lastSearchTerm ?? '';
|
|
const filter = isRoot ? '' : id;
|
|
const searchPath = buildSearchUrl(currentTerm, filter, 0);
|
|
|
|
navigationAutomat.setState({
|
|
lastCategoryId: isRoot ? null : id,
|
|
lastCategorySlug: isRoot ? null : cleanSlug,
|
|
lastCategoryPath: categoryPath,
|
|
lastArticleSearchPath: searchPath,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set the last visited category slug.
|
|
*
|
|
* @param {string|null} slug Category slug (e.g. `obst-gemuese/obst` or empty string for root).
|
|
* @returns {void}
|
|
*/
|
|
setLastCategorySlug(slug) {
|
|
const clean = slug ? String(slug).replace(/^\/+|\/+$/g, '') : '';
|
|
const path = clean ? `/kategorien/${clean}` : '/kategorien';
|
|
navigationAutomat.actions.setLastCategoryPath(path);
|
|
},
|
|
|
|
/**
|
|
* Set the full last category path.
|
|
*
|
|
* @param {string|null} path Full category path (e.g. `/kategorien/obst-gemuese/obst`).
|
|
* @returns {void}
|
|
*/
|
|
setLastCategoryPath(path) {
|
|
const cleanPath = path ? String(path).replace(/^\/+/, '/') : '/kategorien';
|
|
const slug = cleanPath.replace(/^\/kategorien\/?/, '').replace(/\/+$/, '') || null;
|
|
const categories = categoryAutomat.state?.categories;
|
|
const rootCategoryId = categoryAutomat.state?.rootCategoryId;
|
|
const resolvedId = slug && categories && rootCategoryId != null
|
|
? resolveCategoryIdFromSlugPath(categories, rootCategoryId, slug)
|
|
: null;
|
|
const isRoot = !resolvedId || (rootCategoryId != null && String(resolvedId) === String(rootCategoryId));
|
|
const catId = isRoot ? null : String(resolvedId);
|
|
|
|
const currentTerm = navigationAutomat.state.lastSearchTerm ?? '';
|
|
const filter = catId ?? '';
|
|
const searchPath = buildSearchUrl(currentTerm, filter, 0);
|
|
|
|
navigationAutomat.setState({
|
|
lastCategoryId: catId,
|
|
lastCategorySlug: isRoot ? null : slug,
|
|
lastCategoryPath: cleanPath,
|
|
lastArticleSearchPath: searchPath,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Clear all navigation history.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
clearNavigation() {
|
|
navigationAutomat.setState(initialState);
|
|
},
|
|
},
|
|
{
|
|
name: 'navigation',
|
|
persist: false,
|
|
}
|
|
);
|
|
|
|
export default navigationAutomat;
|
|
export const {
|
|
setLastDeliveryPath,
|
|
setLastArticleSku,
|
|
setLastSearchTerm,
|
|
setLastArticleSearchPath,
|
|
setLastCategoryId,
|
|
setLastCategorySlug,
|
|
setLastCategoryPath,
|
|
clearNavigation,
|
|
} = navigationAutomat.actions;
|