feat(category): add resets_others option
All checks were successful
Staging Build / build (push) Successful in 2m46s
All checks were successful
Staging Build / build (push) Successful in 2m46s
This commit is contained in:
@@ -91,7 +91,8 @@ export function WysiwygAdminClient({
|
|||||||
free_items_limit: 0,
|
free_items_limit: 0,
|
||||||
preselect: false,
|
preselect: false,
|
||||||
show_in_kauf: mode === 'purchase',
|
show_in_kauf: mode === 'purchase',
|
||||||
show_in_abo: mode === 'subscription'
|
show_in_abo: mode === 'subscription',
|
||||||
|
resets_others: false
|
||||||
})
|
})
|
||||||
setCategories([...categories, newCat])
|
setCategories([...categories, newCat])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -182,7 +183,7 @@ export function WysiwygAdminClient({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Kategorie Attribute (Toggles) */}
|
{/* Kategorie Attribute (Toggles) */}
|
||||||
<div className="flex items-center gap-6 mt-3 text-xs text-slate-400">
|
<div className="flex flex-wrap items-center gap-6 mt-3 text-xs text-slate-400">
|
||||||
<label className="flex items-center gap-2 cursor-pointer select-none">
|
<label className="flex items-center gap-2 cursor-pointer select-none">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -196,11 +197,49 @@ export function WysiwygAdminClient({
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={cat.allow_multiselect}
|
checked={cat.allow_multiselect}
|
||||||
onChange={(e) => handleCategoryToggle(cat.id, { allow_multiselect: e.target.checked })}
|
onChange={(e) => {
|
||||||
|
const checked = e.target.checked
|
||||||
|
handleCategoryToggle(cat.id, {
|
||||||
|
allow_multiselect: checked,
|
||||||
|
...(checked ? { preselect: false } : {})
|
||||||
|
})
|
||||||
|
}}
|
||||||
className="rounded border-white/10 bg-white/5 text-primary focus:ring-0 focus:ring-offset-0"
|
className="rounded border-white/10 bg-white/5 text-primary focus:ring-0 focus:ring-offset-0"
|
||||||
/>
|
/>
|
||||||
<span>Mehrfachauswahl</span>
|
<span>Mehrfachauswahl</span>
|
||||||
</label>
|
</label>
|
||||||
|
{!cat.allow_multiselect && (
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer select-none">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={cat.preselect}
|
||||||
|
onChange={(e) => handleCategoryToggle(cat.id, { preselect: e.target.checked })}
|
||||||
|
className="rounded border-white/10 bg-white/5 text-primary focus:ring-0 focus:ring-offset-0"
|
||||||
|
/>
|
||||||
|
<span>Vorauswahl</span>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer select-none">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={cat.resets_others}
|
||||||
|
onChange={(e) => handleCategoryToggle(cat.id, { resets_others: e.target.checked })}
|
||||||
|
className="rounded border-white/10 bg-white/5 text-primary focus:ring-0 focus:ring-offset-0"
|
||||||
|
/>
|
||||||
|
<span>Rest zurücksetzen</span>
|
||||||
|
</label>
|
||||||
|
{cat.allow_multiselect && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>Freie Artikel Limit:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
value={cat.free_items_limit}
|
||||||
|
onChange={(e) => handleCategoryToggle(cat.id, { free_items_limit: parseInt(e.target.value) || 0 })}
|
||||||
|
className="w-12 rounded border-white/10 bg-white/5 text-white focus:ring-0 focus:ring-offset-0 px-1 py-0.5 text-center"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const categorySchema = z.object({
|
|||||||
preselect: z.boolean().default(true),
|
preselect: z.boolean().default(true),
|
||||||
show_in_kauf: z.boolean().default(true),
|
show_in_kauf: z.boolean().default(true),
|
||||||
show_in_abo: z.boolean().default(true),
|
show_in_abo: z.boolean().default(true),
|
||||||
|
resets_others: z.boolean().default(false),
|
||||||
})
|
})
|
||||||
|
|
||||||
type CategoryFormValues = z.infer<typeof categorySchema>
|
type CategoryFormValues = z.infer<typeof categorySchema>
|
||||||
@@ -75,6 +76,7 @@ export function CategoryDialog({
|
|||||||
preselect: category?.preselect ?? true,
|
preselect: category?.preselect ?? true,
|
||||||
show_in_kauf: category?.show_in_kauf ?? true,
|
show_in_kauf: category?.show_in_kauf ?? true,
|
||||||
show_in_abo: category?.show_in_abo ?? true,
|
show_in_abo: category?.show_in_abo ?? true,
|
||||||
|
resets_others: category?.resets_others ?? false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -269,6 +271,29 @@ export function CategoryDialog({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="resets_others"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-lg border border-slate-200 dark:border-white/10 bg-slate-50 dark:bg-white/5 p-4">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel className="text-slate-900 dark:text-white font-semibold cursor-pointer">
|
||||||
|
Anderen Kategorien bei Auswahl zurücksetzen
|
||||||
|
</FormLabel>
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
Wenn aktiv, setzt die Auswahl eines Artikels dieser Kategorie alle anderen Kategorien zurück.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -379,30 +379,61 @@ export function OrderWizard({
|
|||||||
nextProductId = productId
|
nextProductId = productId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSelectedNow = cat?.allow_multiselect
|
||||||
|
? nextProductIds.includes(productId)
|
||||||
|
: nextProductId === productId
|
||||||
|
|
||||||
|
const shouldReset = isSelectedNow && cat?.resets_others
|
||||||
|
|
||||||
const next = {
|
const next = {
|
||||||
...prev,
|
...prev,
|
||||||
[catId]: { productId: nextProductId, productIds: nextProductIds, moduleIds: [] },
|
[catId]: { productId: nextProductId, productIds: nextProductIds, moduleIds: [] },
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically deselect now conflicting products in other categories
|
if (shouldReset) {
|
||||||
const selectedIds: string[] = []
|
// Reset all other categories
|
||||||
Object.values(next).forEach(s => {
|
categories.forEach(c => {
|
||||||
if (s.productIds && s.productIds.length > 0) {
|
if (c.id !== catId) {
|
||||||
s.productIds.forEach(id => {
|
next[c.id] = { productId: null, productIds: [], moduleIds: [] }
|
||||||
if (id) selectedIds.push(id)
|
}
|
||||||
})
|
})
|
||||||
} else if (s.productId) {
|
} else {
|
||||||
selectedIds.push(s.productId)
|
// Automatically deselect now conflicting products in other categories
|
||||||
}
|
const selectedIds: string[] = []
|
||||||
})
|
Object.values(next).forEach(s => {
|
||||||
|
if (s.productIds && s.productIds.length > 0) {
|
||||||
|
s.productIds.forEach(id => {
|
||||||
|
if (id) selectedIds.push(id)
|
||||||
|
})
|
||||||
|
} else if (s.productId) {
|
||||||
|
selectedIds.push(s.productId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
categories.forEach(c => {
|
categories.forEach(c => {
|
||||||
if (c.id === catId) return
|
if (c.id === catId) return
|
||||||
const s = next[c.id]
|
const s = next[c.id]
|
||||||
if (c.allow_multiselect && s?.productIds) {
|
if (c.allow_multiselect && s?.productIds) {
|
||||||
const validProductIds = s.productIds.filter(pId => {
|
const validProductIds = s.productIds.filter(pId => {
|
||||||
const prod = products.find(p => p.id === pId)
|
const prod = products.find(p => p.id === pId)
|
||||||
const otherSelectedProductIds = selectedIds.filter(id => id !== pId)
|
const otherSelectedProductIds = selectedIds.filter(id => id !== pId)
|
||||||
|
const isExcluded = prod && (
|
||||||
|
otherSelectedProductIds.some(otherId => {
|
||||||
|
const otherProd = products.find(p => p.id === otherId)
|
||||||
|
return otherProd?.exclusions?.includes(prod.id)
|
||||||
|
}) ||
|
||||||
|
prod.exclusions?.some(exId => otherSelectedProductIds.includes(exId))
|
||||||
|
)
|
||||||
|
return !isExcluded
|
||||||
|
})
|
||||||
|
next[c.id] = {
|
||||||
|
productId: validProductIds[0] || null,
|
||||||
|
productIds: validProductIds,
|
||||||
|
moduleIds: []
|
||||||
|
}
|
||||||
|
} else if (s?.productId) {
|
||||||
|
const prod = products.find(p => p.id === s.productId)
|
||||||
|
const otherSelectedProductIds = selectedIds.filter(id => id !== s.productId)
|
||||||
const isExcluded = prod && (
|
const isExcluded = prod && (
|
||||||
otherSelectedProductIds.some(otherId => {
|
otherSelectedProductIds.some(otherId => {
|
||||||
const otherProd = products.find(p => p.id === otherId)
|
const otherProd = products.find(p => p.id === otherId)
|
||||||
@@ -410,28 +441,12 @@ export function OrderWizard({
|
|||||||
}) ||
|
}) ||
|
||||||
prod.exclusions?.some(exId => otherSelectedProductIds.includes(exId))
|
prod.exclusions?.some(exId => otherSelectedProductIds.includes(exId))
|
||||||
)
|
)
|
||||||
return !isExcluded
|
if (isExcluded) {
|
||||||
})
|
next[c.id] = { productId: null, productIds: [], moduleIds: [] }
|
||||||
next[c.id] = {
|
}
|
||||||
productId: validProductIds[0] || null,
|
|
||||||
productIds: validProductIds,
|
|
||||||
moduleIds: []
|
|
||||||
}
|
}
|
||||||
} else if (s?.productId) {
|
})
|
||||||
const prod = products.find(p => p.id === s.productId)
|
}
|
||||||
const otherSelectedProductIds = selectedIds.filter(id => id !== s.productId)
|
|
||||||
const isExcluded = prod && (
|
|
||||||
otherSelectedProductIds.some(otherId => {
|
|
||||||
const otherProd = products.find(p => p.id === otherId)
|
|
||||||
return otherProd?.exclusions?.includes(prod.id)
|
|
||||||
}) ||
|
|
||||||
prod.exclusions?.some(exId => otherSelectedProductIds.includes(exId))
|
|
||||||
)
|
|
||||||
if (isExcluded) {
|
|
||||||
next[c.id] = { productId: null, productIds: [], moduleIds: [] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export type Category = {
|
|||||||
preselect: boolean
|
preselect: boolean
|
||||||
show_in_kauf?: boolean
|
show_in_kauf?: boolean
|
||||||
show_in_abo?: boolean
|
show_in_abo?: boolean
|
||||||
|
resets_others: boolean
|
||||||
created_at: string
|
created_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Add resets_others column to categories table
|
||||||
|
ALTER TABLE public.categories ADD COLUMN IF NOT EXISTS resets_others BOOLEAN DEFAULT false NOT NULL;
|
||||||
Reference in New Issue
Block a user