101 lines
4.1 KiB
React
101 lines
4.1 KiB
React
import { Button } from '../ui'
|
||
import { euro } from '../../lib/financeFormat'
|
||
import { INVOICE_LIMITS, lineTotalCents, invoiceTotalCents } from '../../lib/financeView'
|
||
|
||
/**
|
||
* Positionszeilen des Rechnungsdialogs (Beschreibung, Menge, Einzelpreis, Summe, Entfernen) mit „+ Position“-Knopf.
|
||
* Rein darstellend, hält keinen Zustand. Props: legende, labelPrefix (z. B. „Position“ oder „Zusatzposition“), idPrefix,
|
||
* zeilen ([{ uid, description, quantity, price }]), minZeilen, onChange(uid, feld, wert), onAdd(), onRemove(uid), addLabel,
|
||
* zeigeSumme (Gesamtsumme und Hinweis zu § 19 UStG).
|
||
*/
|
||
export default function RechnungPositionen({
|
||
legende,
|
||
labelPrefix,
|
||
idPrefix,
|
||
zeilen,
|
||
minZeilen = 0,
|
||
onChange,
|
||
onAdd,
|
||
onRemove,
|
||
addLabel,
|
||
zeigeSumme = false,
|
||
}) {
|
||
return (
|
||
<fieldset className="rd-group">
|
||
<legend>{legende}</legend>
|
||
<div className="rd-stack">
|
||
{zeilen.map((zeile, index) => {
|
||
const n = index + 1
|
||
const summe = lineTotalCents(zeile)
|
||
return (
|
||
<div className="rd-item" key={zeile.uid}>
|
||
<div className="rd-item-field rd-item-desc">
|
||
<label className="rd-item-label" htmlFor={`${idPrefix}-desc-${zeile.uid}`}>Beschreibung</label>
|
||
<input
|
||
id={`${idPrefix}-desc-${zeile.uid}`}
|
||
className="input"
|
||
value={zeile.description}
|
||
maxLength={INVOICE_LIMITS.maxDescription}
|
||
placeholder="z. B. Webdesign Startseite"
|
||
aria-label={`${labelPrefix} ${n}: Beschreibung`}
|
||
onChange={(e) => onChange(zeile.uid, 'description', e.target.value)}
|
||
/>
|
||
</div>
|
||
<div className="rd-item-field rd-item-qty">
|
||
<label className="rd-item-label" htmlFor={`${idPrefix}-qty-${zeile.uid}`}>Menge</label>
|
||
<input
|
||
id={`${idPrefix}-qty-${zeile.uid}`}
|
||
className="input fin-amount"
|
||
value={zeile.quantity}
|
||
inputMode="numeric"
|
||
aria-label={`${labelPrefix} ${n}: Menge`}
|
||
onChange={(e) => onChange(zeile.uid, 'quantity', e.target.value)}
|
||
/>
|
||
</div>
|
||
<div className="rd-item-field rd-item-price">
|
||
<label className="rd-item-label" htmlFor={`${idPrefix}-price-${zeile.uid}`}>Einzelpreis in €</label>
|
||
<input
|
||
id={`${idPrefix}-price-${zeile.uid}`}
|
||
className="input fin-amount"
|
||
value={zeile.price}
|
||
inputMode="decimal"
|
||
placeholder="0,00"
|
||
aria-label={`${labelPrefix} ${n}: Einzelpreis in Euro`}
|
||
onChange={(e) => onChange(zeile.uid, 'price', e.target.value)}
|
||
/>
|
||
</div>
|
||
<div className="rd-item-field rd-item-sumfeld">
|
||
<span className="rd-item-label">Summe</span>
|
||
<span className="fin-amount rd-item-sum">{summe === null ? '–' : euro(summe)}</span>
|
||
</div>
|
||
<div className="rd-item-remove">
|
||
<button
|
||
type="button"
|
||
className="link"
|
||
disabled={zeilen.length <= minZeilen}
|
||
aria-label={`${labelPrefix} ${n} entfernen`}
|
||
onClick={() => onRemove(zeile.uid)}
|
||
>
|
||
Entfernen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
<div className="rd-items-foot">
|
||
<Button type="button" onClick={onAdd} disabled={zeilen.length >= INVOICE_LIMITS.maxItems}>
|
||
{addLabel}
|
||
</Button>
|
||
{zeigeSumme && (
|
||
<div className="rd-total" aria-live="polite">
|
||
<span>Summe</span>
|
||
<span className="fin-amount">{euro(invoiceTotalCents(zeilen))}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
{zeigeSumme && <p className="rd-hint">Gemäß § 19 UStG ohne Umsatzsteuer.</p>}
|
||
</div>
|
||
</fieldset>
|
||
)
|
||
}
|