feat: add hall logo support, fix fonts and build errors
This commit is contained in:
@@ -67,7 +67,7 @@ export function EditCustomerForm({ customer }: EditCustomerFormProps) {
|
||||
toast.success("Müşteri başarıyla güncellendi")
|
||||
router.push('/dashboard/customers')
|
||||
router.refresh()
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error("Bir hata oluştu")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -83,7 +83,7 @@ export function EditCustomerForm({ customer }: EditCustomerFormProps) {
|
||||
toast.success("Müşteri silindi")
|
||||
router.push('/dashboard/customers')
|
||||
router.refresh()
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error("Silme işlemi başarısız")
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Button } from "@/components/ui/button"
|
||||
import { Plus, Search, Phone, Mail, FileText, Edit } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card"
|
||||
|
||||
export default async function CustomersPage({
|
||||
searchParams,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { createClient } from "@/lib/supabase/client"
|
||||
import { toast } from "sonner"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
export function CategoryManager({ initialCategories }: { initialCategories: any[] }) {
|
||||
export function CategoryManager({ initialCategories }: { initialCategories: { id: string; name: string }[] }) {
|
||||
const [categories, setCategories] = useState(initialCategories)
|
||||
const [newCategory, setNewCategory] = useState("")
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
@@ -17,7 +17,7 @@ import { toast } from "sonner"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { format } from "date-fns"
|
||||
|
||||
export function ExpenseForm({ categories }: { categories: any[] }) {
|
||||
export function ExpenseForm({ categories }: { categories: { id: string; name: string }[] }) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
const supabase = createClient()
|
||||
|
||||
@@ -65,7 +65,7 @@ export default async function ExpensesPage() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
expenses.map((expense: any) => (
|
||||
expenses.map((expense) => (
|
||||
<TableRow key={expense.id}>
|
||||
<TableCell>
|
||||
{format(new Date(expense.date), 'd MMMM yyyy', { locale: tr })}
|
||||
|
||||
@@ -18,7 +18,7 @@ export async function deleteHall(id: string) {
|
||||
revalidatePath('/dashboard/halls')
|
||||
}
|
||||
|
||||
export async function updateHall(id: string, data: { name: string; capacity: number; description?: string }) {
|
||||
export async function updateHall(id: string, data: { name: string; capacity: number; description?: string; logo_url?: string }) {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase
|
||||
@@ -27,6 +27,7 @@ export async function updateHall(id: string, data: { name: string; capacity: num
|
||||
name: data.name,
|
||||
capacity: data.capacity,
|
||||
description: data.description,
|
||||
logo_url: data.logo_url,
|
||||
})
|
||||
.eq('id', id)
|
||||
|
||||
|
||||
25
src/app/dashboard/halls/[id]/page.tsx
Normal file
25
src/app/dashboard/halls/[id]/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createClient } from "@/lib/supabase/server"
|
||||
import { HallForm } from "../hall-form"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export default async function HallEditPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params
|
||||
const supabase = await createClient()
|
||||
const { data: hall } = await supabase.from('halls').select('*').eq('id', id).single()
|
||||
|
||||
if (!hall) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Salon Düzenle</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Salon bilgilerini güncelleyin.
|
||||
</p>
|
||||
</div>
|
||||
<HallForm initialData={hall} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { ImageUpload } from "@/components/ui/image-upload"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { createHall } from "./actions"
|
||||
import { createHall } from "./new/actions"
|
||||
import { updateHall } from "./[id]/actions"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
@@ -27,29 +28,46 @@ const formSchema = z.object({
|
||||
capacity: z.coerce.number().min(1, {
|
||||
message: "Kapasite en az 1 olmalıdır.",
|
||||
}),
|
||||
logo_url: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
})
|
||||
|
||||
export function HallForm() {
|
||||
interface HallFormProps {
|
||||
initialData?: {
|
||||
id: string
|
||||
name: string
|
||||
capacity: number
|
||||
description?: string | null
|
||||
logo_url?: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export function HallForm({ initialData }: HallFormProps) {
|
||||
const router = useRouter()
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
const form = useForm({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
capacity: 0,
|
||||
description: "",
|
||||
name: initialData?.name || "",
|
||||
capacity: initialData?.capacity || 0,
|
||||
logo_url: initialData?.logo_url || "",
|
||||
description: initialData?.description || "",
|
||||
},
|
||||
})
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
setLoading(true)
|
||||
try {
|
||||
await createHall(values)
|
||||
if (initialData) {
|
||||
await updateHall(initialData.id, values)
|
||||
toast.success("Salon başarıyla güncellendi")
|
||||
} else {
|
||||
await createHall(values)
|
||||
toast.success("Salon başarıyla oluşturuldu")
|
||||
}
|
||||
router.push('/dashboard/halls')
|
||||
router.refresh()
|
||||
toast.success("Salon başarıyla oluşturuldu")
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast.error("Bir hata oluştu")
|
||||
@@ -81,7 +99,24 @@ export function HallForm() {
|
||||
<FormItem>
|
||||
<FormLabel>Kapasite</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" placeholder="500" {...field} />
|
||||
<Input type="number" placeholder="500" {...field} value={field.value as number} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="logo_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Kurum Logosu</FormLabel>
|
||||
<FormControl>
|
||||
<ImageUpload
|
||||
value={field.value || ""}
|
||||
onChange={field.onChange}
|
||||
bucketName="hall-logos"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -101,7 +136,7 @@ export function HallForm() {
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? "Kaydediliyor..." : "Kaydet"}
|
||||
{loading ? "Kaydediliyor..." : (initialData ? "Güncelle" : "Kaydet")}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
@@ -5,13 +5,14 @@ import { revalidatePath } from "next/cache"
|
||||
|
||||
import { logAction } from "@/lib/logger"
|
||||
|
||||
export async function createHall(data: { name: string; capacity: number; description?: string }) {
|
||||
export async function createHall(data: { name: string; capacity: number; description?: string; logo_url?: string }) {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { data: newHall, error } = await supabase.from('halls').insert({
|
||||
name: data.name,
|
||||
capacity: data.capacity,
|
||||
description: data.description,
|
||||
logo_url: data.logo_url,
|
||||
}).select().single()
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { HallForm } from "./hall-form"
|
||||
|
||||
import { HallForm } from "../hall-form"
|
||||
|
||||
export default function NewHallPage() {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Yeni Salon Ekle</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HallForm />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Yeni Salon Ekle</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Sisteme yeni bir düğün salonu ekleyin.
|
||||
</p>
|
||||
</div>
|
||||
<HallForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,12 +34,29 @@ export default async function HallsPage() {
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{halls?.map((hall) => (
|
||||
<Card key={hall.id} className="flex flex-col overflow-hidden transition-all hover:shadow-md">
|
||||
<CardHeader className="bg-muted/20 pb-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<CardTitle className="text-xl font-semibold">{hall.name}</CardTitle>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-primary">
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
import Image from "next/image"
|
||||
|
||||
// ... inside the CardHeader ...
|
||||
<CardHeader className="bg-muted/20 pb-4 relative">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
{hall.logo_url && (
|
||||
<div className="relative h-12 w-12 rounded-lg overflow-hidden border bg-background shrink-0">
|
||||
<Image
|
||||
src={hall.logo_url}
|
||||
alt={hall.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<CardTitle className="text-xl font-semibold">{hall.name}</CardTitle>
|
||||
</div>
|
||||
<Link href={`/dashboard/halls/${hall.id}`}>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-primary">
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pt-6 space-y-4">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { createClient } from "@/lib/supabase/server"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { CalendarDays, CreditCard, Users, DollarSign, TrendingUp, ArrowUpRight, Calendar as CalendarIcon } from "lucide-react"
|
||||
import { CalendarDays, DollarSign, TrendingUp, Calendar as CalendarIcon } from "lucide-react"
|
||||
import { format } from "date-fns"
|
||||
import { tr } from "date-fns/locale"
|
||||
import Link from "next/link"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const supabase = await createClient()
|
||||
|
||||
@@ -41,6 +41,11 @@ export default async function ReservationsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const getSingle = (item: any) => {
|
||||
if (Array.isArray(item)) return item[0]
|
||||
return item
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
@@ -81,28 +86,34 @@ export default async function ReservationsPage() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
reservations?.map((res) => (
|
||||
<TableRow key={res.id}>
|
||||
<TableCell className="font-medium">
|
||||
{format(new Date(res.start_time), 'd MMMM yyyy', { locale: tr })}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{format(new Date(res.start_time), 'HH:mm')} - {format(new Date(res.end_time), 'HH:mm')}
|
||||
</TableCell>
|
||||
<TableCell>{res.halls?.name}</TableCell>
|
||||
<TableCell>{res.customers?.full_name}</TableCell>
|
||||
<TableCell>
|
||||
{res.packages?.name}
|
||||
{res.packages?.price && <span className="text-xs text-muted-foreground block">₺{res.packages.price}</span>}
|
||||
</TableCell>
|
||||
<TableCell>{getStatusBadge(res.status || 'pending')}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Link href={`/dashboard/reservations/${res.id}`}>
|
||||
<Button variant="ghost" size="sm">Detay</Button>
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
reservations?.map((res) => {
|
||||
const hall = getSingle(res.halls)
|
||||
const customer = getSingle(res.customers)
|
||||
const pkg = getSingle(res.packages)
|
||||
|
||||
return (
|
||||
<TableRow key={res.id}>
|
||||
<TableCell className="font-medium">
|
||||
{format(new Date(res.start_time), 'd MMMM yyyy', { locale: tr })}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{format(new Date(res.start_time), 'HH:mm')} - {format(new Date(res.end_time), 'HH:mm')}
|
||||
</TableCell>
|
||||
<TableCell>{hall?.name}</TableCell>
|
||||
<TableCell>{customer?.full_name}</TableCell>
|
||||
<TableCell>
|
||||
{pkg?.name}
|
||||
{pkg?.price && <span className="text-xs text-muted-foreground block">₺{pkg.price}</span>}
|
||||
</TableCell>
|
||||
<TableCell>{getStatusBadge(res.status || 'pending')}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Link href={`/dashboard/reservations/${res.id}`}>
|
||||
<Button variant="ghost" size="sm">Detay</Button>
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
@@ -115,42 +126,48 @@ export default async function ReservationsPage() {
|
||||
Henüz rezervasyon yok.
|
||||
</div>
|
||||
) : (
|
||||
reservations?.map((res) => (
|
||||
<Card key={res.id} className="overflow-hidden">
|
||||
<CardHeader className="bg-muted/20 p-4 flex flex-row items-center justify-between space-y-0">
|
||||
<span className="font-semibold">
|
||||
{format(new Date(res.start_time), 'd MMMM yyyy', { locale: tr })}
|
||||
</span>
|
||||
{getStatusBadge(res.status || 'pending')}
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 space-y-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<Clock className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{format(new Date(res.start_time), 'HH:mm')} - {format(new Date(res.end_time), 'HH:mm')}
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<MapPin className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{res.halls?.name}
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<User className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{res.customers?.full_name}
|
||||
</div>
|
||||
{res.packages?.name && (
|
||||
reservations?.map((res) => {
|
||||
const hall = getSingle(res.halls)
|
||||
const customer = getSingle(res.customers)
|
||||
const pkg = getSingle(res.packages)
|
||||
|
||||
return (
|
||||
<Card key={res.id} className="overflow-hidden">
|
||||
<CardHeader className="bg-muted/20 p-4 flex flex-row items-center justify-between space-y-0">
|
||||
<span className="font-semibold">
|
||||
{format(new Date(res.start_time), 'd MMMM yyyy', { locale: tr })}
|
||||
</span>
|
||||
{getStatusBadge(res.status || 'pending')}
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 space-y-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<Package className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
<span>{res.packages.name}</span>
|
||||
{res.packages.price && <span className="ml-1 text-muted-foreground">(₺{res.packages.price})</span>}
|
||||
<Clock className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{format(new Date(res.start_time), 'HH:mm')} - {format(new Date(res.end_time), 'HH:mm')}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="p-4 pt-0">
|
||||
<Link href={`/dashboard/reservations/${res.id}`} className="w-full">
|
||||
<Button variant="outline" className="w-full">Detayları Gör</Button>
|
||||
</Link>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))
|
||||
<div className="flex items-center text-sm">
|
||||
<MapPin className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{hall?.name}
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<User className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{customer?.full_name}
|
||||
</div>
|
||||
{pkg?.name && (
|
||||
<div className="flex items-center text-sm">
|
||||
<Package className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
<span>{pkg.name}</span>
|
||||
{pkg.price && <span className="ml-1 text-muted-foreground">(₺{pkg.price})</span>}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="p-4 pt-0">
|
||||
<Link href={`/dashboard/reservations/${res.id}`} className="w-full">
|
||||
<Button variant="outline" className="w-full">Detayları Gör</Button>
|
||||
</Link>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme {
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
|
||||
--color-background: hsl(var(--background));
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { Toaster } from "@/components/ui/sonner"
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -35,7 +30,7 @@ export default function RootLayout({
|
||||
<html lang="en">
|
||||
<body
|
||||
suppressHydrationWarning
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
className={`${inter.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<Toaster />
|
||||
|
||||
Reference in New Issue
Block a user