Angebote bearbeiten fuer Admin und Ersteller implementiert
All checks were successful
Staging Build / build (push) Successful in 3m51s
All checks were successful
Staging Build / build (push) Successful in 3m51s
This commit is contained in:
@@ -216,22 +216,29 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{order.pdf_url ? (
|
||||
<div className="flex justify-end gap-1.5">
|
||||
<Button variant="outline" size="sm" asChild className="h-8 border-white/10 hover:bg-primary/20 text-xs">
|
||||
<a href={order.pdf_url} target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="w-3.5 h-3.5 mr-1" /> PDF
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" asChild className="h-8 text-xs">
|
||||
<a href={`/api/admin/orders/${order.id}/download`}>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs text-slate-500 italic">Keine Bestätigung</span>
|
||||
)}
|
||||
<div className="flex justify-end gap-1.5">
|
||||
<Button variant="outline" size="sm" asChild className="h-8 border-amber-500/20 hover:bg-amber-500/10 text-amber-400 text-xs">
|
||||
<a href={`/order?id=${order.id}`}>
|
||||
Bearbeiten
|
||||
</a>
|
||||
</Button>
|
||||
{order.pdf_url ? (
|
||||
<>
|
||||
<Button variant="outline" size="sm" asChild className="h-8 border-white/10 hover:bg-primary/20 text-xs">
|
||||
<a href={order.pdf_url} target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="w-3.5 h-3.5 mr-1" /> PDF
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" asChild className="h-8 text-xs">
|
||||
<a href={`/api/admin/orders/${order.id}/download`}>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xs text-slate-500 italic self-center px-2">Keine Bestätigung</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { Product, ProductModule, Profile, EndCustomer } from '@/lib/types'
|
||||
import { Product, ProductModule, Profile, EndCustomer, Order } from '@/lib/types'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
@@ -12,7 +12,7 @@ import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { Check, ChevronRight, ChevronLeft, ShoppingCart, User, ShieldCheck, Cloud, Utensils, HardDrive, Package, AlertCircle, Loader2, UserPlus, Building2, CreditCard, Calendar, Search } from 'lucide-react'
|
||||
import { submitOrder } from '@/lib/actions/orders'
|
||||
import { submitOrder, updateOrder } from '@/lib/actions/orders'
|
||||
import { createEndCustomer } from '@/lib/actions/end-customers'
|
||||
import { Category } from '@/lib/types'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
@@ -80,11 +80,13 @@ export function OrderWizard({
|
||||
categories,
|
||||
initialProfile,
|
||||
initialEndCustomers,
|
||||
initialOrder,
|
||||
}: {
|
||||
products: Product[]
|
||||
categories: Category[]
|
||||
initialProfile: Profile | null
|
||||
initialEndCustomers: EndCustomer[]
|
||||
initialOrder?: Order | null
|
||||
}) {
|
||||
const router = useRouter()
|
||||
const [step, setStep] = useState(1)
|
||||
@@ -96,7 +98,9 @@ export function OrderWizard({
|
||||
// Endkunden-State
|
||||
const [endCustomers, setEndCustomers] = useState<EndCustomer[]>(initialEndCustomers)
|
||||
const [selectedEndCustomerId, setSelectedEndCustomerId] = useState<string | null>(
|
||||
initialEndCustomers.length > 0 ? initialEndCustomers[0].id : null
|
||||
initialOrder
|
||||
? initialOrder.end_customer_id
|
||||
: (initialEndCustomers.length > 0 ? initialEndCustomers[0].id : null)
|
||||
)
|
||||
const selectedEndCustomer = endCustomers.find(c => c.id === selectedEndCustomerId) ?? null
|
||||
|
||||
@@ -134,13 +138,26 @@ export function OrderWizard({
|
||||
})
|
||||
|
||||
// Abrechnungsmodell-Auswahl ('one_time' = Kauf, 'monthly' = Abo)
|
||||
const [selectedBillingInterval, setSelectedBillingInterval] = useState<'one_time' | 'monthly'>('monthly')
|
||||
const [selectedBillingInterval, setSelectedBillingInterval] = useState<'one_time' | 'monthly'>(
|
||||
initialOrder?.order_data?.billing_cycle ?? 'monthly'
|
||||
)
|
||||
|
||||
// Modulmengen (moduleId -> Menge)
|
||||
const [moduleQuantities, setModuleQuantities] = useState<Record<string, number>>({})
|
||||
const [moduleQuantities, setModuleQuantities] = useState<Record<string, number>>(() => {
|
||||
if (!initialOrder) return {}
|
||||
const quantities: Record<string, number> = {}
|
||||
initialOrder.order_data?.items?.forEach(item => {
|
||||
item.selected_modules?.forEach(mod => {
|
||||
quantities[mod.module_id] = mod.quantity ?? 1
|
||||
})
|
||||
})
|
||||
return quantities
|
||||
})
|
||||
|
||||
// Datum der letzten Lizenz (Bestandskunden-Update-Logik)
|
||||
const [lastLicenseDate, setLastLicenseDate] = useState<string>("")
|
||||
const [lastLicenseDate, setLastLicenseDate] = useState<string>(
|
||||
initialOrder?.order_data?.last_license_date ?? ""
|
||||
)
|
||||
|
||||
const lastLicenseMonths = useMemo(() => {
|
||||
if (!lastLicenseDate || customerMode !== 'select' || !selectedEndCustomerId) return null
|
||||
@@ -174,6 +191,16 @@ export function OrderWizard({
|
||||
const [selections, setSelections] = useState<Record<string, CategorySelection>>(() => {
|
||||
const init: Record<string, CategorySelection> = {}
|
||||
categories.forEach(cat => {
|
||||
if (initialOrder) {
|
||||
const item = initialOrder.order_data?.items?.find(i => i.category_id === cat.id)
|
||||
if (item) {
|
||||
init[cat.id] = {
|
||||
productId: item.product_id,
|
||||
moduleIds: item.selected_modules?.map(m => m.module_id) ?? []
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
const first = products.find(p => p.category_id === cat.id && p.show_in_abo !== false)
|
||||
init[cat.id] = { productId: first?.id ?? null, moduleIds: [] }
|
||||
})
|
||||
@@ -335,21 +362,34 @@ export function OrderWizard({
|
||||
if (isSubmitting) return // Doppelklick-Guard
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
const order = await submitOrder({
|
||||
selections,
|
||||
moduleQuantities,
|
||||
products,
|
||||
categories,
|
||||
customerProfile: customerData,
|
||||
endCustomerId: selectedEndCustomerId,
|
||||
endCustomer: selectedEndCustomer,
|
||||
billingInterval: selectedBillingInterval,
|
||||
lastLicenseDate: lastLicenseDate || null,
|
||||
})
|
||||
let order
|
||||
if (initialOrder) {
|
||||
order = await updateOrder(initialOrder.id, {
|
||||
selections,
|
||||
moduleQuantities,
|
||||
customerProfile: customerData,
|
||||
endCustomerId: selectedEndCustomerId,
|
||||
endCustomer: selectedEndCustomer,
|
||||
billingInterval: selectedBillingInterval,
|
||||
lastLicenseDate: lastLicenseDate || null,
|
||||
})
|
||||
} else {
|
||||
order = await submitOrder({
|
||||
selections,
|
||||
moduleQuantities,
|
||||
products,
|
||||
categories,
|
||||
customerProfile: customerData,
|
||||
endCustomerId: selectedEndCustomerId,
|
||||
endCustomer: selectedEndCustomer,
|
||||
billingInterval: selectedBillingInterval,
|
||||
lastLicenseDate: lastLicenseDate || null,
|
||||
})
|
||||
}
|
||||
router.push(`/order/success?id=${order.id}`)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
alert('Fehler bei der Bestellung. Bitte versuchen Sie es erneut.')
|
||||
alert(error?.message || (initialOrder ? 'Fehler beim Aktualisieren des Angebots.' : 'Fehler bei der Bestellung. Bitte versuchen Sie es erneut.'))
|
||||
setIsSubmitting(false) // Nur bei Fehler wieder entsperren
|
||||
}
|
||||
}
|
||||
@@ -1106,9 +1146,13 @@ export function OrderWizard({
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<><Loader2 className="mr-2 h-5 w-5 animate-spin" /> Bestellung wird verarbeitet...</>
|
||||
initialOrder ? (
|
||||
<><Loader2 className="mr-2 h-5 w-5 animate-spin" /> Angebot wird aktualisiert...</>
|
||||
) : (
|
||||
<><Loader2 className="mr-2 h-5 w-5 animate-spin" /> Bestellung wird verarbeitet...</>
|
||||
)
|
||||
) : (
|
||||
'Kostenpflichtig bestellen'
|
||||
initialOrder ? 'Angebot aktualisieren' : 'Kostenpflichtig bestellen'
|
||||
)}
|
||||
</Button>
|
||||
<Button variant="ghost" className="w-full text-white" onClick={prevStep}>
|
||||
|
||||
Reference in New Issue
Block a user