feat: Implement dashboard navigation, payments list, and deployment docs

This commit is contained in:
2025-12-07 19:05:59 +03:00
parent ce41fdb432
commit b189a19651
5 changed files with 248 additions and 49 deletions

51
COOLIFY.md Normal file
View File

@@ -0,0 +1,51 @@
# Coolify ile Düğün Salonu Uygulaması Yayınlama Rehberi
Bu proje **Next.js** tabanlıdır ve Coolify üzerinde **Nixpacks** kullanılarak kolayca yayınlanabilir.
## 1. Hazırlık
Projenizin kodlarının GitHub, GitLab veya Bitbucket üzerinde güncel olduğundan emin olun.
## 2. Coolify Üzerinde Proje Oluşturma
1. Coolify panelinize giriş yapın.
2. **Projects** (Projeler) sekmesine gidin ve bir proje seçin (veya yeni oluşturun).
3. **New** -> **Public Repository** (veya Private Repository) seçeneğini tıklayın.
4. Git deposu bağlantısını yapıştırın (Örn: `https://github.com/kullanici/dugunsalonu`).
5. **Check Repository** diyerek ilerleyin.
## 3. Yapılandırma (Configuration)
Coolify genellikle Next.js projesini otomatik algılar, ancak şu ayarları kontrol edin:
* **Build Pack**: `Nixpacks` (Önerilen)
* **Install Command**: `npm install`
* **Build Command**: `npm run build`
* **Start Command**: `npm run start`
* **Port**: `3000`
## 4. Ortam Değişkenleri (Environment Variables)
Uygulamanın çalışması için `.env.local` dosyasındaki değerleri Coolify'a eklemeniz gerekir.
1. Coolify'da projenizin **Environment Variables** (Secrets) sekmesine gidin.
2. Aşağıdaki anahtarları ve değerlerini ekleyin:
```env
NEXT_PUBLIC_SUPABASE_URL=... (Supabase URL'iniz)
NEXT_PUBLIC_SUPABASE_ANON_KEY=... (Supabase Anon Key'iniz)
```
> **Not:** Eğer varsa diğer `.env` değişkenlerinizi de eklemeyi unutmayın.
## 5. Domain Ayarları
1. **General** sekmesinde **Domains** bölümüne gidin.
2. Uygulamanızın çalışacağı alanı adını (URL) girin. (Örn: `https://panel.dugunsalonu.com`)
3. Domain sağlayıcınızdan (Cloudflare, GoDaddy vb.) `A` kaydını Coolify sunucunuzun IP adresine yönlendirin.
## 6. Dağıtım (Deploy)
1. Sağ üstteki **Deploy** butonuna basın.
2. **Deployments** sekmesinden sürecin loglarını takip edebilirsiniz.
3. "Build Success" mesajını gördüğünüzde uygulamanız yayında olacaktır.
## Olası Sorunlar ve Çözümler
* **Build Hatası:** Logları inceleyin. Genellikle bağımlılık (dependency) sorunları olabilir. `package-lock.json` dosyasının git reponuzda olduğundan emin olun.
* **Resimler Görünmüyor:** `next.config.ts` dosyasındaki `images.remotePatterns` ayarının Supabase URL'nizi kapsadığından emin olun. (Bu projede `apiilker.edoysoft.com` zaten ekli).
* **Sayfa Yenileyince 404:** Next.js SSR (Server Side Rendering) kullandığı için bu sorun genellikle yaşanmaz ancak Dockerfile kullanıyorsanız doğru yapılandırıldığından emin olun. Nixpacks bu ayarları otomatik yapar.

View File

@@ -6,6 +6,7 @@ import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableFooter,
TableHead, TableHead,
TableHeader, TableHeader,
TableRow, TableRow,
@@ -83,6 +84,16 @@ export default async function ExpensesPage() {
)) ))
)} )}
</TableBody> </TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3} className="font-bold text-right">Toplam Gider:</TableCell>
<TableCell className="text-right font-bold text-red-600 dark:text-red-400">
- {new Intl.NumberFormat('tr-TR', { style: 'currency', currency: 'TRY' }).format(
expenses?.reduce((sum, expense) => sum + Number(expense.amount), 0) || 0
)}
</TableCell>
</TableRow>
</TableFooter>
</Table> </Table>
</div> </div>
</div> </div>

View File

