/** * @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} */ 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;