feat(categories): add branch visibility selection
All checks were successful
Staging Build / build (push) Successful in 2m43s
All checks were successful
Staging Build / build (push) Successful in 2m43s
This commit is contained in:
@@ -46,6 +46,8 @@ const categorySchema = z.object({
|
|||||||
allow_multiselect: z.boolean().default(false),
|
allow_multiselect: z.boolean().default(false),
|
||||||
free_items_limit: z.coerce.number().int().min(0).default(0),
|
free_items_limit: z.coerce.number().int().min(0).default(0),
|
||||||
preselect: z.boolean().default(true),
|
preselect: z.boolean().default(true),
|
||||||
|
show_in_kauf: z.boolean().default(true),
|
||||||
|
show_in_abo: z.boolean().default(true),
|
||||||
})
|
})
|
||||||
|
|
||||||
type CategoryFormValues = z.infer<typeof categorySchema>
|
type CategoryFormValues = z.infer<typeof categorySchema>
|
||||||
@@ -71,6 +73,8 @@ export function CategoryDialog({
|
|||||||
allow_multiselect: category?.allow_multiselect ?? false,
|
allow_multiselect: category?.allow_multiselect ?? false,
|
||||||
free_items_limit: category?.free_items_limit ?? 0,
|
free_items_limit: category?.free_items_limit ?? 0,
|
||||||
preselect: category?.preselect ?? true,
|
preselect: category?.preselect ?? true,
|
||||||
|
show_in_kauf: category?.show_in_kauf ?? true,
|
||||||
|
show_in_abo: category?.show_in_abo ?? true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -264,6 +268,54 @@ export function CategoryDialog({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="show_in_kauf"
|
||||||
|
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">
|
||||||
|
In Kauf anzeigen
|
||||||
|
</FormLabel>
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
Im Kauf-Zweig des Wizards anzeigen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="show_in_abo"
|
||||||
|
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">
|
||||||
|
In Abo anzeigen
|
||||||
|
</FormLabel>
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
Im Abo-Zweig des Wizards anzeigen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{form.watch('allow_multiselect') && (
|
{form.watch('allow_multiselect') && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -195,8 +195,17 @@ export function OrderWizard({
|
|||||||
}
|
}
|
||||||
}, [lastLicenseMonths, selectedBillingInterval])
|
}, [lastLicenseMonths, selectedBillingInterval])
|
||||||
|
|
||||||
|
const visibleCategories = useMemo(() => {
|
||||||
|
return categories.filter(cat =>
|
||||||
|
selectedBillingInterval === 'one_time'
|
||||||
|
? cat.show_in_kauf !== false
|
||||||
|
: cat.show_in_abo !== false
|
||||||
|
)
|
||||||
|
}, [categories, selectedBillingInterval])
|
||||||
|
|
||||||
// Per-category selection map: categoryId -> { productId, productIds, moduleIds }
|
// Per-category selection map: categoryId -> { productId, productIds, moduleIds }
|
||||||
const [selections, setSelections] = useState<Record<string, CategorySelection>>(() => {
|
const [selections, setSelections] = useState<Record<string, CategorySelection>>(() => {
|
||||||
|
const initInterval = initialOrder?.order_data?.billing_cycle ?? 'monthly'
|
||||||
const init: Record<string, CategorySelection> = {}
|
const init: Record<string, CategorySelection> = {}
|
||||||
categories.forEach(cat => {
|
categories.forEach(cat => {
|
||||||
if (initialOrder) {
|
if (initialOrder) {
|
||||||
@@ -211,10 +220,11 @@ export function OrderWizard({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cat.allow_multiselect || cat.preselect === false) {
|
const isVisible = initInterval === 'one_time' ? cat.show_in_kauf !== false : cat.show_in_abo !== false
|
||||||
|
if (!isVisible || cat.allow_multiselect || cat.preselect === false) {
|
||||||
init[cat.id] = { productId: null, productIds: [], moduleIds: [] }
|
init[cat.id] = { productId: null, productIds: [], moduleIds: [] }
|
||||||
} else {
|
} else {
|
||||||
const first = products.find(p => p.category_id === cat.id && p.show_in_abo !== false)
|
const first = products.find(p => p.category_id === cat.id && (initInterval === 'one_time' ? p.show_in_kauf !== false : p.show_in_abo !== false))
|
||||||
init[cat.id] = { productId: first?.id ?? null, productIds: first ? [first.id] : [], moduleIds: [] }
|
init[cat.id] = { productId: first?.id ?? null, productIds: first ? [first.id] : [], moduleIds: [] }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -223,7 +233,7 @@ export function OrderWizard({
|
|||||||
|
|
||||||
// Only REQUIRED categories must have a product selected (which must be filtered by selected branch)
|
// Only REQUIRED categories must have a product selected (which must be filtered by selected branch)
|
||||||
const allCategoriesFilled = useMemo(() => {
|
const allCategoriesFilled = useMemo(() => {
|
||||||
return categories
|
return visibleCategories
|
||||||
.filter(cat => cat.is_required)
|
.filter(cat => cat.is_required)
|
||||||
.every(cat => {
|
.every(cat => {
|
||||||
const sel = selections[cat.id]
|
const sel = selections[cat.id]
|
||||||
@@ -243,7 +253,7 @@ export function OrderWizard({
|
|||||||
? prod.show_in_kauf !== false
|
? prod.show_in_kauf !== false
|
||||||
: prod.show_in_abo !== false
|
: prod.show_in_abo !== false
|
||||||
})
|
})
|
||||||
}, [categories, selections, products, selectedBillingInterval])
|
}, [visibleCategories, selections, products, selectedBillingInterval])
|
||||||
|
|
||||||
// Product validation errors (requirements and exclusions)
|
// Product validation errors (requirements and exclusions)
|
||||||
const productValidationErrors = useMemo(() => {
|
const productValidationErrors = useMemo(() => {
|
||||||
@@ -459,7 +469,8 @@ export function OrderWizard({
|
|||||||
}
|
}
|
||||||
const newSelections: Record<string, CategorySelection> = {}
|
const newSelections: Record<string, CategorySelection> = {}
|
||||||
categories.forEach(cat => {
|
categories.forEach(cat => {
|
||||||
if (cat.allow_multiselect || cat.preselect === false) {
|
const isVisible = val === 'one_time' ? cat.show_in_kauf !== false : cat.show_in_abo !== false
|
||||||
|
if (!isVisible || cat.allow_multiselect || cat.preselect === false) {
|
||||||
newSelections[cat.id] = { productId: null, productIds: [], moduleIds: [] }
|
newSelections[cat.id] = { productId: null, productIds: [], moduleIds: [] }
|
||||||
} else {
|
} else {
|
||||||
const matchingProd = products.find(p =>
|
const matchingProd = products.find(p =>
|
||||||
@@ -870,7 +881,7 @@ export function OrderWizard({
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-8">
|
<CardContent className="space-y-8">
|
||||||
{categories.map((cat, idx) => {
|
{visibleCategories.map((cat, idx) => {
|
||||||
const otherSelectedProductIds = Object.entries(selections)
|
const otherSelectedProductIds = Object.entries(selections)
|
||||||
.filter(([cId]) => cId !== cat.id)
|
.filter(([cId]) => cId !== cat.id)
|
||||||
.map(([, s]) => s.productId)
|
.map(([, s]) => s.productId)
|
||||||
@@ -1122,7 +1133,7 @@ export function OrderWizard({
|
|||||||
<CardTitle className="text-white">Zusammenfassung</CardTitle>
|
<CardTitle className="text-white">Zusammenfassung</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{categories.map(cat => {
|
{visibleCategories.map(cat => {
|
||||||
const sel = selections[cat.id]
|
const sel = selections[cat.id]
|
||||||
const selectedProds: Product[] = []
|
const selectedProds: Product[] = []
|
||||||
if (cat.allow_multiselect && sel?.productIds) {
|
if (cat.allow_multiselect && sel?.productIds) {
|
||||||
@@ -1311,7 +1322,7 @@ export function OrderWizard({
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6 text-left">
|
<CardContent className="space-y-6 text-left">
|
||||||
<div className="p-4 rounded-lg bg-white/5 space-y-4">
|
<div className="p-4 rounded-lg bg-white/5 space-y-4">
|
||||||
{categories.map(cat => {
|
{visibleCategories.map(cat => {
|
||||||
const sel = selections[cat.id]
|
const sel = selections[cat.id]
|
||||||
const selectedProds: Product[] = []
|
const selectedProds: Product[] = []
|
||||||
if (cat.allow_multiselect && sel?.productIds) {
|
if (cat.allow_multiselect && sel?.productIds) {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type Category = {
|
|||||||
allow_multiselect: boolean
|
allow_multiselect: boolean
|
||||||
free_items_limit: number
|
free_items_limit: number
|
||||||
preselect: boolean
|
preselect: boolean
|
||||||
|
show_in_kauf?: boolean
|
||||||
|
show_in_abo?: boolean
|
||||||
created_at: string
|
created_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- Add show_in_kauf and show_in_abo columns to categories table
|
||||||
|
ALTER TABLE public.categories ADD COLUMN IF NOT EXISTS show_in_kauf BOOLEAN DEFAULT true NOT NULL;
|
||||||
|
ALTER TABLE public.categories ADD COLUMN IF NOT EXISTS show_in_abo BOOLEAN DEFAULT true NOT NULL;
|
||||||
Reference in New Issue
Block a user