u
This commit is contained in:
@@ -6,7 +6,7 @@ alwaysApply: false
|
|||||||
|
|
||||||
# Class Components bevorzugen
|
# Class Components bevorzugen
|
||||||
|
|
||||||
Neue und geänderte React-Komponenten als **Class Components** (`extends Component`), nicht als Function Components.
|
Neue und geänderte React-Komponenten als **Class Components** (`extends PureComponent`), nicht als Function Components.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// ❌
|
// ❌
|
||||||
|
|||||||
49
src/App.jsx
49
src/App.jsx
@@ -1,26 +1,12 @@
|
|||||||
import { Component, lazy, Suspense } from 'react';
|
import { Component, Suspense } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
import { selectAuthStatus, selectNeedsCompanySetup } from './store/authSlice';
|
import { selectAuthStatus, selectNeedsCompanySetup } from './store/authSlice';
|
||||||
import LoginPage from './components/LoginPage';
|
import LoginPage from './components/LoginPage';
|
||||||
import CompanySetupDialog from './components/CompanySetupDialog';
|
import CompanySetupDialog from './components/CompanySetupDialog';
|
||||||
import AppShell from './components/AppShell';
|
import { router } from './appRouter';
|
||||||
import SectionLayout from './components/SectionLayout';
|
|
||||||
|
|
||||||
const ArtikelUebersichtPage = lazy(() => import('./pages/artikel/index'));
|
|
||||||
const ArtikelKategorienPage = lazy(() => import('./pages/artikel/kategorien'));
|
|
||||||
const RedirectArtikelKategorien = lazy(() => import('./pages/artikel/RedirectArtikelKategorien'));
|
|
||||||
const ArtikelLagerPage = lazy(() => import('./pages/artikel/lager'));
|
|
||||||
const ArtikelSkuPage = lazy(() => import('./pages/artikel/sku'));
|
|
||||||
const LieferungPage = lazy(() => import('./pages/lieferung/index'));
|
|
||||||
const AuftragAktivePage = lazy(() => import('./pages/auftrag/index'));
|
|
||||||
const AuftragArchivPage = lazy(() => import('./pages/auftrag/archiv'));
|
|
||||||
const KundeListePage = lazy(() => import('./pages/kunde/index'));
|
|
||||||
const KundeGruppenPage = lazy(() => import('./pages/kunde/gruppen'));
|
|
||||||
const KundeKontaktePage = lazy(() => import('./pages/kunde/kontakte'));
|
|
||||||
const InfosPage = lazy(() => import('./pages/infos/index'));
|
|
||||||
|
|
||||||
function PageFallback() {
|
function PageFallback() {
|
||||||
return (
|
return (
|
||||||
@@ -67,36 +53,9 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
|
||||||
<Suspense fallback={<PageFallback />}>
|
<Suspense fallback={<PageFallback />}>
|
||||||
<Routes>
|
<RouterProvider router={router} />
|
||||||
<Route path="/" element={<AppShell />}>
|
|
||||||
<Route index element={<Navigate to="/artikel" replace />} />
|
|
||||||
<Route path="lieferung" element={<LieferungPage />} />
|
|
||||||
<Route path="infos" element={<InfosPage />} />
|
|
||||||
<Route path="kategorien/*" element={<SectionLayout sectionPath="/artikel" />}>
|
|
||||||
<Route path="*" element={<ArtikelKategorienPage />} />
|
|
||||||
</Route>
|
|
||||||
<Route path="artikel" element={<SectionLayout sectionPath="/artikel" />}>
|
|
||||||
<Route index element={<ArtikelUebersichtPage />} />
|
|
||||||
<Route path="kategorien/*" element={<RedirectArtikelKategorien />} />
|
|
||||||
<Route path="lager" element={<ArtikelLagerPage />} />
|
|
||||||
<Route path=":sku/:tab?" element={<ArtikelSkuPage />} />
|
|
||||||
</Route>
|
|
||||||
<Route path="auftrag" element={<SectionLayout sectionPath="/auftrag" />}>
|
|
||||||
<Route index element={<AuftragAktivePage />} />
|
|
||||||
<Route path="archiv" element={<AuftragArchivPage />} />
|
|
||||||
</Route>
|
|
||||||
<Route path="kunde" element={<SectionLayout sectionPath="/kunde" />}>
|
|
||||||
<Route index element={<KundeListePage />} />
|
|
||||||
<Route path="gruppen" element={<KundeGruppenPage />} />
|
|
||||||
<Route path="kontakte" element={<KundeKontaktePage />} />
|
|
||||||
</Route>
|
|
||||||
<Route path="*" element={<Navigate to="/artikel" replace />} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</BrowserRouter>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/appRouter.jsx
Normal file
62
src/appRouter.jsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { lazy } from 'react';
|
||||||
|
import { Navigate, createBrowserRouter } from 'react-router-dom';
|
||||||
|
import AppShell from './components/AppShell';
|
||||||
|
import SectionLayout from './components/SectionLayout';
|
||||||
|
|
||||||
|
const ArtikelUebersichtPage = lazy(() => import('./pages/artikel/index'));
|
||||||
|
const ArtikelKategorienPage = lazy(() => import('./pages/artikel/kategorien'));
|
||||||
|
const RedirectArtikelKategorien = lazy(() => import('./pages/artikel/RedirectArtikelKategorien'));
|
||||||
|
const ArtikelLagerPage = lazy(() => import('./pages/artikel/lager'));
|
||||||
|
const ArtikelSkuPage = lazy(() => import('./pages/artikel/sku'));
|
||||||
|
const LieferungPage = lazy(() => import('./pages/lieferung/index'));
|
||||||
|
const AuftragAktivePage = lazy(() => import('./pages/auftrag/index'));
|
||||||
|
const AuftragArchivPage = lazy(() => import('./pages/auftrag/archiv'));
|
||||||
|
const KundeListePage = lazy(() => import('./pages/kunde/index'));
|
||||||
|
const KundeGruppenPage = lazy(() => import('./pages/kunde/gruppen'));
|
||||||
|
const KundeKontaktePage = lazy(() => import('./pages/kunde/kontakte'));
|
||||||
|
const InfosPage = lazy(() => import('./pages/infos/index'));
|
||||||
|
|
||||||
|
export const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <AppShell />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <Navigate to="/artikel" replace /> },
|
||||||
|
{ path: 'lieferung', element: <LieferungPage /> },
|
||||||
|
{ path: 'infos', element: <InfosPage /> },
|
||||||
|
{
|
||||||
|
path: 'kategorien/*',
|
||||||
|
element: <SectionLayout sectionPath="/artikel" />,
|
||||||
|
children: [{ path: '*', element: <ArtikelKategorienPage /> }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'artikel',
|
||||||
|
element: <SectionLayout sectionPath="/artikel" />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <ArtikelUebersichtPage /> },
|
||||||
|
{ path: 'kategorien/*', element: <RedirectArtikelKategorien /> },
|
||||||
|
{ path: 'lager', element: <ArtikelLagerPage /> },
|
||||||
|
{ path: ':sku/:tab?', element: <ArtikelSkuPage /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'auftrag',
|
||||||
|
element: <SectionLayout sectionPath="/auftrag" />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <AuftragAktivePage /> },
|
||||||
|
{ path: 'archiv', element: <AuftragArchivPage /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'kunde',
|
||||||
|
element: <SectionLayout sectionPath="/kunde" />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <KundeListePage /> },
|
||||||
|
{ path: 'gruppen', element: <KundeGruppenPage /> },
|
||||||
|
{ path: 'kontakte', element: <KundeKontaktePage /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ path: '*', element: <Navigate to="/artikel" replace /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import List from '@mui/material/List';
|
import List from '@mui/material/List';
|
||||||
import ListItemButton from '@mui/material/ListItemButton';
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import UserMenu from './UserMenu';
|
import UserMenu from './UserMenu';
|
||||||
import { NAV_ITEMS, isNavItemActive } from '../nav';
|
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
||||||
import { artikelQueryPath } from '../pages/artikel/artikelQuery';
|
import { artikelQueryPath } from '../pages/artikel/artikelQuery';
|
||||||
|
import { defaultCategoryPath } from '../pages/artikel/categoryRoute';
|
||||||
|
|
||||||
const SIDEBAR_WIDTH = 144;
|
const SIDEBAR_WIDTH = 144;
|
||||||
|
const SUB_NAV_WIDTH = 80;
|
||||||
|
|
||||||
export default function AppShell() {
|
class AppSidebar extends PureComponent {
|
||||||
const navigate = useNavigate();
|
render() {
|
||||||
const { pathname } = useLocation();
|
const { activeNav, navigate } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', minHeight: '100vh', bgcolor: 'background.default' }}>
|
|
||||||
<Box
|
<Box
|
||||||
component="nav"
|
component="nav"
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
@@ -28,7 +31,7 @@ export default function AppShell() {
|
|||||||
>
|
>
|
||||||
<List disablePadding sx={{ flexGrow: 1 }}>
|
<List disablePadding sx={{ flexGrow: 1 }}>
|
||||||
{NAV_ITEMS.map(({ path, label, icon: Icon }) => {
|
{NAV_ITEMS.map(({ path, label, icon: Icon }) => {
|
||||||
const active = isNavItemActive(pathname, path);
|
const active = path === activeNav;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
@@ -73,18 +76,114 @@ export default function AppShell() {
|
|||||||
|
|
||||||
<UserMenu />
|
<UserMenu />
|
||||||
</Box>
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppSubNav extends PureComponent {
|
||||||
|
render() {
|
||||||
|
const { activeNav, activeSub, navigate } = this.props;
|
||||||
|
const subNav = activeNav ? SUB_NAV[activeNav] : null;
|
||||||
|
|
||||||
|
if (!subNav?.length) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: SUB_NAV_WIDTH,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
component="nav"
|
||||||
|
aria-label="Unternavigation"
|
||||||
|
sx={{
|
||||||
|
width: SUB_NAV_WIDTH,
|
||||||
|
flexShrink: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subNav.map(({ segment, label, icon: Icon }) => {
|
||||||
|
const active = segment === activeSub;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
key={segment || 'index'}
|
||||||
|
type="button"
|
||||||
|
aria-label={label}
|
||||||
|
aria-current={active ? 'page' : undefined}
|
||||||
|
disableRipple
|
||||||
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
|
onClick={() => {
|
||||||
|
if (!segment && activeNav === '/artikel') {
|
||||||
|
navigate(artikelQueryPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (segment === 'kategorien') {
|
||||||
|
navigate(defaultCategoryPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigate(segment ? `${activeNav}/${segment}` : activeNav);
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: '50%',
|
||||||
|
color: active
|
||||||
|
? theme.palette.primary.main
|
||||||
|
: theme.palette.text.secondary,
|
||||||
|
bgcolor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: theme.palette.action.hover,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Icon sx={{ fontSize: 40 }} />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppShell() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const activeNav = resolveActiveNav(pathname);
|
||||||
|
const activeSub = activeNav ? resolveActiveSub(pathname, activeNav) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', minHeight: '100vh', bgcolor: 'background.default' }}>
|
||||||
|
<AppSidebar activeNav={activeNav} navigate={navigate} />
|
||||||
<Box
|
<Box
|
||||||
component="main"
|
component="main"
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
gap: 2,
|
||||||
py: 4,
|
py: 4,
|
||||||
px: 3,
|
px: 3,
|
||||||
bgcolor: theme.nav.mainBackground,
|
bgcolor: theme.nav.mainBackground,
|
||||||
|
minWidth: 0,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
<AppSubNav
|
||||||
|
activeNav={activeNav}
|
||||||
|
activeSub={activeSub}
|
||||||
|
navigate={navigate}
|
||||||
|
/>
|
||||||
|
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +1,14 @@
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
|
||||||
import { artikelQueryPath } from '../pages/artikel/artikelQuery';
|
|
||||||
import { defaultCategoryPath, isKategorienPath } from '../pages/artikel/categoryRoute';
|
|
||||||
|
|
||||||
const SUB_NAV_WIDTH = 80;
|
|
||||||
|
|
||||||
function isSubNavActive(pathname, basePath, segment) {
|
|
||||||
if (!segment) {
|
|
||||||
if (isKategorienPath(pathname)) return false;
|
|
||||||
return pathname === basePath || pathname === `${basePath}/`;
|
|
||||||
}
|
|
||||||
if (segment === 'kategorien') {
|
|
||||||
return isKategorienPath(pathname);
|
|
||||||
}
|
|
||||||
const subPath = `${basePath}/${segment}`;
|
|
||||||
return pathname === subPath || pathname === `${subPath}/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PageLayout({ title, showTitle = true, titleAction, subNav, basePath, children }) {
|
|
||||||
const hasSubNav = subNav?.length > 0;
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { pathname } = useLocation();
|
|
||||||
|
|
||||||
|
export default function PageLayout({
|
||||||
|
title,
|
||||||
|
showTitle = true,
|
||||||
|
titleAction,
|
||||||
|
children,
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', gap: 2, minHeight: 0 }}>
|
<Box sx={{ minWidth: 0 }}>
|
||||||
<Box
|
|
||||||
component={hasSubNav ? 'nav' : 'div'}
|
|
||||||
aria-label={hasSubNav ? 'Unternavigation' : undefined}
|
|
||||||
sx={{
|
|
||||||
width: SUB_NAV_WIDTH,
|
|
||||||
flexShrink: 0,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{hasSubNav &&
|
|
||||||
subNav.map(({ segment, label, icon: Icon }) => {
|
|
||||||
const active = isSubNavActive(pathname, basePath, segment);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
key={segment || 'index'}
|
|
||||||
type="button"
|
|
||||||
aria-label={label}
|
|
||||||
aria-current={active ? 'page' : undefined}
|
|
||||||
disableRipple
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
onClick={() => {
|
|
||||||
if (!segment && basePath === '/artikel') {
|
|
||||||
navigate(artikelQueryPath());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (segment === 'kategorien') {
|
|
||||||
navigate(defaultCategoryPath());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigate(segment ? `${basePath}/${segment}` : basePath);
|
|
||||||
}}
|
|
||||||
sx={(theme) => ({
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
borderRadius: '50%',
|
|
||||||
color: active ? theme.palette.primary.main : theme.palette.text.secondary,
|
|
||||||
bgcolor: 'transparent',
|
|
||||||
'&:hover': {
|
|
||||||
bgcolor: theme.palette.action.hover,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Icon sx={{ fontSize: 40 }} />
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
|
||||||
{showTitle ? (
|
{showTitle ? (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2 }}>
|
||||||
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
||||||
@@ -90,6 +19,5 @@ export default function PageLayout({ title, showTitle = true, titleAction, subNa
|
|||||||
) : null}
|
) : null}
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import ListItemText from '@mui/material/ListItemText';
|
|||||||
import GetAppIcon from '@mui/icons-material/GetApp';
|
import GetAppIcon from '@mui/icons-material/GetApp';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import PageLayout from './PageLayout';
|
import PageLayout from './PageLayout';
|
||||||
import { NAV_ITEMS, SUB_NAV } from '../nav';
|
import { NAV_ITEMS, SUB_NAV, resolveActiveSub } from '../nav';
|
||||||
import { isKategorienPath } from '../pages/artikel/categoryRoute';
|
import { isKategorienPath } from '../pages/artikel/categoryRoute';
|
||||||
import { selectRenameMode, setRenameMode } from '../store/categorySlice';
|
import { selectRenameMode, setRenameMode } from '../store/categorySlice';
|
||||||
import { ActionElevatedPill, ToggleElevatedPill } from './ElevatedPill';
|
import { ActionElevatedPill, ToggleElevatedPill } from './ElevatedPill';
|
||||||
@@ -34,13 +34,8 @@ export default function SectionLayout({ sectionPath }) {
|
|||||||
const [extrasAnchor, setExtrasAnchor] = useState(null);
|
const [extrasAnchor, setExtrasAnchor] = useState(null);
|
||||||
const title = NAV_ITEMS.find((item) => item.path === sectionPath)?.label ?? '';
|
const title = NAV_ITEMS.find((item) => item.path === sectionPath)?.label ?? '';
|
||||||
const subNav = SUB_NAV[sectionPath];
|
const subNav = SUB_NAV[sectionPath];
|
||||||
const activeSub = subNav?.find((item) => {
|
const subSegment = resolveActiveSub(pathname, sectionPath);
|
||||||
if (item.segment === 'kategorien') {
|
const activeSub = subNav?.find((item) => item.segment === subSegment);
|
||||||
return isKategorienPath(pathname);
|
|
||||||
}
|
|
||||||
const subPath = item.segment ? `${sectionPath}/${item.segment}` : sectionPath;
|
|
||||||
return pathname === subPath || pathname === `${subPath}/`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const knownSegments = new Set((subNav ?? []).map((item) => item.segment).filter(Boolean));
|
const knownSegments = new Set((subNav ?? []).map((item) => item.segment).filter(Boolean));
|
||||||
const rest = pathname.slice(sectionPath.length).replace(/^\//, '').split('/')[0];
|
const rest = pathname.slice(sectionPath.length).replace(/^\//, '').split('/')[0];
|
||||||
@@ -107,8 +102,6 @@ export default function SectionLayout({ sectionPath }) {
|
|||||||
<PageLayout
|
<PageLayout
|
||||||
title={pageTitle}
|
title={pageTitle}
|
||||||
showTitle={!isArticleDetail}
|
showTitle={!isArticleDetail}
|
||||||
subNav={subNav}
|
|
||||||
basePath={sectionPath}
|
|
||||||
titleAction={titleAction}
|
titleAction={titleAction}
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, createRef } from 'react';
|
import { PureComponent, createRef } from 'react';
|
||||||
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||||
@@ -11,7 +11,7 @@ import { articleWithUploadedPhoto, primaryPhoto } from '../../utils/articleDetai
|
|||||||
import ArticlePhoto from './ArticlePhoto';
|
import ArticlePhoto from './ArticlePhoto';
|
||||||
import ArticlePhotosDialog from './ArticlePhotosDialog';
|
import ArticlePhotosDialog from './ArticlePhotosDialog';
|
||||||
|
|
||||||
class ArticlePhotoTile extends Component {
|
class ArticlePhotoTile extends PureComponent {
|
||||||
static contextType = StorageManagerContext;
|
static contextType = StorageManagerContext;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { PureComponent } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Tab from '@mui/material/Tab';
|
import Tab from '@mui/material/Tab';
|
||||||
@@ -69,7 +69,7 @@ function visibleTabs(article) {
|
|||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArticleTileTab extends Component {
|
class ArticleTileTab extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import ArticleViewEANs from './boxes/ArticleViewEANs';
|
|||||||
import ArticleViewPrice from './boxes/ArticleViewPrice';
|
import ArticleViewPrice from './boxes/ArticleViewPrice';
|
||||||
import ArticleViewLists from './boxes/ArticleViewLists';
|
import ArticleViewLists from './boxes/ArticleViewLists';
|
||||||
|
|
||||||
|
/** Stable prop so PureComponent photo tile does not see a new sx each render. */
|
||||||
|
const PHOTO_TILE_SX = { minHeight: 0 };
|
||||||
|
|
||||||
class ArticleView extends Component {
|
class ArticleView extends Component {
|
||||||
static contextType = StorageManagerContext;
|
static contextType = StorageManagerContext;
|
||||||
|
|
||||||
@@ -27,10 +30,14 @@ class ArticleView extends Component {
|
|||||||
super(props, context);
|
super(props, context);
|
||||||
const { article, unwatch } = context.article.watch(
|
const { article, unwatch } = context.article.watch(
|
||||||
props.sku,
|
props.sku,
|
||||||
(next) => {this.setState({ article: next });},
|
(next) => {
|
||||||
|
this.setState({ article: next });
|
||||||
|
},
|
||||||
);
|
);
|
||||||
this.state = { article };
|
this.state = { article };
|
||||||
this.unwatchArticle = unwatch;
|
this.unwatchArticle = unwatch;
|
||||||
|
this.handleTabChange = this.handleTabChange.bind(this);
|
||||||
|
this.handleArticleDeleted = this.handleArticleDeleted.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@@ -60,7 +67,7 @@ class ArticleView extends Component {
|
|||||||
this.unwatchArticle = null;
|
this.unwatchArticle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleArticleDeleted = () => {
|
handleArticleDeleted() {
|
||||||
const { navigate } = this.props;
|
const { navigate } = this.props;
|
||||||
const { article } = this.state;
|
const { article } = this.state;
|
||||||
const categoryState = this.context.getCategoryState();
|
const categoryState = this.context.getCategoryState();
|
||||||
@@ -72,13 +79,13 @@ class ArticleView extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate(defaultCategoryPath());
|
navigate(defaultCategoryPath());
|
||||||
};
|
}
|
||||||
|
|
||||||
handleTabChange = (nextTab) => {
|
handleTabChange(nextTab) {
|
||||||
const { navigate } = this.props;
|
const { navigate } = this.props;
|
||||||
const { article } = this.state;
|
const { article } = this.state;
|
||||||
navigate(artikelDetailPath(article, nextTab));
|
navigate(artikelDetailPath(article, nextTab));
|
||||||
};
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { tab } = this.props;
|
const { tab } = this.props;
|
||||||
@@ -99,7 +106,7 @@ class ArticleView extends Component {
|
|||||||
<ArticlePhotoTile
|
<ArticlePhotoTile
|
||||||
wide={1}
|
wide={1}
|
||||||
color="#fde68a"
|
color="#fde68a"
|
||||||
sx={{ minHeight: 0 }}
|
sx={PHOTO_TILE_SX}
|
||||||
article={article}
|
article={article}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { Component, PureComponent } from 'react';
|
||||||
import Autocomplete from '@mui/material/Autocomplete';
|
import Autocomplete from '@mui/material/Autocomplete';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
@@ -71,7 +71,7 @@ class BrandEdit extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ArticleViewBrand extends Component {
|
export default class ArticleViewBrand extends PureComponent {
|
||||||
static contextType = StorageManagerContext;
|
static contextType = StorageManagerContext;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { Component, PureComponent } from 'react';
|
||||||
import Autocomplete from '@mui/material/Autocomplete';
|
import Autocomplete from '@mui/material/Autocomplete';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
@@ -83,7 +83,7 @@ class EansEdit extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ArticleViewEANs extends Component {
|
export default class ArticleViewEANs extends PureComponent {
|
||||||
static contextType = StorageManagerContext;
|
static contextType = StorageManagerContext;
|
||||||
|
|
||||||
handleSave = async (draft) => {
|
handleSave = async (draft) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { Component, PureComponent } from 'react';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
@@ -35,7 +35,7 @@ class RelationLine extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ArticleViewLists extends Component {
|
export default class ArticleViewLists extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { article: articleData } = this.props;
|
const { article: articleData } = this.props;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { Component, PureComponent } from 'react';
|
||||||
import { EditablePanelTile } from '../../panels';
|
import { EditablePanelTile } from '../../panels';
|
||||||
import SingleLineTextEdit from '../../SingleLineTextEdit';
|
import SingleLineTextEdit from '../../SingleLineTextEdit';
|
||||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||||
@@ -224,7 +224,7 @@ class NameEdit extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ArticleViewName extends Component {
|
export default class ArticleViewName extends PureComponent {
|
||||||
static contextType = StorageManagerContext;
|
static contextType = StorageManagerContext;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { Component, PureComponent } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import InputAdornment from '@mui/material/InputAdornment';
|
import InputAdornment from '@mui/material/InputAdornment';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
@@ -364,7 +364,7 @@ class PriceEdit extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ArticleViewPrice extends Component {
|
export default class ArticleViewPrice extends PureComponent {
|
||||||
static contextType = StorageManagerContext;
|
static contextType = StorageManagerContext;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { Component, PureComponent } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { PanelTile } from '../../panels';
|
import { PanelTile } from '../../panels';
|
||||||
@@ -25,7 +25,7 @@ class PanelValue extends Component {
|
|||||||
|
|
||||||
const SECOND_ROW_MIN_HEIGHT = 220;
|
const SECOND_ROW_MIN_HEIGHT = 220;
|
||||||
|
|
||||||
export default class ArticleViewStock extends Component {
|
export default class ArticleViewStock extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { article: articleData } = this.props;
|
const { article: articleData } = this.props;
|
||||||
return (
|
return (
|
||||||
|
|||||||
55
src/nav.js
55
src/nav.js
@@ -20,15 +20,6 @@ export const NAV_ITEMS = [
|
|||||||
{ path: '/infos', label: 'Infos', icon: InfoIcon },
|
{ path: '/infos', label: 'Infos', icon: InfoIcon },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function isNavItemActive(pathname, navPath) {
|
|
||||||
if (navPath === '/artikel') {
|
|
||||||
return pathname === navPath
|
|
||||||
|| pathname.startsWith(`${navPath}/`)
|
|
||||||
|| isKategorienPath(pathname);
|
|
||||||
}
|
|
||||||
return pathname === navPath || pathname.startsWith(`${navPath}/`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SUB_NAV = {
|
export const SUB_NAV = {
|
||||||
'/artikel': [
|
'/artikel': [
|
||||||
{ segment: '', icon: ViewListIcon },
|
{ segment: '', icon: ViewListIcon },
|
||||||
@@ -45,3 +36,49 @@ export const SUB_NAV = {
|
|||||||
{ segment: 'kontakte', label: 'Kontakte', icon: ContactPageIcon },
|
{ segment: 'kontakte', label: 'Kontakte', icon: ContactPageIcon },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 detail tabs).
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
if (isArticleDetail) return null;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user