188 lines
9.6 KiB
TypeScript
188 lines
9.6 KiB
TypeScript
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 { 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()
|
||
|
||
// 1. Total Reservations (Count)
|
||
const { count: totalReservations } = await supabase
|
||
.from('reservations')
|
||
.select('*', { count: 'exact', head: true })
|
||
.neq('status', 'cancelled')
|
||
|
||
// 2. Active Customers (Count)
|
||
const { count: totalCustomers } = await supabase
|
||
.from('customers')
|
||
.select('*', { count: 'exact', head: true })
|
||
|
||
// 3. Pending Payments (Sum of remaining balances)
|
||
// This is complex to calculate in one query without a view or function,
|
||
// so we'll approximate or fetch pending payments directly if possible.
|
||
// For now, let's just count pending reservations as a proxy or fetch recent payments.
|
||
// Better: Sum of 'amount' from 'payments' where status = 'pending' (if we tracked pending payments that way)
|
||
// Or: Calculate total potential revenue vs paid revenue.
|
||
// Let's stick to "Total Revenue" (Paid) for now.
|
||
|
||
const { data: payments } = await supabase
|
||
.from('payments')
|
||
.select('amount')
|
||
.eq('status', 'paid')
|
||
|
||
const totalRevenue = payments?.reduce((sum, p) => sum + Number(p.amount), 0) || 0
|
||
|
||
// 4. Upcoming Events (Next 5)
|
||
const { data: upcomingEvents } = await supabase
|
||
.from('reservations')
|
||
.select(`
|
||
id,
|
||
start_time,
|
||
status,
|
||
halls (name),
|
||
customers (full_name)
|
||
`)
|
||
.gte('start_time', new Date().toISOString())
|
||
.neq('status', 'cancelled')
|
||
.order('start_time', { ascending: true })
|
||
.limit(5)
|
||
|
||
// 5. Recent Activities (Last 5 created reservations)
|
||
const { data: recentReservations } = await supabase
|
||
.from('reservations')
|
||
.select(`
|
||
id,
|
||
created_at,
|
||
customers (full_name)
|
||
`)
|
||
.order('created_at', { ascending: false })
|
||
.limit(5)
|
||
|
||
return (
|
||
<div className="space-y-8">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h2 className="text-3xl font-bold tracking-tight gradient-text">Hoş Geldiniz, Admin</h2>
|
||
<p className="text-muted-foreground mt-1">İşletmenizin durumu hakkında genel bakış.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||
<Card className="card-hover border-l-4 border-l-blue-500">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||
Toplam Rezervasyon
|
||
</CardTitle>
|
||
<div className="h-8 w-8 rounded-full bg-blue-100 dark:bg-blue-900/20 flex items-center justify-center">
|
||
<CalendarDays className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-3xl font-bold mt-2">{totalReservations || 0}</div>
|
||
<p className="text-xs text-muted-foreground mt-1 flex items-center">
|
||
Aktif rezervasyonlar
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="card-hover border-l-4 border-l-purple-500">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||
Toplam Müşteri
|
||
</CardTitle>
|
||
<div className="h-8 w-8 rounded-full bg-purple-100 dark:bg-purple-900/20 flex items-center justify-center">
|
||
<Users className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-3xl font-bold mt-2">{totalCustomers || 0}</div>
|
||
<p className="text-xs text-muted-foreground mt-1 flex items-center">
|
||
Kayıtlı müşteri sayısı
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="card-hover border-l-4 border-l-green-500">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||
Toplam Gelir
|
||
</CardTitle>
|
||
<div className="h-8 w-8 rounded-full bg-green-100 dark:bg-green-900/20 flex items-center justify-center">
|
||
<DollarSign className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-3xl font-bold mt-2">₺{totalRevenue.toLocaleString('tr-TR')}</div>
|
||
<p className="text-xs text-muted-foreground mt-1 flex items-center">
|
||
Tahsil edilen ödemeler
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-7">
|
||
<Card className="col-span-4 shadow-md border-none">
|
||
<CardHeader>
|
||
<CardTitle>Yaklaşan Etkinlikler</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{upcomingEvents?.length === 0 ? (
|
||
<div className="h-[200px] flex items-center justify-center bg-muted/20 rounded-lg border border-dashed">
|
||
<p className="text-muted-foreground">Yaklaşan etkinlik bulunmuyor.</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{upcomingEvents?.map((event) => (
|
||
<div key={event.id} className="flex items-center justify-between p-4 border rounded-lg bg-card hover:bg-accent/50 transition-colors">
|
||
<div className="flex items-center gap-4">
|
||
<div className="h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center text-primary">
|
||
<CalendarIcon className="h-5 w-5" />
|
||
</div>
|
||
<div>
|
||
<p className="font-medium">{event.customers?.full_name}</p>
|
||
<p className="text-sm text-muted-foreground">{event.halls?.name}</p>
|
||
</div>
|
||
</div>
|
||
<div className="text-right">
|
||
<p className="font-medium">{format(new Date(event.start_time), 'd MMM yyyy', { locale: tr })}</p>
|
||
<p className="text-sm text-muted-foreground">{format(new Date(event.start_time), 'HH:mm')}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
<Card className="col-span-3 shadow-md border-none">
|
||
<CardHeader>
|
||
<CardTitle>Son İşlemler</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
{recentReservations?.map((res) => (
|
||
<div key={res.id} className="flex items-center gap-4 p-3 rounded-lg hover:bg-muted/50 transition-colors">
|
||
<div className="h-9 w-9 rounded-full bg-primary/10 flex items-center justify-center text-primary font-bold text-sm">
|
||
{res.customers?.full_name?.substring(0, 2).toUpperCase()}
|
||
</div>
|
||
<div className="flex-1">
|
||
<p className="text-sm font-medium">{res.customers?.full_name} rezervasyon oluşturdu</p>
|
||
<p className="text-xs text-muted-foreground">
|
||
{format(new Date(res.created_at), 'd MMM HH:mm', { locale: tr })}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
{recentReservations?.length === 0 && (
|
||
<p className="text-sm text-muted-foreground text-center py-4">Henüz işlem yok.</p>
|
||
)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|