From 90c502071a82e10615e948222c4b923c2e768e22 Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 8 Oct 2026 20:55:36 +0200 Subject: [PATCH] u --- .../article/boxes/ArticleViewPrice.jsx | 70 +++++++++++++++---- .../article/boxes/ArticleViewStock.jsx | 65 +++++++++++++---- .../brand/BrandImageSearchDialog.jsx | 21 ------ src/index.css | 46 ------------ src/main.jsx | 2 +- 5 files changed, 108 insertions(+), 96 deletions(-) diff --git a/src/components/article/boxes/ArticleViewPrice.jsx b/src/components/article/boxes/ArticleViewPrice.jsx index a0bbd0b..17947b4 100644 --- a/src/components/article/boxes/ArticleViewPrice.jsx +++ b/src/components/article/boxes/ArticleViewPrice.jsx @@ -8,7 +8,7 @@ import ArticlePriceTiersDialog from '../ArticlePriceTiersDialog'; import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog'; import unitAutomat from '../../../automats/unitAutomat'; import companyAutomat from '../../../automats/companyAutomat'; -import artikelAutomat from '../../../automats/artikelAutomat'; +import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat'; import { requireDefined, requireObject } from '../../../utils/apiShape'; import { buildPosPricesPutBody, @@ -19,6 +19,7 @@ import { } from '../../../utils/articleDetail'; import { formatPriceAmount, + getArticleGross, netFromGross, parsePriceInput, } from '../../../utils/price'; @@ -33,7 +34,6 @@ import TierPricePills from './TierPricePills'; import { PriceUnitRow, PriceUnitCorner } from './PriceUnitRow'; import { BLUE_PANEL, - BORDER, } from '../../../colors'; const SECOND_ROW_MIN_HEIGHT = 220; @@ -387,6 +387,20 @@ export default class ArticleViewPrice extends PureComponent { this.unsubCompany = companyAutomat.subscribe(this, (state) => ({ company: state.current ?? null, })); + this.unsubArtikel = artikelAutomat.subscribe(this, () => ({})); + if (this.props.article?.isParent) { + this.ensureChildren(this.props.article.children); + } + } + + componentDidUpdate(prevProps) { + if ( + this.props.article?.isParent && + (prevProps.article?.sku !== this.props.article?.sku || + prevProps.article?.children !== this.props.article?.children) + ) { + this.ensureChildren(this.props.article.children); + } } componentWillUnmount() { @@ -395,6 +409,17 @@ export default class ArticleViewPrice extends PureComponent { this.unsubUnits = null; this.unsubCompany?.(); this.unsubCompany = null; + this.unsubArtikel?.(); + this.unsubArtikel = null; + } + + ensureChildren(children) { + if (!Array.isArray(children)) return; + for (const child of children) { + if (child?.sku && !getArticle(child.sku)) { + void artikelAutomat.actions.loadArticle(child.sku).catch(() => {}); + } + } } handleSave = async (draft) => { @@ -481,11 +506,33 @@ export default class ArticleViewPrice extends PureComponent { const { article: articleData } = this.props; if (articleData?.isParent) { + const { company } = this.state; + const defaultTaxRate = company?.taxrate ?? 19; + const children = Array.isArray(articleData.children) ? articleData.children : []; + const prices = []; + for (const child of children) { + const fullChild = getArticle(child.sku) || child; + const gross = getArticleGross(fullChild, defaultTaxRate); + if (gross != null && Number.isFinite(gross)) { + prices.push(gross); + } + } + + let priceText = 'Kein Preis'; + if (prices.length > 0) { + const min = Math.min(...prices); + const max = Math.max(...prices); + priceText = + min === max + ? `€ ${formatPriceAmount(min)}` + : `€ ${formatPriceAmount(min)} bis € ${formatPriceAmount(max)}`; + } + return ( @@ -493,32 +540,25 @@ export default class ArticleViewPrice extends PureComponent { variant="caption" color="text.secondary" sx={{ - textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, display: 'block', }} > - Preis + Preis auf Variantenebene pflegen - Kein Preis - - - Preise werden auf Variantenebene gepflegt + {priceText} diff --git a/src/components/article/boxes/ArticleViewStock.jsx b/src/components/article/boxes/ArticleViewStock.jsx index 0deb8bc..d11b03d 100644 --- a/src/components/article/boxes/ArticleViewStock.jsx +++ b/src/components/article/boxes/ArticleViewStock.jsx @@ -7,8 +7,8 @@ import Tooltip from '@mui/material/Tooltip'; import { PanelTile } from 'dashgui/panels'; import { formatStock } from '../searchFormat'; import ArticleStockAdjustDialog from '../ArticleStockAdjustDialog'; +import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat'; import { - BORDER, GREEN_PANEL, PETROL_PANEL, SORT_GHOST_BG, @@ -28,6 +28,36 @@ export default class ArticleViewStock extends PureComponent { this.handleCloseAdjust = this.handleCloseAdjust.bind(this); } + componentDidMount() { + this.unsubArtikel = artikelAutomat.subscribe(this, () => ({})); + if (this.props.article?.isParent) { + this.ensureChildren(this.props.article.children); + } + } + + componentDidUpdate(prevProps) { + if ( + this.props.article?.isParent && + (prevProps.article?.sku !== this.props.article?.sku || + prevProps.article?.children !== this.props.article?.children) + ) { + this.ensureChildren(this.props.article.children); + } + } + + componentWillUnmount() { + this.unsubArtikel?.(); + } + + ensureChildren(children) { + if (!Array.isArray(children)) return; + for (const child of children) { + if (child?.sku && !getArticle(child.sku)) { + void artikelAutomat.actions.loadArticle(child.sku).catch(() => {}); + } + } + } + handleOpenAdjust() { this.setState({ adjustOpen: true }); } @@ -54,6 +84,22 @@ export default class ArticleViewStock extends PureComponent { const warehouses = rawWarehouses.filter((wh) => (Number(wh.stock) || 0) > 0); const hasWarehouses = warehouses.length > 0; + let totalVariantsStock = 0; + if (isParent) { + const children = Array.isArray(articleData?.children) ? articleData.children : []; + for (const child of children) { + const fullChild = getArticle(child.sku) || child; + if (Array.isArray(fullChild.warehouses) && fullChild.warehouses.length > 0) { + totalVariantsStock += fullChild.warehouses.reduce( + (sum, wh) => sum + (Number(wh.stock) || 0), + 0, + ); + } else if (fullChild.stock != null) { + totalVariantsStock += Number(fullChild.stock) || 0; + } + } + } + const totalStock = rawWarehouses.length > 0 ? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0) : articleData?.stock != null @@ -74,7 +120,7 @@ export default class ArticleViewStock extends PureComponent { - Bestand + {isParent ? 'Bestand auf Variantenebene pflegen' : 'Bestand'} {isParent ? ( @@ -116,17 +162,10 @@ export default class ArticleViewStock extends PureComponent { fontSize: '1.25rem', fontWeight: 700, lineHeight: 1.2, - color: 'text.secondary', + color: 'text.primary', }} > - Kein Bestand - - - Bestand wird auf Variantenebene geführt + Alle Varianten zusammen: {formatStock(totalVariantsStock)} ) : isStueckliste ? ( diff --git a/src/components/brand/BrandImageSearchDialog.jsx b/src/components/brand/BrandImageSearchDialog.jsx index 11a6bd4..791095d 100644 --- a/src/components/brand/BrandImageSearchDialog.jsx +++ b/src/components/brand/BrandImageSearchDialog.jsx @@ -319,27 +319,6 @@ export default class BrandImageSearchDialog extends PureComponent { - {/* User helper tip */} - {images.length > 0 && !error && ( - - - - Klicken Sie auf ein Logo: Es wird heruntergeladen, automatisch freigestellt (Weiß- & Transparenz-Ränder entfernt), in AVIF konvertiert und als Markenbild hinterlegt. - - - )} - {/* Progress / Status banner */} {status === 'loading' && } diff --git a/src/index.css b/src/index.css index ec26dfa..f19b475 100644 --- a/src/index.css +++ b/src/index.css @@ -16,52 +16,6 @@ body { touch-action: manipulation; } -:root { - --df-zoom: 1; - --df-vh: 100vh; -} - -/* ──────────────────────────────────────────────────────────────────────────── - * RESPONSIVE VIEWPORT ZOOM - * - * Automatically scales the UI down on narrower viewports: - * - < 1700px: 80% zoom - * - < 1400px: 65% zoom - * - < 1000px: 50% zoom - * - * `--df-vh` scales inversely with zoom so that elements with full-screen - * height (like AppSidebar and full-height page wrappers) always reach the - * true bottom of the viewport instead of shrinking. - * ──────────────────────────────────────────────────────────────────────────── */ -@media (max-width: 1699.98px) { - :root { - --df-zoom: 0.8; - --df-vh: calc(100vh / 0.8); - } - body { - zoom: 0.8; - } -} - -@media (max-width: 1399.98px) { - :root { - --df-zoom: 0.65; - --df-vh: calc(100vh / 0.65); - } - body { - zoom: 0.65; - } -} - -@media (max-width: 999.98px) { - :root { - --df-zoom: 0.5; - --df-vh: calc(100vh / 0.5); - } - body { - zoom: 0.5; - } -} /* ──────────────────────────────────────────────────────────────────────────── * SYSTEM-WIDE SPELLCHECK OFF diff --git a/src/main.jsx b/src/main.jsx index 3649e2f..a8d4a94 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -53,7 +53,7 @@ createRoot(document.getElementById('root')).render( {/* Flex row so the dev inspector docks beside the app instead of overlaying it; the app content shrinks while the panel is open. */} - +