Files
dashfront/src/components/article/ArticlePriceTiersDialog.jsx
2026-10-05 14:54:43 +02:00

183 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Component } from 'react';
import AddIcon from '@mui/icons-material/Add';
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import AppDialog from '../AppDialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import TextInput from '../TextInput';
import Typography from '@mui/material/Typography';
import { formatPriceAmount, parsePriceInput } from '../../utils/price';
function emptyTier() {
return { minmultiplier: '', gross: '' };
}
function parseMultiplier(value) {
if (value === '' || value == null) return null;
const n = Number(String(value).trim());
return Number.isInteger(n) && n >= 2 ? n : null;
}
function tiersValid(tiers) {
const seen = new Set();
for (const tier of tiers) {
const mult = parseMultiplier(tier.minmultiplier);
const gross = parsePriceInput(tier.gross);
if (mult == null || gross == null) return false;
if (seen.has(mult)) return false;
seen.add(mult);
}
return true;
}
class ArticlePriceTiersDialog extends Component {
constructor(props) {
super(props);
this.state = { tiers: [] };
}
componentDidUpdate(prevProps) {
if (this.props.open && !prevProps.open) {
const tiers = this.props.tiers?.length
? this.props.tiers.map((row) => ({
minmultiplier: String(row.minmultiplier),
gross: row.gross,
}))
: [];
this.setState({ tiers });
}
}
handleChange = (index, field, value) => {
this.setState((state) => {
const tiers = state.tiers.map((row, i) =>
i === index ? { ...row, [field]: value } : row,
);
return { tiers };
});
};
handleAdd = () => {
this.setState((state) => ({ tiers: [...state.tiers, emptyTier()] }));
};
handleRemove = (index) => {
this.setState((state) => ({
tiers: state.tiers.filter((_, i) => i !== index),
}));
};
handleApply = () => {
const { tiers } = this.state;
if (!tiersValid(tiers)) return;
this.props.onApply(
tiers.map((row) => ({
minmultiplier: parseMultiplier(row.minmultiplier),
gross: formatPriceAmount(parsePriceInput(row.gross)),
})),
);
this.props.onClose();
};
render() {
const { open, onClose } = this.props;
const { tiers } = this.state;
const canApply = tiersValid(tiers);
return (
<AppDialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>Staffelpreise</DialogTitle>
<DialogContent dividers>
{tiers.length === 0 ? (
<Typography color="text.secondary" sx={{ py: 1 }}>
Keine Staffeln. Basispreis bleibt der Einzelpreis (×1).
</Typography>
) : null}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{tiers.map((tier, index) => {
const mult = parseMultiplier(tier.minmultiplier);
const gross = parsePriceInput(tier.gross);
return (
<Box
key={index}
sx={{
display: 'flex',
gap: 1,
alignItems: 'flex-start',
}}
>
<TextInput
label="Ab Menge"
value={tier.minmultiplier}
onChange={(event) =>
this.handleChange(index, 'minmultiplier', event.target.value)
}
error={tier.minmultiplier !== '' && mult == null}
slotProps={{
htmlInput: { inputMode: 'numeric' },
}}
sx={{ width: 120 }}
/>
<TextInput
label="Brutto"
value={tier.gross}
onChange={(event) =>
this.handleChange(index, 'gross', event.target.value)
}
fullWidth
error={tier.gross !== '' && gross == null}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">€</InputAdornment>
),
},
htmlInput: { inputMode: 'decimal' },
}}
/>
<IconButton
type="button"
aria-label="Staffel entfernen"
onClick={() => this.handleRemove(index)}
sx={{ mt: 0.5 }}
>
<DeleteOutlineOutlinedIcon />
</IconButton>
</Box>
);
})}
</Box>
<Button
type="button"
startIcon={<AddIcon />}
onClick={this.handleAdd}
sx={{ mt: 2 }}
>
Staffel hinzufügen
</Button>
</DialogContent>
<DialogActions sx={{ px: 3, py: 2 }}>
<Button type="button" onClick={onClose}>
Abbrechen
</Button>
<Button
type="button"
variant="contained"
onClick={this.handleApply}
disabled={!canApply}
>
Übernehmen
</Button>
</DialogActions>
</AppDialog>
);
}
}
export default ArticlePriceTiersDialog;