Add unit pricing measure to product XML generation in feeds.cjs. Updated Product, ProductDetailPage, and AddToCartButton components to support new pricing fields (fGrundPreis, cGrundEinheit) for compliance with German regulations. Enhanced SearchBar with enter icon functionality for improved user experience.

This commit is contained in:
seb
2025-07-06 20:36:23 +02:00
parent 205558d06c
commit 23e1742e40
6 changed files with 65 additions and 8 deletions

View File

@@ -11,6 +11,22 @@ Crawl-delay: 0
return robotsTxt; return robotsTxt;
}; };
// Helper function to determine unit pricing measure based on product data
const determineUnitPricingMeasure = (product) => {
// Use the actual unit data from the product structure
if (product.fEinheitMenge && product.cEinheit) {
const amount = parseFloat(product.fEinheitMenge);
const unit = product.cEinheit.trim();
if (amount > 0 && unit) {
return `${amount} ${unit}`;
}
}
// Return null if no unit data available - don't corrupt data with fallbacks
return null;
};
const generateProductsXml = (allProductsData = [], baseUrl, config) => { const generateProductsXml = (allProductsData = [], baseUrl, config) => {
const currentDate = new Date().toISOString(); const currentDate = new Date().toISOString();
@@ -325,6 +341,13 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
<g:shipping_weight>${parseFloat(product.weight).toFixed(2)} g</g:shipping_weight>`; <g:shipping_weight>${parseFloat(product.weight).toFixed(2)} g</g:shipping_weight>`;
} }
// Add unit pricing measure (required by German law for many products)
const unitPricingMeasure = determineUnitPricingMeasure(product);
if (unitPricingMeasure) {
productsXml += `
<g:unit_pricing_measure>${unitPricingMeasure}</g:unit_pricing_measure>`;
}
productsXml += ` productsXml += `
</item>`; </item>`;

View File

