292 lines
13 KiB
TypeScript
292 lines
13 KiB
TypeScript
'use client'
|
||
|
||
import { zodResolver } from "@hookform/resolvers/zod"
|
||
import { useForm } from "react-hook-form"
|
||
import * as z from "zod"
|
||
import { Button } from "@/components/ui/button"
|
||
import {
|
||
Form,
|
||
FormControl,
|
||
FormField,
|
||
FormItem,
|
||
FormLabel,
|
||
FormMessage,
|
||
} from "@/components/ui/form"
|
||
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 { Check, ChevronsUpDown } from "lucide-react"
|
||
import { cn } from "@/lib/utils"
|
||
import {
|
||
Command,
|
||
CommandEmpty,
|
||
CommandGroup,
|
||
CommandInput,
|
||
CommandItem,
|
||
CommandList,
|
||
} from "@/components/ui/command"
|
||
import {
|
||
Popover,
|
||
PopoverContent,
|
||
PopoverTrigger,
|
||
} from "@/components/ui/popover"
|
||
|
||
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(),
|
||
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(),
|
||
})
|
||
|
||
interface ReservationFormProps {
|
||
halls: { id: string, name: string }[]
|
||
customers: { id: string, full_name: string, phone?: string | null }[]
|
||
packages: { id: string, name: string, price: number }[]
|
||
}
|
||
|
||
export function ReservationForm({ halls, customers, packages }: ReservationFormProps) {
|
||
const [loading, setLoading] = useState(false)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const [openCustomer, setOpenCustomer] = useState(false)
|
||
|
||
const form = useForm<z.infer<typeof formSchema>>({
|
||
resolver: zodResolver(formSchema),
|
||
defaultValues: {
|
||
hall_id: "",
|
||
customer_id: "",
|
||
package_id: "",
|
||
date: "",
|
||
start_time: "",
|
||
end_time: "",
|
||
notes: "",
|
||
},
|
||
})
|
||
|
||
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}`)
|
||
|
||
if (endDateTime <= startDateTime) {
|
||
setError("Bitiş saati başlangıç saatinden sonra olmalıdır.")
|
||
setLoading(false)
|
||
return
|
||
}
|
||
|
||
try {
|
||
const result = await createReservation({
|
||
hall_id: values.hall_id,
|
||
customer_id: values.customer_id,
|
||
package_id: values.package_id === "none" ? undefined : values.package_id,
|
||
start_time: startDateTime.toISOString(),
|
||
end_time: endDateTime.toISOString(),
|
||
notes: values.notes,
|
||
})
|
||
|
||
if (result && result.error) {
|
||
setError(result.error)
|
||
}
|
||
} catch (e) {
|
||
setError("Beklenmedik bir hata oluştu.")
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<Form {...form}>
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||
{error && (
|
||
<div className="bg-destructive/15 text-destructive px-4 py-2 rounded-md text-sm font-medium">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="hall_id"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Salon</FormLabel>
|
||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||
<FormControl>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="Salon Seçin" />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
{halls.map(hall => (
|
||
<SelectItem key={hall.id} value={hall.id}>{hall.name}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="customer_id"
|
||
render={({ field }) => (
|
||
<FormItem className="flex flex-col">
|
||
<FormLabel>Müşteri</FormLabel>
|
||
<Popover open={openCustomer} onOpenChange={setOpenCustomer}>
|
||
<PopoverTrigger asChild>
|
||
<FormControl>
|
||
<Button
|
||
variant="outline"
|
||
role="combobox"
|
||
aria-expanded={openCustomer}
|
||
className={cn(
|
||
"w-full justify-between",
|
||
!field.value && "text-muted-foreground"
|
||
)}
|
||
>
|
||
{field.value
|
||
? customers.find((customer) => customer.id === field.value)?.full_name
|
||
: "Müşteri Seçin"}
|
||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||
</Button>
|
||
</FormControl>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-[300px] p-0">
|
||
<Command>
|
||
<CommandInput placeholder="Müşteri ara (İsim veya Telefon)..." />
|
||
<CommandList>
|
||
<CommandEmpty>Müşteri bulunamadı.</CommandEmpty>
|
||
<CommandGroup>
|
||
{customers.map((customer) => (
|
||
<CommandItem
|
||
value={`${customer.full_name} ${customer.phone || ''}`}
|
||
key={customer.id}
|
||
onSelect={() => {
|
||
form.setValue("customer_id", customer.id)
|
||
setOpenCustomer(false)
|
||
}}
|
||
>
|
||
<Check
|
||
className={cn(
|
||
"mr-2 h-4 w-4",
|
||
customer.id === field.value
|
||
? "opacity-100"
|
||
: "opacity-0"
|
||
)}
|
||
/>
|
||
<div className="flex flex-col">
|
||
<span>{customer.full_name}</span>
|
||
{customer.phone && (
|
||
<span className="text-xs text-muted-foreground">{customer.phone}</span>
|
||
)}
|
||
</div>
|
||
</CommandItem>
|
||
))}
|
||
</CommandGroup>
|
||
</CommandList>
|
||
</Command>
|
||
</PopoverContent>
|
||
</Popover>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="date"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Tarih</FormLabel>
|
||
<FormControl>
|
||
<Input type="date" {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="start_time"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Başlangıç Saati</FormLabel>
|
||
<FormControl>
|
||
<Input type="time" {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="end_time"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Bitiş Saati</FormLabel>
|
||
<FormControl>
|
||
<Input type="time" {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="package_id"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Paket (Opsiyonel)</FormLabel>
|
||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||
<FormControl>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="Paket Seçin" />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
<SelectItem value="none">Paket Yok</SelectItem>
|
||
{packages.map(p => (
|
||
<SelectItem key={p.id} value={p.id}>{p.name} - ₺{p.price}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="notes"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Notlar</FormLabel>
|
||
<FormControl>
|
||
<Textarea placeholder="Özel istekler..." {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<Button type="submit" disabled={loading} className="w-full">
|
||
{loading ? "Oluşturuluyor..." : "Rezervasyonu Oluştur"}
|
||
</Button>
|
||
</form>
|
||
</Form>
|
||
)
|
||
}
|