@@ -72,7 +72,8 @@ export default async function DashboardPage() {
</div> </div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
<Card className="card-hover border-l-4 border-l-green-500"> <Link href="/dashboard/payments" className="block">
<Card className="card-hover border-l-4 border-l-green-500 h-full cursor-pointer hover:bg-muted/5 transition-colors">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground"> <CardTitle className="text-sm font-medium text-muted-foreground">
Toplam Gelir Toplam Gelir
@@ -88,8 +89,10 @@ export default async function DashboardPage() {
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
</Link>
<Card className="card-hover border-l-4 border-l-red-500"> <Link href="/dashboard/expenses" className="block">
<Card className="card-hover border-l-4 border-l-red-500 h-full cursor-pointer hover:bg-muted/5 transition-colors">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground"> <CardTitle className="text-sm font-medium text-muted-foreground">
Toplam Gider Toplam Gider
@@ -105,6 +108,7 @@ export default async function DashboardPage() {
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
</Link>
<Card className="card-hover border-l-4 border-l-blue-500"> <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"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@@ -123,7 +127,8 @@ export default async function DashboardPage() {
</CardContent> </CardContent>
</Card> </Card>
<Card className="card-hover border-l-4 border-l-purple-500"> <Link href="/dashboard/reservations" className="block">
<Card className="card-hover border-l-4 border-l-purple-500 h-full cursor-pointer hover:bg-muted/5 transition-colors">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground"> <CardTitle className="text-sm font-medium text-muted-foreground">
Toplam Rezervasyon Toplam Rezervasyon
@@ -139,6 +144,7 @@ export default async function DashboardPage() {
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
</Link>
</div> </div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-7"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-7">

View File

@@ -0,0 +1,131 @@
import { createClient } from "@/lib/supabase/server"
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { format } from "date-fns"
import { tr } from "date-fns/locale"
import { Badge } from "@/components/ui/badge"
export default async function PaymentsPage() {
const supabase = await createClient()
const { data: payments } = await supabase
.from('payments')
.select(`
id,
amount,
created_at,
payment_method,
payment_type,
status,
reservations (
id,
start_time,
status,
customers (full_name)
)
`)
.order('created_at', { ascending: false })
const getStatusBadge = (status: string) => {
switch (status) {
case 'paid': return <Badge className="bg-green-600">Ödendi</Badge>
case 'pending': return <Badge variant="secondary">Bekliyor</Badge>
case 'refunded': return <Badge variant="destructive">İade</Badge>
case 'confirmed': return <Badge className="bg-blue-600">Onaylandı</Badge>
case 'cancelled': return <Badge variant="destructive">İptal</Badge>
case 'completed': return <Badge variant="outline">Tamamlandı</Badge>
default: return <Badge>{status}</Badge>
}
}
const getCustomerName = (reservation: any) => {
if (!reservation) return "-"
const res = Array.isArray(reservation) ? reservation[0] : reservation
const customer = res?.customers
const cust = Array.isArray(customer) ? customer[0] : customer
return cust?.full_name || "-"
}
const getReservationDate = (reservation: any) => {
if (!reservation) return "-"
const res = Array.isArray(reservation) ? reservation[0] : reservation
return res?.start_time ? format(new Date(res.start_time), 'd MMM yyyy', { locale: tr }) : "-"
}
const getReservationStatus = (reservation: any) => {
if (!reservation) return "-"
const res = Array.isArray(reservation) ? reservation[0] : reservation
return res?.status || "-"
}
return (
<div className="space-y-6">
<div>
<h2 className="text-3xl font-bold tracking-tight">Ödemeler</h2>
<p className="text-muted-foreground">Tüm alınan ödemelerin listesi.</p>
</div>
<div className="rounded-md border bg-card">
<Table>
<TableHeader>
<TableRow>
<TableHead>Tarih</TableHead>
<TableHead>Müşteri</TableHead>
<TableHead>Rez. Tarihi</TableHead>
<TableHead>Tutar</TableHead>
<TableHead>Ödeme Türü</TableHead>
<TableHead>Ödeme Yöntemi</TableHead>
<TableHead>Ödeme Durumu</TableHead>
<TableHead>Rez. Durumu</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{payments?.length === 0 ? (
<TableRow>
<TableCell colSpan={8} className="text-center h-24 text-muted-foreground">
Henüz ödeme kaydı yok.
</TableCell>
</TableRow>
) : (
payments?.map((payment: any) => (
<TableRow key={payment.id}>
<TableCell className="font-medium">
{format(new Date(payment.created_at), 'd MMMM yyyy HH:mm', { locale: tr })}
</TableCell>
<TableCell>{getCustomerName(payment.reservations)}</TableCell>
<TableCell>{getReservationDate(payment.reservations)}</TableCell>
<TableCell>{Number(payment.amount).toLocaleString('tr-TR')}</TableCell>
<TableCell className="capitalize">
{payment.payment_type === 'deposit' ? 'Kapora' :
payment.payment_type === 'full' ? 'Tam Ödeme' :
payment.payment_type === 'remaining' ? 'Kalan' : payment.payment_type}
</TableCell>
<TableCell className="capitalize">{payment.payment_method === 'credit_card' ? 'Kredi Kartı' : payment.payment_method === 'cash' ? 'Nakit' : 'Havale/EFT'}</TableCell>
<TableCell>{getStatusBadge(payment.status)}</TableCell>
<TableCell>{getStatusBadge(getReservationStatus(payment.reservations))}</TableCell>
</TableRow>
))
)}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3} className="font-bold text-right">Toplam:</TableCell>
<TableCell className="font-bold">
{payments?.reduce((sum: number, p: any) => sum + Number(p.amount), 0).toLocaleString('tr-TR')}
</TableCell>
<TableCell colSpan={4}></TableCell>
</TableRow>
</TableFooter>
</Table>
</div>
</div>
)
}

View File

@@ -29,7 +29,7 @@ export default async function ReservationsPage() {
customers (full_name), customers (full_name),
packages (name, price) packages (name, price)
`) `)
.order('start_time', { ascending: true }) .order('created_at', { ascending: false })
const getStatusBadge = (status: string) => { const getStatusBadge = (status: string) => {
switch (status) { switch (status) {