diff --git a/src/components/AppShell.jsx b/src/components/AppShell.jsx
index 82e1fe4..4236833 100644
--- a/src/components/AppShell.jsx
+++ b/src/components/AppShell.jsx
@@ -87,8 +87,8 @@ class AppSidebar extends PureComponent {
disablePadding
sx={{
flexGrow: 1,
- display: 'flex',
- flexDirection: { xs: 'row', lg: 'column' },
+ display: { xs: 'flex', lg: 'block' },
+ flexDirection: 'row',
alignItems: 'center',
height: { xs: '100%', lg: 'auto' },
overflowX: { xs: 'auto', lg: 'visible' },
@@ -104,6 +104,8 @@ class AppSidebar extends PureComponent {
e.preventDefault()}
onClick={() => {
@@ -131,7 +133,7 @@ class AppSidebar extends PureComponent {
borderRadius: { xs: 1, lg: 0 },
width: { xs: 'auto', lg: '100%' },
height: { xs: '100%', lg: 'auto' },
- px: { xs: 1.25, sm: 1.5 },
+ px: { xs: active ? 1.25 : 1, sm: active ? 1.5 : 1.25, md: 1.5 },
py: { xs: 0.75, sm: 1, lg: 1.25 },
borderLeft: { xs: 'none', lg: '3px solid transparent' },
borderBottom: { xs: '3px solid transparent', lg: 'none' },
@@ -141,7 +143,8 @@ class AppSidebar extends PureComponent {
'& .MuiListItemIcon-root': {
color: 'inherit',
minWidth: { xs: 'auto', lg: 36 },
- mr: { xs: 0.75, lg: 0 },
+ mr: active ? { xs: 0.75, lg: 0 } : { xs: 0, md: 0.75, lg: 0 },
+ justifyContent: 'center',
},
'&:hover': {
bgcolor: theme.nav.itemHover,
@@ -172,7 +175,10 @@ class AppSidebar extends PureComponent {
fontWeight: active ? 600 : 500,
noWrap: true,
}}
- sx={{ m: 0 }}
+ sx={{
+ m: { xs: 0, lg: 'initial' },
+ display: active ? 'block' : { xs: 'none', md: 'block' },
+ }}
/>
);
@@ -239,25 +245,34 @@ class AppSubNav extends PureComponent {
({
+ width: { xs: '100%', sm: 72, md: SUB_NAV_WIDTH },
flexShrink: 0,
display: 'flex',
- flexDirection: 'column',
+ flexDirection: { xs: 'row', sm: 'column' },
alignItems: 'center',
- gap: 1.5,
+ gap: { xs: 1, sm: 1.5 },
position: 'sticky',
top: { xs: 52, sm: 56, lg: 0 },
+ zIndex: { xs: 1090, sm: 'auto' },
alignSelf: 'flex-start',
height: {
- xs: 'calc(var(--df-vh, 100vh) - 52px)',
+ xs: 'auto',
sm: 'calc(var(--df-vh, 100vh) - 56px)',
lg: 'var(--df-vh, 100vh)',
},
- py: { xs: 2, lg: 4 },
- ml: { xs: 1, sm: 2, lg: 3 },
- mr: { xs: 1, sm: 1.5, lg: 2 },
- }}
+ py: { xs: 0.75, sm: 2, lg: 4 },
+ px: { xs: 1.5, sm: 0 },
+ ml: { xs: 0, sm: 2, lg: 3 },
+ mr: { xs: 0, sm: 1.5, lg: 2 },
+ overflowX: { xs: 'auto', sm: 'visible' },
+ scrollbarWidth: 'none',
+ '&::-webkit-scrollbar': { display: 'none' },
+ bgcolor: { xs: theme.nav?.mainBackground || 'background.default', sm: 'transparent' },
+ borderBottom: { xs: '1px solid', sm: 'none' },
+ borderColor: { xs: 'divider', sm: 'transparent' },
+ boxSizing: 'border-box',
+ })}
>
{subNav.map(({ segment, label, title, help, icon: Icon }) => {
const active = segment === activeSub;
@@ -266,6 +281,7 @@ class AppSubNav extends PureComponent {
({
- width: { xs: 60, sm: 72, md: SUB_NAV_WIDTH },
- py: 1,
- px: 0.5,
+ width: { xs: 'auto', sm: 72, md: SUB_NAV_WIDTH },
+ py: { xs: 0.5, sm: 1 },
+ px: { xs: active ? 1.25 : 0.75, sm: 0.5 },
borderRadius: 2,
display: 'flex',
- flexDirection: 'column',
+ flexDirection: { xs: 'row', sm: 'column' },
alignItems: 'center',
justifyContent: 'center',
- gap: 0.5,
+ gap: { xs: active ? 0.75 : 0, sm: 0.5 },
+ flexShrink: 0,
+ whiteSpace: 'nowrap',
color: active
? theme.nav?.itemActiveAccent || NAV_ITEM_ACTIVE_ACCENT
: theme.palette.text.secondary,
@@ -314,18 +332,19 @@ class AppSubNav extends PureComponent {
})}
>
-
+
{label ? (
{label}
@@ -358,7 +377,7 @@ export default function AppShell() {
({
diff --git a/src/components/manifest/ManifestFormLine.jsx b/src/components/manifest/ManifestFormLine.jsx
index 9277da7..57fee87 100644
--- a/src/components/manifest/ManifestFormLine.jsx
+++ b/src/components/manifest/ManifestFormLine.jsx
@@ -19,11 +19,13 @@ import SearchIcon from '@mui/icons-material/Search';
import LinkOffOutlinedIcon from '@mui/icons-material/LinkOffOutlined';
import { getFirstVal, parseNum, LINE_COLUMNS } from './manifestConfig';
import ArticlePhoto from '../article/ArticlePhoto';
-import { formatPrice } from '../../utils/price';
+import { formatPrice, grossFromNet } from '../../utils/price';
import { primaryPrice, priceBrutto } from '../../utils/articleDetail';
import artikelAutomat, { createArticleSelector, getArticle } from '../../automats/artikelAutomat';
import { artikelDetailPath } from '../article/articleRoute';
import { BrandLabel } from '../brand';
+import { articleSupplierAutomat } from '../../automats/articleDetailAutomat';
+import { selectCompanyTaxrate } from '../../automats/companyAutomat';
import {
BORDER,
} from '../../colors';
@@ -35,6 +37,7 @@ class ManifestFormLine extends PureComponent {
this.state = {
cachedArticle: sku ? getArticle(sku) : null,
loadingArticle: false,
+ fallbackSuppliers: [],
};
this.handleRemove = this.handleRemove.bind(this);
this.handleBlur = this.handleBlur.bind(this);
@@ -64,14 +67,18 @@ class ManifestFormLine extends PureComponent {
componentWillUnmount() {
this.unsubArticle?.();
+ this.unsubSupplier?.();
}
subscribeArticle(sku) {
this.unsubArticle?.();
this.unsubArticle = null;
+ this.unsubSupplier?.();
+ this.unsubSupplier = null;
+
if (!sku) {
- if (this.state.cachedArticle || this.state.loadingArticle) {
- this.setState({ cachedArticle: null, loadingArticle: false });
+ if (this.state.cachedArticle || this.state.loadingArticle || this.state.fallbackSuppliers?.length) {
+ this.setState({ cachedArticle: null, loadingArticle: false, fallbackSuppliers: [] });
}
return;
}
@@ -99,6 +106,15 @@ class ManifestFormLine extends PureComponent {
...(art ? { loadingArticle: false } : {}),
};
});
+
+ // Fallback: subscribe to articleSupplierAutomat if cachedArticle doesn't have suppliers
+ if (!current?.suppliers?.length) {
+ const supKey = articleSupplierAutomat.key(sku);
+ this.unsubSupplier = supKey.subscribe(this, (item) => ({
+ fallbackSuppliers: Array.isArray(item?.data) ? item.data : [],
+ }));
+ void articleSupplierAutomat.load(sku).catch(() => {});
+ }
}
handleRemove() {
@@ -235,8 +251,9 @@ class ManifestFormLine extends PureComponent {
} else if (id === 'gross') {
const netNum = parseNum(line.netprice ?? line.net);
const taxNum = parseNum(line.taxrate ?? line.tax) ?? 0;
+ const rebateNum = parseNum(line.rebate) ?? 0;
if (netNum != null) {
- val = String(Math.round((netNum * (1 + taxNum / 100) + Number.EPSILON) * 100) / 100);
+ val = String(grossFromNet(netNum, taxNum, rebateNum));
}
}
}
@@ -375,6 +392,41 @@ class ManifestFormLine extends PureComponent {
: null)
: null;
+ const suppliers =
+ Array.isArray(cachedArticle.suppliers) && cachedArticle.suppliers.length > 0
+ ? cachedArticle.suppliers
+ : (this.state.fallbackSuppliers ?? []);
+
+ const topSupplier =
+ suppliers.find((s) => s.sort === 0) ??
+ (suppliers.length > 0
+ ? [...suppliers].sort((a, b) => (Number(a.sort) || 0) - (Number(b.sort) || 0))[0]
+ : null);
+
+ const ekNetto =
+ topSupplier?.price?.net != null
+ ? Number(topSupplier.price.net)
+ : topSupplier?.netPrice != null
+ ? Number(topSupplier.netPrice)
+ : topSupplier?.net != null
+ ? Number(topSupplier.net)
+ : null;
+
+ const ekTaxRate =
+ topSupplier?.price?.taxRate != null
+ ? Number(topSupplier.price.taxRate)
+ : topSupplier?.taxRate != null
+ ? Number(topSupplier.taxRate)
+ : parseNum(this.props.line?.taxrate ?? this.props.line?.tax) ?? selectCompanyTaxrate() ?? 19;
+
+ const ekBrutto =
+ ekNetto != null
+ ? Math.round((ekNetto * (1 + Number(ekTaxRate) / 100) + Number.EPSILON) * 100) / 100
+ : null;
+
+ const hasPriceInfo =
+ vkNetto != null || vkBrutto != null || ekNetto != null || ekBrutto != null || kind === 'quote';
+
return (
)}
- {/* VK Netto & VK Brutto: omit end on 2nd line for order */}
- {!isOrder && (vkNetto != null || vkBrutto != null) && (
+ {/* EK Netto, EK Brutto, VK Netto, VK Brutto */}
+ {!isOrder && hasPriceInfo && (
+ {/* EK Netto */}
+
+
+ EK Netto: {ekNetto != null ? formatPrice(ekNetto) : '—'}
+
+
+
+ {/* EK Brutto */}
+
+
+ EK Brutto: {ekBrutto != null ? formatPrice(ekBrutto) : '—'}
+
+
+
+ {/* VK Netto */}
{vkNetto != null && (
)}
+
+ {/* VK Brutto */}
{vkBrutto != null && (