Files
dashfront/src/nav.js
2026-10-04 09:21:45 +02:00

324 lines
9.0 KiB
JavaScript

import { PureComponent, createElement } from 'react';
import Badge from '@mui/material/Badge';
import Inventory2Icon from '@mui/icons-material/Inventory2';
import LocalShippingIcon from '@mui/icons-material/LocalShipping';
import AssignmentIcon from '@mui/icons-material/Assignment';
import PeopleIcon from '@mui/icons-material/People';
import InfoIcon from '@mui/icons-material/Info';
import ViewListIcon from '@mui/icons-material/ViewList';
import CategoryIcon from '@mui/icons-material/Category';
import GridViewIcon from '@mui/icons-material/GridView';
import WarehouseIcon from '@mui/icons-material/Warehouse';
import CopyrightIcon from '@mui/icons-material/Copyright';
import ListAltIcon from '@mui/icons-material/ListAlt';
import GroupsIcon from '@mui/icons-material/Groups';
import AddBoxIcon from '@mui/icons-material/AddBox';
import StorefrontIcon from '@mui/icons-material/Storefront';
import RequestQuoteIcon from '@mui/icons-material/RequestQuote';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import LabelIcon from '@mui/icons-material/Label';
import HistoryIcon from '@mui/icons-material/History';
import PublicIcon from '@mui/icons-material/Public';
import BarChartIcon from '@mui/icons-material/BarChart';
import { isKategorienPath } from './components/category/categoryRoute';
import { deliveryEditorAutomat } from './automats/deliveryEditorAutomat';
class DeliveryNavIcon extends PureComponent {
constructor(props) {
super(props);
const state = deliveryEditorAutomat.state;
const isDraft = state.id == null && state.deliveryId == null;
this.state = {
hasDraft: isDraft && (state.lines?.length || 0) > 0,
};
}
componentDidMount() {
this.unsub = deliveryEditorAutomat.subscribe(this, (state) => {
const isDraft = state.id == null && state.deliveryId == null;
return {
hasDraft: isDraft && (state.lines?.length || 0) > 0,
};
});
}
componentWillUnmount() {
this.unsub?.();
}
render() {
const { hasDraft } = this.state;
const { sx, ...rest } = this.props;
return createElement(LocalShippingIcon, {
...rest,
sx: [
...(Array.isArray(sx) ? sx : sx ? [sx] : []),
(theme) => (hasDraft ? { color: theme.nav?.itemActiveAccent || '#2dd4bf' } : null),
],
});
}
}
export const NAV_ITEMS = [
{
path: '/artikel',
label: 'Artikel',
icon: Inventory2Icon,
help: 'Artikelübersicht, Stammdaten, Barcodes und Bestandsverwaltung',
},
{
path: '/lieferung',
label: 'Lieferung',
icon: DeliveryNavIcon,
help: 'Wareneingänge verwalten, Lieferungen buchen und Lieferscheine erfassen',
},
{
path: '/auftrag',
label: 'Auftrag',
icon: AssignmentIcon,
help: 'Kundenangebote kalkulieren und Bestellungen abwickeln',
},
{
path: '/kunde',
label: 'Kunde',
icon: PeopleIcon,
help: 'Kundenstammdaten, Adressen, Ansprechpartner und Kundengruppen',
},
{
path: '/infos',
label: 'Infos',
icon: InfoIcon,
help: 'Systemstatus, Serververbindung und Mandanteneinstellungen',
},
];
class DeliveryNeuIcon extends PureComponent {
constructor(props) {
super(props);
const state = deliveryEditorAutomat.state;
const isDraft = state.id == null && state.deliveryId == null;
this.state = {
badgeCount: isDraft ? (state.lines?.length || 0) : 0,
};
}
componentDidMount() {
this.unsub = deliveryEditorAutomat.subscribe(this, (state) => {
const isDraft = state.id == null && state.deliveryId == null;
return {
badgeCount: isDraft ? (state.lines?.length || 0) : 0,
};
});
}
componentWillUnmount() {
this.unsub?.();
}
render() {
return createElement(
Badge,
{
badgeContent: this.state.badgeCount,
invisible: !this.state.badgeCount,
color: 'primary',
max: 999,
},
createElement(AddBoxIcon, this.props)
);
}
}
export const SUB_NAV = {
'/artikel': [
{
segment: '',
label: 'Liste',
title: 'Artikel',
icon: ViewListIcon,
help: 'Gesamtliste aller Artikel durchsuchen, filtern und öffnen',
},
{
segment: 'kategorien',
label: 'Kategorieen',
title: 'Kategorien',
icon: GridViewIcon,
help: 'Hierarchischer Kategoriebaum zur Sortimentsgliederung',
},
{
segment: 'lager',
label: 'Lager',
icon: WarehouseIcon,
help: 'Lagerbestände einsehen, Bestandsabgleiche und Lagerplatzverwaltung',
},
{
segment: 'marken',
label: 'Marken',
icon: CopyrightIcon,
help: 'Katalog aller Hersteller und Marken verwalten',
},
{
segment: 'vorlagen',
label: 'Vorlagen',
icon: LabelIcon,
help: 'Druckvorlagen für Etiketten und Barcodes bearbeiten',
},
{
segment: 'merkmale',
label: 'Merkmale',
title: 'Merkmale & Attribute',
icon: CategoryIcon,
help: 'Globale Merkmale, Attribute und Werte verwalten',
},
],
'/lieferung': [
{
segment: '',
label: 'Liste',
title: 'Lieferungen',
icon: ListAltIcon,
help: 'Historie und Status aller Wareneingänge und Lieferungen',
},
{
segment: 'neu',
label: 'Neu',
title: 'Neue Lieferung',
icon: DeliveryNeuIcon,
help: 'Neuen Wareneingang mit Belegscan und Positionserfassung buchen',
},
{
segment: 'lieferanten',
label: 'Lieferanten',
icon: StorefrontIcon,
help: 'Lieferantenverzeichnis mit hinterlegten Katalogen und Konditionen',
},
],
'/auftrag': [
{
segment: '',
label: 'Angebot',
title: 'Angebot',
icon: RequestQuoteIcon,
help: 'Angebote für Kunden kalkulieren und versenden',
},
{
segment: 'bestellung',
label: 'Bestellung',
title: 'Bestellung',
icon: ShoppingCartIcon,
help: 'Bestellungen prüfen, freigeben und kommissionieren',
},
],
'/kunde': [
{
segment: '',
label: 'Liste',
title: 'Kundenliste',
icon: PeopleIcon,
help: 'Kundenkartei mit Kontaktdaten und Historie',
},
{
segment: 'neu',
label: 'neu',
title: 'Neuer Kunde',
icon: AddBoxIcon,
help: 'Neuen Kundenstamm mit Anschriften anlegen',
},
{
segment: 'gruppen',
label: 'Gruppen',
title: 'Gruppen',
icon: GroupsIcon,
help: 'Kundengruppen für Staffelpreise und Rabatte verwalten',
},
],
'/infos': [
{
segment: '',
label: 'ArticleHistory',
title: 'ArticleHistory',
icon: HistoryIcon,
help: 'Historie aller Artikeländerungen und Ereignisse',
},
{
segment: 'globalhistory',
label: 'GlobalHistory',
title: 'GlobalHistory',
icon: PublicIcon,
help: 'Globale System- und Änderungshistorie',
},
{
segment: 'statistics',
label: 'Statistics',
title: 'Statistics',
icon: BarChartIcon,
help: 'Statistische Auswertungen und Kennzahlen',
},
],
};
export function isNavItemActive(pathname, navPath) {
if (navPath === '/artikel') {
return (
pathname === navPath ||
pathname.startsWith(`${navPath}/`) ||
isKategorienPath(pathname)
);
}
return pathname === navPath || pathname.startsWith(`${navPath}/`);
}
/** Stable across article tab changes (e.g. always `/artikel`). */
export function resolveActiveNav(pathname) {
for (const item of NAV_ITEMS) {
if (isNavItemActive(pathname, item.path)) return item.path;
}
return null;
}
/**
* Active sub-nav segment for a section.
* `''` = section index, `null` = none (e.g. article / kunde detail).
*/
export function resolveActiveSub(pathname, sectionPath) {
const subNav = SUB_NAV[sectionPath];
if (!subNav) return null;
const knownSegments = new Set(subNav.map((item) => item.segment).filter(Boolean));
const rest = pathname.slice(sectionPath.length).replace(/^\//, '').split('/')[0];
const isArticleDetail =
sectionPath === '/artikel' &&
!isKategorienPath(pathname) &&
rest &&
!knownSegments.has(rest);
const isKundeDetail =
sectionPath === '/kunde' && rest && !knownSegments.has(rest);
const isLieferungDetail =
sectionPath === '/lieferung' && rest && !knownSegments.has(rest);
// Angebot neu/edit: /auftrag/neu or /auftrag/:quoteId (not a known sub)
const isAngebotDoc =
sectionPath === '/auftrag' && rest && !knownSegments.has(rest);
if (isArticleDetail || isKundeDetail || isLieferungDetail || isAngebotDoc) {
return null;
}
// Keep Bestellung active for nested paths (e.g. /auftrag/bestellung/neu).
if (sectionPath === '/auftrag' && rest && knownSegments.has(rest)) {
return rest;
}
// Treat /infos/articlehistory as segment ''
if (sectionPath === '/infos' && rest === 'articlehistory') {
return '';
}
const active = subNav.find((item) => {
if (item.segment === 'kategorien') {
return isKategorienPath(pathname);
}
const subPath = item.segment ? `${sectionPath}/${item.segment}` : sectionPath;
return pathname === subPath || pathname === `${subPath}/`;
});
return active ? active.segment : null;
}