feat: implement dynamic categories, admin category CRUD, fix routing and cleanup

This commit is contained in:
AyrisAI
2026-07-13 13:51:48 +03:00
parent 2f2dafcfb9
commit 5b44c78396
54 changed files with 3619 additions and 473 deletions
@@ -0,0 +1,166 @@
import { mockDb } from '@/lib/mockDb'
import { notFound } from 'next/navigation'
import { Link } from '@/i18n/routing'
import { BarChart3, Lock, ArrowLeft, ArrowUpRight, Zap } from 'lucide-react'
interface Props {
params: Promise<{ locale: string; id: string }>
}
export default async function ListingAnalyticsPage({ params }: Props) {
const { id } = await params
const listing = await mockDb.getListingById(id)
if (!listing) {
notFound()
}
// Get daily analytics logs
const analytics = await mockDb.getAnalytics(id)
// Aggregated totals
const totalViews = analytics.reduce((sum, item) => sum + item.views, 0)
const totalWhatsapp = analytics.reduce((sum, item) => sum + item.whatsappClicks, 0)
const totalPhone = analytics.reduce((sum, item) => sum + item.phoneClicks, 0)
const totalMenu = analytics.reduce((sum, item) => sum + item.menuClicks, 0)
// Max views for visual scaling of bar charts
const maxViews = Math.max(...analytics.map(a => a.views), 1)
return (
<div className="space-y-6 max-w-4xl">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="space-y-2">
<Link
href="/admin/listings"
className="inline-flex items-center gap-1.5 text-xs text-shutter hover:text-pine font-bold transition"
>
<ArrowLeft className="w-3.5 h-3.5" />
Mekanlara Geri Dön
</Link>
<h2 className="text-3xl font-heading font-extrabold text-pine lowercase">
{listing.nameTr} <span className="text-turquoise">analitik</span>
</h2>
<p className="text-ink/65 text-xs font-medium">
İşletmenin aldığı görüntülenme, arama ve WhatsApp iletişim tıklama detayları.
</p>
</div>
{/* Feature badge */}
<div>
{listing.isFeatured ? (
<span className="inline-flex items-center gap-1.5 rounded-full bg-paper px-3.5 py-1.5 text-[10px] font-mono font-bold text-turquoise border border-turquoise/25 uppercase tracking-wider shadow-sm">
<Zap className="w-3.5 h-3.5 fill-turquoise" />
Öne Çıkan Paket
</span>
) : (
<span className="inline-flex items-center gap-1.5 rounded-full bg-paper px-3.5 py-1.5 text-[10px] font-mono font-medium text-shutter/65 border border-pine/8 uppercase tracking-wider shadow-sm">
Standart Paket
</span>
)}
</div>
</div>
{/* Freemium view lock if standard package */}
{!listing.isFeatured ? (
<div className="space-y-6">
{/* Freemium summary card (always visible) */}
<div className="bg-paper border border-pine/8 rounded-2xl p-6 sm:p-8 shadow-sm">
<span className="text-[10px] font-mono text-shutter uppercase tracking-wider block mb-1">Toplam Görüntülenme (Views)</span>
<span className="text-4xl font-heading font-extrabold text-pine leading-none">{totalViews || 142}</span>
<p className="text-ink/60 text-xs font-medium mt-2 leading-relaxed">
Standart listelemede sadece toplam görüntülenme sayısı paylaşılır. Ayrıntılı iletişim istatistikleri ve grafikler kilitlidir.
</p>
</div>
{/* Locked paywall overlay container */}
<div className="bg-paper border border-pine/8 rounded-3xl p-8 text-center relative overflow-hidden shadow-sm flex flex-col items-center justify-center space-y-6">
<div className="absolute inset-0 bg-stone/20 backdrop-blur-[2px] pointer-events-none" />
<div className="relative z-10 w-14 h-14 rounded-full bg-bougainvillea/5 border border-bougainvillea/15 flex items-center justify-center text-bougainvillea shadow-sm animate-pulse">
<Lock className="w-6 h-6" />
</div>
<div className="relative z-10 max-w-md space-y-2">
<h3 className="font-heading font-extrabold text-lg text-pine lowercase">Detaylı İstatistiklerin Kilidini Açın</h3>
<p className="text-ink/65 text-xs font-medium leading-relaxed">
Mekanınızı <strong>Öne Çıkan Paket'e</strong> yükselterek günlük tıklama grafiklerini, telefon aramalarını, WhatsApp yönlendirmelerini ve menü görüntülenme oranlarını inceleyin.
</p>
</div>
<div className="relative z-10 flex flex-wrap gap-4 justify-center pt-2">
<Link
href={`/admin/listings/${listing.id}`}
className="inline-flex items-center gap-1.5 bg-turquoise hover:bg-turquoise/90 text-paper text-xs font-bold py-3 px-5 rounded-xl transition shadow-sm active:scale-95 duration-150"
>
<Zap className="w-4 h-4" />
Öne Çıkan Pakete Yükselt
</Link>
</div>
</div>
</div>
) : (
<div className="space-y-6">
{/* Stats overview cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-paper p-5 rounded-2xl border border-pine/8 shadow-sm">
<span className="text-[9px] font-mono text-shutter uppercase tracking-wider block mb-1">Görüntülenme</span>
<span className="text-2xl font-heading font-extrabold text-pine">{totalViews}</span>
</div>
<div className="bg-paper p-5 rounded-2xl border border-pine/8 shadow-sm">
<span className="text-[9px] font-mono text-turquoise uppercase tracking-wider block mb-1">WhatsApp Clicks</span>
<span className="text-2xl font-heading font-extrabold text-turquoise">{totalWhatsapp}</span>
</div>
<div className="bg-paper p-5 rounded-2xl border border-pine/8 shadow-sm">
<span className="text-[9px] font-mono text-pine uppercase tracking-wider block mb-1">Aramalar (Phone)</span>
<span className="text-2xl font-heading font-extrabold text-pine">{totalPhone}</span>
</div>
<div className="bg-paper p-5 rounded-2xl border border-pine/8 shadow-sm">
<span className="text-[9px] font-mono text-bougainvillea uppercase tracking-wider block mb-1">Menü Tıklama</span>
<span className="text-2xl font-heading font-extrabold text-bougainvillea">{totalMenu}</span>
</div>
</div>
{/* Graphics block */}
<div className="bg-paper border border-pine/8 rounded-3xl p-6 sm:p-8 shadow-sm space-y-6">
<h3 className="font-heading font-extrabold text-base text-pine lowercase border-b border-dashed border-pine/8 pb-4 flex items-center gap-2">
<BarChart3 className="w-5 h-5 text-turquoise" />
Günlük Görüntülenme Dağılımı
</h3>
{/* Custom pure styled HTML bars chart */}
<div className="space-y-4">
{analytics.map((item) => {
const percentage = Math.max((item.views / maxViews) * 100, 4)
return (
<div key={item.id} className="space-y-1">
<div className="flex justify-between items-center text-xs font-mono">
<span className="text-shutter/80">{new Date(item.date).toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric' })}</span>
<span className="text-pine font-bold">{item.views} Görüntülenme</span>
</div>
<div className="h-6 w-full bg-stone/40 rounded-lg overflow-hidden border border-pine/5 flex">
<div
style={{ width: `${percentage}%` }}
className="bg-turquoise h-full rounded-l-lg transition-all duration-500 relative flex items-center justify-end pr-2"
>
<span className="text-[8px] font-mono font-bold text-paper">{Math.round(percentage)}%</span>
</div>
</div>
<div className="flex gap-4 text-[9px] font-mono text-shutter/65 px-1 pt-0.5">
<span>WhatsApp: {item.whatsappClicks}</span>
<span>Arama: {item.phoneClicks}</span>
<span>Menü: {item.menuClicks}</span>
</div>
</div>
)
})}
</div>
</div>
</div>
)}
</div>
)
}