feat(admin): add product dependencies and clean up labels
All checks were successful
Staging Build / build (push) Successful in 2m43s

- Add addProductDependency and removeProductDependency actions

- Create DependencyPopover and integrate in live editor UI

- Render green requirements and red exclusions badges

- Remove technical parentheses from category checkbox labels
This commit is contained in:
DanielS
2026-07-09 16:12:57 +02:00
parent 215da5f25b
commit 567d1f226b
4 changed files with 280 additions and 5 deletions

View File

@@ -2,9 +2,10 @@
import { useState, useTransition } from 'react'
import { Category, Product, ProductModule } from '@/lib/types'
import { updateProduct, createProduct, updateCategory, createCategory, deleteProduct, deleteCategory } from '@/lib/actions/products'
import { updateProduct, createProduct, updateCategory, createCategory, deleteProduct, deleteCategory, addProductDependency, removeProductDependency } from '@/lib/actions/products'
import { InlineInput } from './components/inline-input'
import { ModulePopover } from './components/module-popover'
import { DependencyPopover } from './components/dependency-popover'
import { Trash2 } from 'lucide-react'
export function WysiwygAdminClient({
@@ -60,6 +61,14 @@ export function WysiwygAdminClient({
})
}
const getDependencyName = (depId: string) => {
const matchedProduct = products.find(p => p.id === depId)
if (matchedProduct) return matchedProduct.name
const matchedModule = allModules.find(m => m.id === depId)
if (matchedModule) return `${matchedModule.name} (Modul)`
return depId
}
// Filterung der Kategorien nach aktivem Modus
const filteredCategories = categories.filter(cat =>
mode === 'purchase' ? cat.show_in_kauf : cat.show_in_abo
@@ -181,7 +190,7 @@ export function WysiwygAdminClient({
onChange={(e) => handleCategoryToggle(cat.id, { is_required: e.target.checked })}
className="rounded border-white/10 bg-white/5 text-primary focus:ring-0 focus:ring-offset-0"
/>
<span>Pflichtauswahl (is_required)</span>
<span>Pflichtauswahl</span>
</label>
<label className="flex items-center gap-2 cursor-pointer select-none">
<input
@@ -190,7 +199,7 @@ export function WysiwygAdminClient({
onChange={(e) => handleCategoryToggle(cat.id, { allow_multiselect: e.target.checked })}
className="rounded border-white/10 bg-white/5 text-primary focus:ring-0 focus:ring-offset-0"
/>
<span>Mehrfachauswahl (allow_multiselect)</span>
<span>Mehrfachauswahl</span>
</label>
</div>
</div>
@@ -274,6 +283,84 @@ export function WysiwygAdminClient({
)}
</div>
</div>
{/* Requirements */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold block">Benötigt:</span>
<DependencyPopover
currentProductId={prod.id}
assignedIds={prod.requirements || []}
allProducts={products}
allModules={allModules}
onAddDependency={async (depId) => {
const newReqs = [...(prod.requirements || []), depId]
setProducts(products.map(p => p.id === prod.id ? { ...p, requirements: newReqs } : p))
await addProductDependency(prod.id, 'requirements', depId)
}}
/>
</div>
<div className="flex flex-wrap gap-1">
{(prod.requirements || []).length > 0 ? (
(prod.requirements || []).map((reqId) => (
<span key={reqId} className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] bg-green-500/10 text-green-400 border border-green-500/20 font-medium animate-fade-in">
{getDependencyName(reqId)}
<button
onClick={async () => {
const newReqs = (prod.requirements || []).filter(id => id !== reqId)
setProducts(products.map(p => p.id === prod.id ? { ...p, requirements: newReqs } : p))
await removeProductDependency(prod.id, 'requirements', reqId)
}}
className="text-green-600 hover:text-green-400 font-bold transition-colors ml-0.5"
>
×
</button>
</span>
))
) : (
<span className="text-[10px] text-slate-500 italic">Keine Anforderungen</span>
)}
</div>
</div>
{/* Exclusions */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold block">Schließt aus:</span>
<DependencyPopover
currentProductId={prod.id}
assignedIds={prod.exclusions || []}
allProducts={products}
allModules={allModules}
onAddDependency={async (depId) => {
const newExcs = [...(prod.exclusions || []), depId]
setProducts(products.map(p => p.id === prod.id ? { ...p, exclusions: newExcs } : p))
await addProductDependency(prod.id, 'exclusions', depId)
}}
/>
</div>
<div className="flex flex-wrap gap-1">
{(prod.exclusions || []).length > 0 ? (
(prod.exclusions || []).map((exId) => (
<span key={exId} className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] bg-red-500/10 text-red-400 border border-red-500/20 font-medium animate-fade-in">
{getDependencyName(exId)}
<button
onClick={async () => {
const newExcs = (prod.exclusions || []).filter(id => id !== exId)
setProducts(products.map(p => p.id === prod.id ? { ...p, exclusions: newExcs } : p))
await removeProductDependency(prod.id, 'exclusions', exId)
}}
className="text-red-600 hover:text-red-400 font-bold transition-colors ml-0.5"
>
×
</button>
</span>
))
) : (
<span className="text-[10px] text-slate-500 italic">Keine Ausschlüsse</span>
)}
</div>
</div>
</div>
{/* Modul hinzufügen Popover */}