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:
@@ -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>`;
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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" },
|
||||||
|
|||||||
Reference in New Issue
Block a user