feat: update reservation form and add schema migration
This commit is contained in:
@@ -2,17 +2,26 @@ import { MainNav } from "@/components/main-nav"
|
||||
import { UserNav } from "@/components/user-nav"
|
||||
import { Building } from "lucide-react"
|
||||
import { MobileSidebar } from "@/components/mobile-sidebar"
|
||||
import { createClient } from "@/lib/supabase/server"
|
||||
|
||||
export default function DashboardLayout({
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const supabase = await createClient()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
|
||||
const userData = user ? {
|
||||
name: user.user_metadata?.full_name || user.email?.split('@')[0],
|
||||
email: user.email || null
|
||||
} : undefined
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-gray-50/50 dark:bg-gray-900/50">
|
||||
{/* Desktop Sidebar */}
|
||||
<aside className="hidden md:flex w-72 flex-col fixed inset-y-0 z-50 bg-white dark:bg-gray-950 border-r shadow-sm">
|
||||
<MainNav />
|
||||
<MainNav user={userData} />
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
@@ -20,7 +29,7 @@ export default function DashboardLayout({
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-40 h-16 glass border-b px-4 md:px-6 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 md:hidden">
|
||||
<MobileSidebar />
|
||||
<MobileSidebar user={userData} />
|
||||
<div className="flex items-center font-bold text-lg">
|
||||
<Building className="mr-2 h-5 w-5 text-primary" />
|
||||
WeddingOS
|
||||
|
||||
@@ -101,6 +101,20 @@ export default async function ReservationDetailsPage({
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||
{reservation.groom_region && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Damat Yöre</p>
|
||||
<p className="text-base">{reservation.groom_region}</p>
|
||||
</div>
|
||||
)}
|
||||
{reservation.bride_region && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Gelin Yöre</p>
|
||||
<p className="text-base">{reservation.bride_region}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Separator />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground mb-2">Notlar</p>
|
||||
|
||||
@@ -8,10 +8,13 @@ import { logAction } from "@/lib/logger"
|
||||
export async function createReservation(data: {
|
||||
hall_id: string
|
||||
customer_id: string
|
||||
package_id?: string
|
||||
package_id: string // Made required based on user request
|
||||
start_time: string
|
||||
end_time: string
|
||||
notes?: string
|
||||
groom_region?: string
|
||||
bride_region?: string
|
||||
price: number
|
||||
}) {
|
||||
const supabase = await createClient()
|
||||
|
||||
@@ -36,11 +39,14 @@ export async function createReservation(data: {
|
||||
const { data: newReservation, error } = await supabase.from('reservations').insert({
|
||||
hall_id: data.hall_id,
|
||||
customer_id: data.customer_id,
|
||||
package_id: data.package_id || null,
|
||||
package_id: data.package_id,
|
||||
start_time: data.start_time,
|
||||
end_time: data.end_time,
|
||||
status: 'pending', // Default to pending, admin must confirm
|
||||
notes: data.notes,
|
||||
groom_region: data.groom_region,
|
||||
bride_region: data.bride_region,
|
||||
price: data.price,
|
||||
}).select().single()
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -16,8 +16,7 @@ import { Input } from "@/components/ui/input"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { createReservation } from "./actions"
|
||||
import { useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { useState, useEffect } from "react"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
@@ -37,11 +36,15 @@ import {
|
||||
const formSchema = z.object({
|
||||
hall_id: z.string().min(1, "Salon seçmelisiniz."),
|
||||
customer_id: z.string().min(1, "Müşteri seçmelisiniz."),
|
||||
package_id: z.string().optional(),
|
||||
package_id: z.string().min(1, "Paket seçmelisiniz."),
|
||||
date: z.string().min(1, "Tarih seçmelisiniz."),
|
||||
start_time: z.string().min(1, "Başlangıç saati seçmelisiniz."),
|
||||
end_time: z.string().min(1, "Bitiş saati seçmelisiniz."),
|
||||
notes: z.string().optional(),
|
||||
groom_region: z.string().optional(),
|
||||
bride_region: z.string().optional(),
|
||||
price: z.coerce.number().min(0, "Fiyat 0'dan küçük olamaz."),
|
||||
discount_rate: z.coerce.number().min(0).max(100).optional(),
|
||||
})
|
||||
|
||||
interface ReservationFormProps {
|
||||
@@ -56,7 +59,7 @@ export function ReservationForm({ halls, customers, packages }: ReservationFormP
|
||||
const [openCustomer, setOpenCustomer] = useState(false)
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
resolver: zodResolver(formSchema) as any,
|
||||
defaultValues: {
|
||||
hall_id: "",
|
||||
customer_id: "",
|
||||
@@ -65,14 +68,40 @@ export function ReservationForm({ halls, customers, packages }: ReservationFormP
|
||||
start_time: "",
|
||||
end_time: "",
|
||||
notes: "",
|
||||
groom_region: "",
|
||||
bride_region: "",
|
||||
price: 0,
|
||||
discount_rate: 0,
|
||||
},
|
||||
})
|
||||
|
||||
// Watch for changes
|
||||
const selectedPackageId = form.watch("package_id")
|
||||
const discountRate = form.watch("discount_rate")
|
||||
|
||||
// Auto-calculate price logic
|
||||
useEffect(() => {
|
||||
if (selectedPackageId) {
|
||||
const pkg = packages.find(p => p.id === selectedPackageId)
|
||||
if (pkg) {
|
||||
const basePrice = Number(pkg.price)
|
||||
const discount = Number(discountRate) || 0
|
||||
// Calculate new price
|
||||
const discountedPrice = basePrice * (1 - discount / 100)
|
||||
|
||||
// Update price field if it differs significantly (avoid loops/floating point issues)
|
||||
const currentPrice = form.getValues("price")
|
||||
if (Math.abs(currentPrice - discountedPrice) > 0.1) {
|
||||
form.setValue('price', Number(discountedPrice.toFixed(2)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [selectedPackageId, discountRate, packages, form])
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
// Combine date and time
|
||||
const startDateTime = new Date(`${values.date}T${values.start_time}`)
|
||||
const endDateTime = new Date(`${values.date}T${values.end_time}`)
|
||||
|
||||
@@ -86,10 +115,13 @@ export function ReservationForm({ halls, customers, packages }: ReservationFormP
|
||||
const result = await createReservation({
|
||||
hall_id: values.hall_id,
|
||||
customer_id: values.customer_id,
|
||||
package_id: values.package_id === "none" ? undefined : values.package_id,
|
||||
package_id: values.package_id,
|
||||
start_time: startDateTime.toISOString(),
|
||||
end_time: endDateTime.toISOString(),
|
||||
notes: values.notes,
|
||||
groom_region: values.groom_region,
|
||||
bride_region: values.bride_region,
|
||||
price: values.price,
|
||||
})
|
||||
|
||||
if (result && result.error) {
|
||||
@@ -249,7 +281,7 @@ export function ReservationForm({ halls, customers, packages }: ReservationFormP
|
||||
name="package_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Paket (Opsiyonel)</FormLabel>
|
||||
<FormLabel>Paket (Zorunlu)</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -257,7 +289,6 @@ export function ReservationForm({ halls, customers, packages }: ReservationFormP
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">Paket Yok</SelectItem>
|
||||
{packages.map(p => (
|
||||
<SelectItem key={p.id} value={p.id}>{p.name} - ₺{p.price}</SelectItem>
|
||||
))}
|
||||
@@ -268,6 +299,76 @@ export function ReservationForm({ halls, customers, packages }: ReservationFormP
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="discount_rate"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>İndirim Oranı (%)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
placeholder="0"
|
||||
{...field}
|
||||
onChange={e => field.onChange(e.target.valueAsNumber)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="price"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Toplam Tutar (TL)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
{...field}
|
||||
onChange={e => field.onChange(e.target.valueAsNumber)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="groom_region"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Damat Yöre</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Örn: Ankara" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bride_region"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Gelin Yöre</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Örn: İzmir" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notes"
|
||||
|
||||
Reference in New Issue
Block a user