feat: Implement dashboard navigation, payments list, and deployment docs
This commit is contained in:
51
COOLIFY.md
Normal file
51
COOLIFY.md
Normal 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.
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
@@ -83,6 +84,16 @@ export default async function ExpensesPage() {
|
||||
))
|
||||
)}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,39 +72,43 @@ export default async function DashboardPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<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-2xl 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>
|
||||
<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">
|
||||
<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-2xl 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>
|
||||
</Link>
|
||||
|
||||
<Card className="card-hover border-l-4 border-l-red-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 Gider
|
||||
</CardTitle>
|
||||
<div className="h-8 w-8 rounded-full bg-red-100 dark:bg-red-900/20 flex items-center justify-center">
|
||||
<TrendingUp className="h-4 w-4 text-red-600 dark:text-red-400 rotate-180" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold mt-2">₺{totalExpenses.toLocaleString('tr-TR')}</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 flex items-center">
|
||||
İşletme giderleri
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<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">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
Toplam Gider
|
||||
</CardTitle>
|
||||
<div className="h-8 w-8 rounded-full bg-red-100 dark:bg-red-900/20 flex items-center justify-center">
|
||||
<TrendingUp className="h-4 w-4 text-red-600 dark:text-red-400 rotate-180" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold mt-2">₺{totalExpenses.toLocaleString('tr-TR')}</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 flex items-center">
|
||||
İşletme giderleri
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
<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">
|
||||
@@ -123,22 +127,24 @@ export default async function DashboardPage() {
|
||||
</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 Rezervasyon
|
||||
</CardTitle>
|
||||
<div className="h-8 w-8 rounded-full bg-purple-100 dark:bg-purple-900/20 flex items-center justify-center">
|
||||
<CalendarDays className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold mt-2">{totalReservations || 0}</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 flex items-center">
|
||||
Aktif rezervasyonlar
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<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">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
Toplam Rezervasyon
|
||||
</CardTitle>
|
||||
<div className="h-8 w-8 rounded-full bg-purple-100 dark:bg-purple-900/20 flex items-center justify-center">
|
||||
<CalendarDays className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold mt-2">{totalReservations || 0}</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 flex items-center">
|
||||
Aktif rezervasyonlar
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-7">
|
||||
|
||||
131
src/app/dashboard/payments/page.tsx
Normal file
131
src/app/dashboard/payments/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export default async function ReservationsPage() {
|
||||
customers (full_name),
|
||||
packages (name, price)
|
||||
`)
|
||||
.order('start_time', { ascending: true })
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
|
||||
Reference in New Issue
Block a user