feat(shop): move linked fee configuration to product level
All checks were successful
Staging Build / build (push) Successful in 2m52s
All checks were successful
Staging Build / build (push) Successful in 2m52s
This commit is contained in:
@@ -302,31 +302,8 @@ export function WysiwygAdminClient({
|
|||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{(prod.modules || []).length > 0 ? (
|
{(prod.modules || []).length > 0 ? (
|
||||||
(prod.modules || []).map((m) => (
|
(prod.modules || []).map((m) => (
|
||||||
<span key={m.id} className="inline-flex items-center gap-2 px-2.5 py-1 rounded text-[10px] bg-white/10 text-slate-300 font-medium">
|
<span key={m.id} className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] bg-white/10 text-slate-300 font-medium">
|
||||||
<span>{m.name}</span>
|
{m.name}
|
||||||
<select
|
|
||||||
value={m.linked_fee_product_id || ''}
|
|
||||||
onChange={async (e) => {
|
|
||||||
const val = e.target.value || null
|
|
||||||
const updatedModules = (prod.modules || []).map(mod =>
|
|
||||||
mod.id === m.id ? { ...mod, linked_fee_product_id: val } : mod
|
|
||||||
)
|
|
||||||
setProducts(products.map(p => p.id === prod.id ? { ...p, modules: updatedModules } : p))
|
|
||||||
await updateProduct(prod.id, {}, updatedModules)
|
|
||||||
}}
|
|
||||||
className="bg-slate-900 border border-white/10 text-[9px] text-slate-400 focus:ring-0 cursor-pointer rounded px-1 py-0.5 outline-none max-w-[120px]"
|
|
||||||
title="Verknüpfte Gebühr"
|
|
||||||
>
|
|
||||||
<option value="">Keine Gebühr</option>
|
|
||||||
{products
|
|
||||||
.filter(p => p.billing_interval === 'monthly')
|
|
||||||
.map(p => (
|
|
||||||
<option key={p.id} value={p.id}>
|
|
||||||
+ {p.name} ({p.base_price} €)
|
|
||||||
</option>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const updatedModules = (prod.modules || []).filter(mod => mod.id !== m.id)
|
const updatedModules = (prod.modules || []).filter(mod => mod.id !== m.id)
|
||||||
@@ -425,6 +402,30 @@ export function WysiwygAdminClient({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Verknüpfte monatliche Gebühr */}
|
||||||
|
<div className="space-y-1 mt-2">
|
||||||
|
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold block">Verknüpfte Gebühr:</span>
|
||||||
|
<select
|
||||||
|
value={prod.linked_fee_product_id || ''}
|
||||||
|
onChange={async (e) => {
|
||||||
|
const val = e.target.value || null
|
||||||
|
setProducts(products.map(p => p.id === prod.id ? { ...p, linked_fee_product_id: val } : p))
|
||||||
|
await updateProduct(prod.id, { linked_fee_product_id: val }, prod.modules || [])
|
||||||
|
}}
|
||||||
|
className="w-full bg-slate-950 border border-white/10 text-[10px] text-slate-300 focus:ring-0 cursor-pointer rounded px-2 py-1 outline-none"
|
||||||
|
>
|
||||||
|
<option value="">Keine Gebühr verknüpft</option>
|
||||||
|
{products
|
||||||
|
.filter(p => p.billing_interval === 'monthly' && p.id !== prod.id)
|
||||||
|
.map(p => (
|
||||||
|
<option key={p.id} value={p.id}>
|
||||||
|
{p.name} ({new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(p.base_price)} / mtl.)
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Modul hinzufügen Popover */}
|
{/* Modul hinzufügen Popover */}
|
||||||
<div className="mt-3 pt-2 border-t border-white/5">
|
<div className="mt-3 pt-2 border-t border-white/5">
|
||||||
<ModulePopover
|
<ModulePopover
|
||||||
|
|||||||
@@ -99,12 +99,18 @@ export async function POST(request: Request) {
|
|||||||
if (item.selections) {
|
if (item.selections) {
|
||||||
for (const catId in item.selections) {
|
for (const catId in item.selections) {
|
||||||
const sel = item.selections[catId];
|
const sel = item.selections[catId];
|
||||||
sel.moduleIds?.forEach((mId: string) => {
|
|
||||||
for (const p of products) {
|
if (sel.productId) {
|
||||||
const mod = p.modules?.find((m: any) => m.id === mId);
|
const p = products.find((prod: any) => prod.id === sel.productId);
|
||||||
if (mod && mod.linked_fee_product_id) {
|
if (p && p.linked_fee_product_id) {
|
||||||
feeProductIds.add(mod.linked_fee_product_id);
|
feeProductIds.add(p.linked_fee_product_id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.productIds?.forEach((pId: string) => {
|
||||||
|
const p = products.find((prod: any) => prod.id === pId);
|
||||||
|
if (p && p.linked_fee_product_id) {
|
||||||
|
feeProductIds.add(p.linked_fee_product_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -429,6 +429,10 @@ export function OrderWizard({
|
|||||||
oneTime += basePrice
|
oneTime += basePrice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prod.linked_fee_product_id) {
|
||||||
|
feeProductIds.add(prod.linked_fee_product_id)
|
||||||
|
}
|
||||||
|
|
||||||
// Module
|
// Module
|
||||||
sel.moduleIds?.forEach((mId: string) => {
|
sel.moduleIds?.forEach((mId: string) => {
|
||||||
const mod = prod.modules?.find(m => m.id === mId)
|
const mod = prod.modules?.find(m => m.id === mId)
|
||||||
@@ -439,10 +443,6 @@ export function OrderWizard({
|
|||||||
} else {
|
} else {
|
||||||
oneTime += mod.price * qty
|
oneTime += mod.price * qty
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mod.linked_fee_product_id) {
|
|
||||||
feeProductIds.add(mod.linked_fee_product_id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function getProducts() {
|
|||||||
const supabase = await createClient()
|
const supabase = await createClient()
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('products')
|
.from('products')
|
||||||
.select('*, category:categories(*), modules:product_modules!product_modules_product_id_fkey(*)')
|
.select('*, category:categories(*), modules:product_modules(*)')
|
||||||
.order('created_at', { ascending: false })
|
.order('created_at', { ascending: false })
|
||||||
|
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
@@ -90,7 +90,6 @@ export async function createProduct(
|
|||||||
requirements: m.requirements || [],
|
requirements: m.requirements || [],
|
||||||
exclusions: m.exclusions || [],
|
exclusions: m.exclusions || [],
|
||||||
has_quantity: m.has_quantity ?? false,
|
has_quantity: m.has_quantity ?? false,
|
||||||
linked_fee_product_id: m.linked_fee_product_id || null,
|
|
||||||
}))
|
}))
|
||||||
const { error: modulesError } = await supabase
|
const { error: modulesError } = await supabase
|
||||||
.from('product_modules')
|
.from('product_modules')
|
||||||
@@ -128,7 +127,6 @@ export async function updateProduct(id: string, product: Partial<Product>, modul
|
|||||||
requirements: m.requirements || [],
|
requirements: m.requirements || [],
|
||||||
exclusions: m.exclusions || [],
|
exclusions: m.exclusions || [],
|
||||||
has_quantity: m.has_quantity ?? false,
|
has_quantity: m.has_quantity ?? false,
|
||||||
linked_fee_product_id: m.linked_fee_product_id || null,
|
|
||||||
}))
|
}))
|
||||||
const { error: modulesError } = await supabase
|
const { error: modulesError } = await supabase
|
||||||
.from('product_modules')
|
.from('product_modules')
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type Product = {
|
|||||||
requirements?: string[]
|
requirements?: string[]
|
||||||
exclusions?: string[]
|
exclusions?: string[]
|
||||||
allow_update_discount?: boolean
|
allow_update_discount?: boolean
|
||||||
|
linked_fee_product_id?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Category = {
|
export type Category = {
|
||||||
@@ -46,7 +47,6 @@ export type ProductModule = {
|
|||||||
exclusions: string[] // UUID[] von Modulen, die nicht gleichzeitig aktiv sein dürfen
|
exclusions: string[] // UUID[] von Modulen, die nicht gleichzeitig aktiv sein dürfen
|
||||||
created_at: string
|
created_at: string
|
||||||
has_quantity?: boolean
|
has_quantity?: boolean
|
||||||
linked_fee_product_id?: string | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Profile = {
|
export type Profile = {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- Remove linked_fee_product_id from product_modules
|
||||||
|
ALTER TABLE public.product_modules
|
||||||
|
DROP COLUMN IF EXISTS linked_fee_product_id;
|
||||||
|
|
||||||
|
-- Add linked_fee_product_id to products
|
||||||
|
ALTER TABLE public.products
|
||||||
|
ADD COLUMN IF NOT EXISTS linked_fee_product_id UUID REFERENCES public.products(id) ON DELETE SET NULL;
|
||||||
Reference in New Issue
Block a user