@@ -51,6 +51,8 @@ class AddToCartButton extends Component {
seoName: this.props.seoName, seoName: this.props.seoName,
pictureList: this.props.pictureList, pictureList: this.props.pictureList,
price: this.props.price, price: this.props.price,
fGrundPreis: this.props.fGrundPreis,
cGrundEinheit: this.props.cGrundEinheit,
quantity: 1, quantity: 1,
weight: this.props.weight, weight: this.props.weight,
vat: this.props.vat, vat: this.props.vat,

View File

@@ -68,7 +68,7 @@ class Product extends Component {
render() { render() {
const { const {
id, name, price, available, manufacturer, seoName, id, name, price, available, manufacturer, seoName,
currency, vat, massMenge, massEinheit, thc, currency, vat, cGrundEinheit, fGrundPreis, thc,
floweringWeeks,incoming, neu, weight, versandklasse, availableSupplier floweringWeeks,incoming, neu, weight, versandklasse, availableSupplier
} = this.props; } = this.props;
@@ -341,8 +341,8 @@ class Product extends Component {
</Typography> </Typography>
{massMenge != 1 && massEinheit && (<Typography variant="body2" color="text.secondary" sx={{ m: 0,p: 0 }}> {cGrundEinheit && fGrundPreis && fGrundPreis != price && (<Typography variant="body2" color="text.secondary" sx={{ m: 0,p: 0 }}>
({new Intl.NumberFormat('de-DE', {style: 'currency', currency: currency || 'EUR'}).format(price/massMenge)}/{massEinheit}) ({new Intl.NumberFormat('de-DE', {style: 'currency', currency: currency || 'EUR'}).format(fGrundPreis)}/{cGrundEinheit})
</Typography> )} </Typography> )}
</div> </div>
{/*incoming*/} {/*incoming*/}
@@ -358,7 +358,7 @@ class Product extends Component {
> >
<ZoomInIcon /> <ZoomInIcon />
</IconButton> </IconButton>
<AddToCartButton cartButton={true} availableSupplier={availableSupplier} available={available} incoming={incoming} seoName={seoName} pictureList={this.props.pictureList} id={id} price={price} vat={vat} weight={weight} name={name} versandklasse={versandklasse}/> <AddToCartButton cartButton={true} availableSupplier={availableSupplier} cGrundEinheit={cGrundEinheit} fGrundPreis={fGrundPreis} available={available} incoming={incoming} seoName={seoName} pictureList={this.props.pictureList} id={id} price={price} vat={vat} weight={weight} name={name} versandklasse={versandklasse}/>
</Box> </Box>
</Card> </Card>
</Box> </Box>

View File

@@ -452,7 +452,11 @@ class ProductDetailPage extends Component {
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
inkl. {product.vat}% MwSt. inkl. {product.vat}% MwSt.
{product.cGrundEinheit && product.fGrundPreis && (
<>; {new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(product.fGrundPreis)}/{product.cGrundEinheit}</>
)}
</Typography> </Typography>
{product.versandklasse && {product.versandklasse &&
product.versandklasse != "standard" && product.versandklasse != "standard" &&
product.versandklasse != "kostenlos" && ( product.versandklasse != "kostenlos" && (
@@ -516,12 +520,15 @@ class ProductDetailPage extends Component {
available={product.available} available={product.available}
id={product.id} id={product.id}
availableSupplier={product.availableSupplier} availableSupplier={product.availableSupplier}
cGrundEinheit={product.cGrundEinheit}
fGrundPreis={product.fGrundPreis}
price={product.price} price={product.price}
vat={product.vat} vat={product.vat}
weight={product.weight} weight={product.weight}
name={cleanProductName(product.name)} name={cleanProductName(product.name)}
versandklasse={product.versandklasse} versandklasse={product.versandklasse}
/> />
<Typography <Typography
variant="caption" variant="caption"
sx={{ sx={{

View File

@@ -462,8 +462,8 @@ class ProductList extends Component {
available={product.available} available={product.available}
manufacturer={product.manufacturer} manufacturer={product.manufacturer}
vat={product.vat} vat={product.vat}
massMenge={product.massMenge} cGrundEinheit={product.cGrundEinheit}
massEinheit={product.massEinheit} fGrundPreis={product.fGrundPreis}
incoming={product.incomingDate} incoming={product.incomingDate}
neu={product.neu} neu={product.neu}
thc={product.thc} thc={product.thc}

View File

@@ -8,7 +8,9 @@ import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import IconButton from "@mui/material/IconButton";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
import KeyboardReturnIcon from "@mui/icons-material/KeyboardReturn";
import { useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import SocketContext from "../../contexts/SocketContext.js"; import SocketContext from "../../contexts/SocketContext.js";
@@ -184,6 +186,15 @@ const SearchBar = () => {
}, 200); }, 200);
}; };
// Handle enter icon click
const handleEnterClick = () => {
delete window.currentSearchQuery;
setShowSuggestions(false);
if (searchQuery.trim()) {
navigate(`/search?q=${encodeURIComponent(searchQuery)}`);
}
};
// Clean up timers on unmount // Clean up timers on unmount
React.useEffect(() => { React.useEffect(() => {
return () => { return () => {
@@ -244,9 +255,23 @@ const SearchBar = () => {
<SearchIcon /> <SearchIcon />
</InputAdornment> </InputAdornment>
), ),
endAdornment: loadingSuggestions && ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
<CircularProgress size={16} /> {loadingSuggestions && <CircularProgress size={16} />}
<IconButton
size="small"
onClick={handleEnterClick}
sx={{
ml: loadingSuggestions ? 0.5 : 0,
p: 0.5,
color: "text.secondary",
"&:hover": {
color: "primary.main",
},
}}
>
<KeyboardReturnIcon fontSize="small" />
</IconButton>
</InputAdornment> </InputAdornment>
), ),
sx: { borderRadius: 2, bgcolor: "background.paper" }, sx: { borderRadius: 2, bgcolor: "background.paper" },