feat: Add product dependencies (requirements and exclusions) to database, admin UI and wizard validation
All checks were successful
Staging Build / build (push) Successful in 2m39s

This commit is contained in:
DanielS
2026-06-25 14:38:30 +02:00
parent 5a292a8ddc
commit 47928fad3f
7 changed files with 197 additions and 4 deletions

View File

@@ -139,6 +139,45 @@ export function OrderWizard({
})
}, [categories, selections, products, selectedBillingInterval])
// Product validation errors (requirements and exclusions)
const productValidationErrors = useMemo(() => {
const errors: string[] = []
const selectedProductIds = Object.values(selections)
.map(s => s.productId)
.filter((id): id is string => !!id)
selectedProductIds.forEach(prodId => {
const prod = products.find(p => p.id === prodId)
if (!prod) return
// Requirements check
if (prod.requirements && prod.requirements.length > 0) {
const missing = prod.requirements.filter(reqId => !selectedProductIds.includes(reqId))
if (missing.length > 0) {
const names = missing
.map(reqId => products.find(p => p.id === reqId)?.name || reqId)
.join(', ')
errors.push(`"${prod.name}" benötigt das Produkt: ${names}`)
}
}
// Exclusions check
if (prod.exclusions && prod.exclusions.length > 0) {
const conflicting = prod.exclusions.filter(exId => selectedProductIds.includes(exId))
if (conflicting.length > 0) {
const names = conflicting
.map(exId => products.find(p => p.id === exId)?.name || exId)
.join(', ')
errors.push(`"${prod.name}" schließt aus: ${names}`)
}
}
})
return errors
}, [selections, products])
const isNextStepDisabled = !allCategoriesFilled || productValidationErrors.length > 0
// Total price calculations (split by billing interval)
const { monthlyTotal, oneTimeTotal } = useMemo(() => {
let monthly = 0
@@ -786,12 +825,18 @@ export function OrderWizard({
Bitte aus jeder Pflichtkategorie einen Artikel wählen.
</p>
)}
{productValidationErrors.map((err, errIdx) => (
<p key={errIdx} className="text-xs text-destructive flex items-center gap-1">
<AlertCircle className="w-3 h-3 text-destructive" />
{err}
</p>
))}
</CardContent>
<CardFooter className="flex flex-col gap-2">
<Button
className="w-full h-12 text-lg"
onClick={nextStep}
disabled={!allCategoriesFilled}
disabled={isNextStepDisabled}
>
Weiter <ChevronRight className="ml-2 w-5 h-5" />
</Button>