import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { ChevronRight } from "lucide-react"; import { getAllUniversiteler, getAllUniversiteSlugs, getUniversiteBySlug, bolumSlugFromIsim, } from "../katalog-queries"; import { trBaslikDuzeni } from "@/lib/slug"; import { JsonLd, breadcrumbJsonLd, sayfaMetadata, SITE_URL, ustSegmentOgGorseli, } from "@/lib/seo"; import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form"; import { SiteFooter } from "@/components/site-footer"; import { KATALOG_CTA_ACIKLAMA, Sayfalama, SAYFA_BOYU, sayfaDilimi, siraYiliEtiketi, } from "./sayfalama"; import { PagePixelDivider } from "@/components/pixel-decor"; import { UniversiteKonumHaritasi } from "./universite-konum-haritasi"; import { uniUlkeKonum } from "@/lib/harita-ulkeler"; import { UniversiteProgramTablosu } from "./universite-program-tablosu"; import { VeriKaynagi } from "./veri-kaynagi"; import { UniLogo } from "@/components/uni-logo"; import { uniLogoYolu } from "@/lib/uni-logolar"; import { Skeleton } from "@/components/ui/skeleton"; import type { Program } from "@/types/yokatlas"; const sayi = (n: number) => n.toLocaleString("tr-TR"); export function uniToplamSayfa(programSayisi: number): number { return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU)); } // Sayfaların generateStaticParams'ı için — sayfalar *-queries import etmez. export function uniStaticSlugs(): string[] { return getAllUniversiteSlugs(); } // 2..N sayfaları; 1. sayfa taban rota (/universite/[slug]) olduğundan üretilmez. export function uniSayfaStaticParams(): { slug: string; no: string }[] { return getAllUniversiteler().flatMap((u) => { const toplam = uniToplamSayfa(u.programSayisi); return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({ slug: u.slug, no: String(i + 2), })); }); } type UniDetay = NonNullable>; /** * Sayfanın satır dilimi + o dilimde geçen fakülte/birim adları (tablo * sırasıyla, tekrarsız). Liste fakülteye göre dizili olduğundan her derin * sayfa belirli birimleri kapsar. Metadata ve gövde aynı hesabı buradan alır. * Fakültesi boş satırlar tabloda "Diğer" grubuna düşer; ad listesine girmez. */ function uniSayfaDilimi(uni: UniDetay, sayfa: number) { // Sayfalama fakülte sıralı düz liste üzerinden; dilim içi yeniden gruplanır const tumProgramlar = uni.fakulteler.flatMap((f) => f.programlar); const dilim = sayfaDilimi(tumProgramlar, sayfa); const fakulteAdlari: string[] = []; for (const p of dilim) { if (p.fakulte && !fakulteAdlari.includes(p.fakulte)) { fakulteAdlari.push(p.fakulte); } } return { tumProgramlar, dilim, fakulteAdlari }; } /** * İlk 2 ad + "ve N birim daha"; ad yoksa null (çağıran eski metne düşer). * Dilim tek birimden ibaretse (50'den çok programlı MYO'lar art arda iki * sayfayı doldurabiliyor) sayfalar ayırt edilsin diye birim içi taban sırası * aralığı eklenir — birim içinde satırlar zaten taban sırasına göre dizili. */ function fakulteOzeti({ dilim, fakulteAdlari: adlar, }: ReturnType): string | null { if (adlar.length === 0) return null; if (adlar.length === 1) { const siralar = dilim .map((p) => p.sira2025 ?? p.sira2024) .filter((n): n is number => n != null); if (siralar.length === 0) return adlar[0]; const min = Math.min(...siralar); const max = Math.max(...siralar); const yil = siraYiliEtiketi( dilim.filter((p) => p.sira2025 == null && p.sira2024 != null).length, siralar.length, ); return min === max ? `${adlar[0]} (${yil} taban sırası ${sayi(min)})` : `${adlar[0]} (${yil} taban sırası ${sayi(min)}–${sayi(max)} arası)`; } if (adlar.length === 2) return `${adlar[0]} ve ${adlar[1]}`; return `${adlar[0]}, ${adlar[1]} ve ${adlar.length - 2} birim daha`; } export function uniSayfaMetadata(slug: string, sayfa: number): Metadata { const uni = getUniversiteBySlug(slug); if (!uni) return {}; if (sayfa > 1) { // Üniversite adları uzun: sayfa numarasıyla kalır (~60 karakter), // farklılaştırıcı (o sayfadaki birimler) açıklamaya ve gövdeye gider. const birimler = fakulteOzeti(uniSayfaDilimi(uni, sayfa)); return sayfaMetadata({ title: `${uni.ad} Taban Puanları 2026 (Sayfa ${sayfa})`, description: birimler ? `${uni.ad} taban puanları, sayfa ${sayfa}: ${birimler}. Toplam ${uni.programSayisi} programın 2025 yerleştirme verileri — taban puanı, başarı sıralaması ve kontenjan.` : `${uni.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${uni.programSayisi} programın 2025 yerleştirme verileri.`, path: `/universite/${slug}/sayfa/${sayfa}`, openGraph: { images: ustSegmentOgGorseli( `/universite/${slug}`, "Üniversite taban puanları ve sıralamaları — KolayTercih", ), }, }); } return sayfaMetadata({ title: `${uni.ad} Taban Puanları ve Başarı Sıralamaları 2026`, description: `${uni.ad} 2025 yerleştirme verileri: ${uni.programSayisi} programın taban puanları, başarı sıralamaları ve kontenjanları${uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}. 2026 tercih rehberi.`, path: `/universite/${slug}`, }); } export function UniversiteIcerik({ slug, sayfa, }: { slug: string; sayfa: number; }) { const uni = getUniversiteBySlug(slug); if (!uni) notFound(); const sayfaDilim = uniSayfaDilimi(uni, sayfa); const { tumProgramlar, dilim } = sayfaDilim; const toplamSayfa = uniToplamSayfa(tumProgramlar.length); if (sayfa < 1 || sayfa > toplamSayfa) notFound(); const birimler = fakulteOzeti(sayfaDilim); const gruplar: { fakulte: string; programlar: (Program & { bolumSlug: string | null })[]; }[] = []; for (const p of dilim) { const anahtar = p.fakulte ?? "Diğer"; const satir = { ...p, bolumSlug: bolumSlugFromIsim(p.isim) }; const son = gruplar[gruplar.length - 1]; if (son && son.fakulte === anahtar) son.programlar.push(satir); else gruplar.push({ fakulte: anahtar, programlar: [satir] }); } const ilkSayfa = sayfa === 1; const buYol = ilkSayfa ? `/universite/${slug}` : `/universite/${slug}/sayfa/${sayfa}`; const yurtdisi = uniUlkeKonum(uni.ad, uni.il); return ( <> <main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16"> <JsonLd data={breadcrumbJsonLd([ { name: "Ana Sayfa", path: "/" }, { name: "Üniversiteler", path: "/universiteler" }, { name: uni.ad, path: `/universite/${slug}` }, ...(ilkSayfa ? [] : [{ name: `Sayfa ${sayfa}`, path: buYol }]), ])} /> {ilkSayfa ? ( <JsonLd data={{ "@context": "https://schema.org", "@type": "CollegeOrUniversity", name: uni.ad, url: `${SITE_URL}/universite/${slug}`, ...(uniLogoYolu(slug) ? { logo: `${SITE_URL}${uniLogoYolu(slug)}` } : {}), ...(uni.il || yurtdisi ? { address: { "@type": "PostalAddress", ...(yurtdisi?.sehir ? { addressLocality: yurtdisi.sehir } : uni.il ? { addressLocality: trBaslikDuzeni(uni.il) } : {}), addressCountry: yurtdisi ? yurtdisi.iso : "TR", }, } : {}), }} /> ) : null} <nav aria-label="breadcrumb" className="flex items-center gap-1 text-xs text-slate-500" > <Link href="/" className="hover:text-primary hover:underline"> Ana Sayfa </Link> <ChevronRight className="size-3" aria-hidden /> <Link href="/universiteler" className="hover:text-primary hover:underline" > Üniversiteler </Link> <ChevronRight className="size-3" aria-hidden /> {ilkSayfa ? ( <span className="text-slate-700">{uni.ad}</span> ) : ( <> <Link href={`/universite/${slug}`} className="hover:text-primary hover:underline" > {uni.ad} </Link> <ChevronRight className="size-3" aria-hidden /> <span className="text-slate-700">Sayfa {sayfa}</span> </> )} </nav> <div className="mt-4 flex items-center gap-4"> <UniLogo slug={slug} ad={uni.ad} boy="lg" /> <h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl"> {uni.ad} Taban Puanları 2026 {ilkSayfa ? "" : ` — Sayfa ${sayfa}`} </h1> </div> {ilkSayfa ? ( <> <p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600"> {uni.ad} {uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}, 2025 YKS yerleştirme sonuçlarına göre{" "} <strong>{sayi(uni.programSayisi)} programda</strong> öğrenci aldı {uni.fakulteSayisi > 1 ? `; programlar ${sayi(uni.fakulteSayisi)} fakülte/yüksekokula dağılıyor` : ""} . {uni.stats.minSira != null ? ( <> {" "} En iyi taban başarı sıralaması{" "} <strong>{sayi(uni.stats.minSira)}</strong>. </> ) : null} {uni.stats.toplamKontenjan > 0 ? ( <> Toplam kontenjan {sayi(uni.stats.toplamKontenjan)}.</> ) : null}{" "} Aşağıdaki tablolar 2026 tercihleri için rehber niteliğindedir;{" "} <em> 2026 taban puanları ve sıralamaları, yerleştirme sonuçları açıklandığında netleşir. </em> </p> <div className="mt-6 grid grid-cols-2 gap-3 lg:grid-cols-[1fr_1fr_1.35fr] lg:grid-rows-2"> <UniversiteKonumHaritasi universite={uni.ad} il={uni.il} /> <dl className="contents"> <div className="rounded-xl border border-slate-200 bg-white p-4"> <dt className="text-xs font-medium uppercase tracking-wide text-slate-500"> Program </dt> <dd className="mt-1 font-heading text-2xl font-bold text-slate-900"> {sayi(uni.programSayisi)} </dd> </div> <div className="rounded-xl border border-slate-200 bg-white p-4"> <dt className="text-xs font-medium uppercase tracking-wide text-slate-500"> Fakülte / Birim </dt> <dd className="mt-1 font-heading text-2xl font-bold text-slate-900"> {sayi(uni.fakulteSayisi)} </dd> </div> <div className="rounded-xl border border-slate-200 bg-white p-4"> <dt className="text-xs font-medium uppercase tracking-wide text-slate-500"> Tür </dt> <dd className="mt-1 font-heading text-2xl font-bold text-slate-900"> {uni.unitur === "DEVLET" ? "Devlet" : uni.unitur ? trBaslikDuzeni(uni.unitur) : "—"} </dd> </div> <div className="rounded-xl border border-slate-200 bg-white p-4"> <dt className="text-xs font-medium uppercase tracking-wide text-slate-500"> En iyi sıralama </dt> <dd className="mt-1 font-heading text-2xl font-bold text-slate-900"> {uni.stats.minSira != null ? sayi(uni.stats.minSira) : "—"} </dd> </div> </dl> </div> </> ) : ( <p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600"> {uni.ad} taban puanları tablosunun {sayfa}. sayfası ( {sayi(tumProgramlar.length)} programın{" "} {sayi((sayfa - 1) * SAYFA_BOYU + 1)}– {sayi(Math.min(sayfa * SAYFA_BOYU, tumProgramlar.length))}. satırları, fakülte sırasıyla). {birimler ? ` Bu sayfadaki birimler: ${birimler}.` : ""} Üniversite özeti ve istatistikler{" "} <Link href={`/universite/${slug}`} className="font-medium text-primary hover:underline" > ilk sayfada </Link> . </p> )} <PagePixelDivider seed={57} className="mt-8" /> <div id="programlar" className="mt-8"> <h2 className="font-heading text-xl font-bold text-slate-900"> Üniversite programları </h2> <p className="mt-1 text-xs text-slate-500"> Fakülte ve birimlere göre </p> </div> <UniversiteProgramTablosu gruplar={gruplar} /> <VeriKaynagi> "—": o yıl için yerleştirme verisi bulunmuyor (yeni açılan veya o yıl yerleşen çıkmayan program).{" "} </VeriKaynagi> <Sayfalama tabanYol={`/universite/${slug}`} sayfa={sayfa} toplamSayfa={toplamSayfa} /> <div className="mt-10"> <CtaSiraForm baslik={`Sıralaman ${uni.ad} tabanlarının neresinde?`} aciklama={KATALOG_CTA_ACIKLAMA} /> </div> </main> <SiteFooter /> </> ); } export function UniversiteIcerikSkeleton() { return ( <main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16"> <Skeleton className="h-4 w-64" /> <div className="mt-4 flex items-center gap-4"> <Skeleton className="size-14 rounded-lg" /> <Skeleton className="h-10 w-2/3 max-w-xl" /> </div> <Skeleton className="mt-4 h-5 w-full max-w-3xl" /> <div className="mt-6 grid grid-cols-2 gap-3 lg:grid-cols-[1fr_1fr_1.35fr] lg:grid-rows-2"> {Array.from({ length: 5 }, (_, i) => ( <Skeleton key={i} className="h-24 w-full rounded-xl" /> ))} </div> <div className="mt-16 space-y-3"> <Skeleton className="h-7 w-64" /> {Array.from({ length: 5 }, (_, i) => ( <Skeleton key={i} className="h-12 w-full" /> ))} </div> </main> ); }