refactor: katalog feature'ı — sayfalar senkron kompozisyona geçti (Faz 2)
- 8 katalog bileşeni src/features/katalog/components/ altına taşındı; katalog-queries.ts (server-only) lib/katalog'un feature kapısı oldu - /bolumler ve /universiteler'in sayfa içi fetch+inline bileşenleri BolumKatalogu/UniversiteKatalogu feature bileşenlerine çıkarıldı - 6 sayfa async'ten senkron'a: params.then() + sayfada Suspense, skeleton'lar feature bileşeninin yanında (BolumIcerikSkeleton vb.) - generateStaticParams artık *-queries yerine bileşen dosyasının helper'larını kullanıyor (bolumStaticSlugs, uniSayfaStaticParams) - Davranış değişikliği yok; route durumları aynı (◐), 1917 statik sayfa Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,125 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { getBolumBySlug, uniSlugFromAd } from "@/lib/katalog";
|
||||
import { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||
import { SAYFA_BOYU } from "@/components/sayfalama";
|
||||
import { BolumProgramListesi } from "@/components/bolum-program-listesi";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
|
||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||
|
||||
export function bolumToplamSayfa(programSayisi: number): number {
|
||||
return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU));
|
||||
}
|
||||
|
||||
export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
|
||||
const bolum = getBolumBySlug(slug);
|
||||
if (!bolum) return {};
|
||||
const { stats } = bolum;
|
||||
if (sayfa > 1) {
|
||||
return {
|
||||
title: `${bolum.ad} Taban Puanları 2026 (Sayfa ${sayfa})`,
|
||||
description: `${bolum.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${stats.toplam} programın 2025 yerleştirme verileri.`,
|
||||
alternates: { canonical: `/bolum/${slug}/sayfa/${sayfa}` },
|
||||
};
|
||||
}
|
||||
const enIyi =
|
||||
stats.minSira != null
|
||||
? ` En iyi taban sıralaması ${sayi(stats.minSira)}.`
|
||||
: "";
|
||||
return {
|
||||
title: `${bolum.ad} Taban Puanları ve Başarı Sıralamaları 2026`,
|
||||
description: `${bolum.ad} 2025 yerleştirme verileri: ${stats.toplam} programın taban puanı ve sıralaması, ${stats.devlet} devlet / ${stats.vakif} vakıf.${enIyi} 2026 tercih rehberi.`,
|
||||
alternates: { canonical: `/bolum/${slug}` },
|
||||
};
|
||||
}
|
||||
|
||||
export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
const bolum = getBolumBySlug(slug);
|
||||
if (!bolum) notFound();
|
||||
|
||||
const tumProgramlar = [
|
||||
...bolum.lisansProgramlari,
|
||||
...bolum.onlisansProgramlari,
|
||||
];
|
||||
const toplamSayfa = bolumToplamSayfa(tumProgramlar.length);
|
||||
if (sayfa < 1 || sayfa > toplamSayfa) notFound();
|
||||
const ilkSayfa = sayfa === 1;
|
||||
const buYol = ilkSayfa ? `/bolum/${slug}` : `/bolum/${slug}/sayfa/${sayfa}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="mx-auto w-full max-w-5xl px-4 py-10 sm:py-14">
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: "Ana Sayfa", path: "/" },
|
||||
{ name: "Bölümler", path: "/bolumler" },
|
||||
{ name: bolum.ad, path: `/bolum/${slug}` },
|
||||
...(ilkSayfa ? [] : [{ name: `Sayfa ${sayfa}`, path: buYol }]),
|
||||
])}
|
||||
/>
|
||||
|
||||
<nav
|
||||
aria-label="İçerik yolu"
|
||||
className="mb-5 flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground"
|
||||
>
|
||||
<Link
|
||||
href="/"
|
||||
className="transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
Ana sayfa
|
||||
</Link>
|
||||
<ChevronRight className="size-3.5" aria-hidden />
|
||||
<Link
|
||||
href="/bolumler"
|
||||
className="transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
Bölümler
|
||||
</Link>
|
||||
<ChevronRight className="size-3.5" aria-hidden />
|
||||
<span className="text-foreground">
|
||||
{ilkSayfa ? bolum.ad : `Sayfa ${sayfa}`}
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
<header className="border-b pb-7 sm:pb-9">
|
||||
<h1 className="max-w-3xl font-heading text-3xl font-bold tracking-tight text-foreground sm:text-5xl">
|
||||
{bolum.ad}
|
||||
</h1>
|
||||
<p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground sm:text-base">
|
||||
{sayi(tumProgramlar.length)} üniversite programının 2025 taban
|
||||
puanlarını, başarı sıralamalarını ve kontenjanlarını incele
|
||||
{!ilkSayfa ? ` — sayfa ${sayfa}` : ""}.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="universite-programlari" className="mt-8">
|
||||
<div className="mb-4 flex items-end justify-between gap-4">
|
||||
<h2
|
||||
id="universite-programlari"
|
||||
className="font-heading text-xl font-bold text-foreground sm:text-2xl"
|
||||
>
|
||||
Üniversite programları
|
||||
</h2>
|
||||
<span className="shrink-0 text-sm text-muted-foreground">
|
||||
{sayi(tumProgramlar.length)} program
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<BolumProgramListesi
|
||||
programlar={tumProgramlar.map((program) => ({
|
||||
...program,
|
||||
universiteSlug: uniSlugFromAd(program.universite),
|
||||
}))}
|
||||
bazAd={bolum.ad}
|
||||
tabanYol={`/bolum/${slug}`}
|
||||
sayfa={sayfa}
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +1,32 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { getAllBolumSlugs } from "@/lib/katalog";
|
||||
import { BolumIcerik, bolumSayfaMetadata } from "./bolum-icerik";
|
||||
import {
|
||||
BolumIcerik,
|
||||
BolumIcerikSkeleton,
|
||||
bolumSayfaMetadata,
|
||||
bolumStaticSlugs,
|
||||
} from "@/features/katalog/components/bolum-icerik";
|
||||
|
||||
// Tüm bölümler build sırasında bilinir (veri ancak ingest + deploy ile
|
||||
// değişir); bilinmeyen slug getBolumBySlug'dan null dönüp notFound()'a düşer.
|
||||
// (dynamicParams cacheComponents ile uyumsuz olduğundan kullanılamıyor.)
|
||||
export function generateStaticParams() {
|
||||
return getAllBolumSlugs().map((slug) => ({ slug }));
|
||||
return bolumStaticSlugs().map((slug) => ({ slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}): Promise<Metadata> {
|
||||
}: PageProps<"/bolum/[slug]">): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
return bolumSayfaMetadata(slug, 1);
|
||||
}
|
||||
|
||||
export default async function BolumPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
return <BolumIcerik slug={slug} sayfa={1} />;
|
||||
export default function BolumPage({ params }: PageProps<"/bolum/[slug]">) {
|
||||
return (
|
||||
<Suspense fallback={<BolumIcerikSkeleton />}>
|
||||
{params.then(({ slug }) => (
|
||||
<BolumIcerik slug={slug} sayfa={1} />
|
||||
))}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getAllBolumler } from "@/lib/katalog";
|
||||
import {
|
||||
BolumIcerik,
|
||||
BolumIcerikSkeleton,
|
||||
bolumSayfaMetadata,
|
||||
bolumToplamSayfa,
|
||||
} from "../../bolum-icerik";
|
||||
bolumSayfaStaticParams,
|
||||
} from "@/features/katalog/components/bolum-icerik";
|
||||
|
||||
// Yalnızca 1'den fazla sayfası olan bölümler için 2..N sayfaları üretilir;
|
||||
// 1. sayfa taban rota (/bolum/[slug]) olduğundan burada üretilmez.
|
||||
export function generateStaticParams() {
|
||||
return getAllBolumler().flatMap((b) => {
|
||||
const toplam = bolumToplamSayfa(b.programSayisi);
|
||||
return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({
|
||||
slug: b.slug,
|
||||
no: String(i + 2),
|
||||
}));
|
||||
});
|
||||
return bolumSayfaStaticParams();
|
||||
}
|
||||
|
||||
function sayfaNo(no: string): number | null {
|
||||
@@ -26,22 +21,23 @@ function sayfaNo(no: string): number | null {
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string; no: string }>;
|
||||
}): Promise<Metadata> {
|
||||
}: PageProps<"/bolum/[slug]/sayfa/[no]">): Promise<Metadata> {
|
||||
const { slug, no } = await params;
|
||||
const n = sayfaNo(no);
|
||||
if (n == null) return {};
|
||||
return bolumSayfaMetadata(slug, n);
|
||||
}
|
||||
|
||||
export default async function BolumSayfaPage({
|
||||
export default function BolumSayfaPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string; no: string }>;
|
||||
}) {
|
||||
const { slug, no } = await params;
|
||||
const n = sayfaNo(no);
|
||||
if (n == null) notFound();
|
||||
return <BolumIcerik slug={slug} sayfa={n} />;
|
||||
}: PageProps<"/bolum/[slug]/sayfa/[no]">) {
|
||||
return (
|
||||
<Suspense fallback={<BolumIcerikSkeleton />}>
|
||||
{params.then(({ slug, no }) => {
|
||||
const n = sayfaNo(no);
|
||||
if (n == null) notFound();
|
||||
return <BolumIcerik slug={slug} sayfa={n} />;
|
||||
})}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { getAllBolumler, type BolumOzet } from "@/lib/katalog";
|
||||
import { KATEGORILER } from "@/lib/kategoriler";
|
||||
import { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||
import { CtaSiraForm } from "@/components/cta-sira-form";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import {
|
||||
BolumKatalogu,
|
||||
BolumKataloguSkeleton,
|
||||
} from "@/features/katalog/components/bolum-listesi";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026",
|
||||
@@ -14,68 +14,7 @@ export const metadata: Metadata = {
|
||||
alternates: { canonical: "/bolumler" },
|
||||
};
|
||||
|
||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||
|
||||
function BolumLinkleri({ bolumler }: { bolumler: BolumOzet[] }) {
|
||||
return (
|
||||
<ul className="mt-3 grid gap-x-6 gap-y-1.5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{bolumler.map((b) => (
|
||||
<li key={b.slug}>
|
||||
<Link
|
||||
href={`/bolum/${b.slug}`}
|
||||
className="group inline-flex items-baseline gap-2 text-sm text-slate-700 hover:text-primary"
|
||||
>
|
||||
<span className="group-hover:underline">{b.ad}</span>
|
||||
<span className="text-xs text-slate-400">
|
||||
{sayi(b.programSayisi)}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function KategoriGruplari({
|
||||
bolumler,
|
||||
seviye,
|
||||
}: {
|
||||
bolumler: BolumOzet[];
|
||||
seviye: "lisans" | "onlisans";
|
||||
}) {
|
||||
const kalanlar = new Set(bolumler.map((b) => b.slug));
|
||||
const gruplar = KATEGORILER.map((kategori) => {
|
||||
const uyeler = bolumler.filter((b) =>
|
||||
(b.kategoriler as readonly string[]).includes(kategori),
|
||||
);
|
||||
for (const b of uyeler) kalanlar.delete(b.slug);
|
||||
return { kategori: kategori as string, uyeler };
|
||||
}).filter((g) => g.uyeler.length > 0);
|
||||
const diger = bolumler.filter((b) => kalanlar.has(b.slug));
|
||||
if (diger.length > 0) gruplar.push({ kategori: "Diğer", uyeler: diger });
|
||||
|
||||
return (
|
||||
<>
|
||||
{gruplar.map(({ kategori, uyeler }) => (
|
||||
<section key={`${seviye}-${kategori}`} className="mt-8">
|
||||
<h3 className="font-heading text-lg font-bold text-slate-900">
|
||||
{kategori}
|
||||
<span className="ml-2 text-xs font-normal text-slate-400">
|
||||
{sayi(uyeler.length)} bölüm
|
||||
</span>
|
||||
</h3>
|
||||
<BolumLinkleri bolumler={uyeler} />
|
||||
</section>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BolumlerPage() {
|
||||
const hepsi = getAllBolumler();
|
||||
const lisans = hepsi.filter((b) => b.lisans);
|
||||
const onlisans = hepsi.filter((b) => b.onlisans && !b.lisans);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
||||
@@ -89,33 +28,10 @@ export default function BolumlerPage() {
|
||||
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
||||
Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026
|
||||
</h1>
|
||||
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
||||
{sayi(hepsi.length)} bölümün 2025 YKS yerleştirme verileri: taban
|
||||
puanları, başarı sıralamaları, kontenjanlar ve devlet/vakıf dağılımı.
|
||||
Bölüme tıklayınca o bölümü sunan tüm üniversitelerin tablosunu
|
||||
görürsün. Veriler YÖK Atlas 2025 yerleştirme sonuçlarına dayanır; 2026
|
||||
tercihleri için rehber niteliğindedir.
|
||||
</p>
|
||||
|
||||
<PagePixelDivider seed={29} className="mt-8" />
|
||||
|
||||
<section className="mt-8">
|
||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||
Lisans Bölümleri (4 Yıllık)
|
||||
</h2>
|
||||
<KategoriGruplari bolumler={lisans} seviye="lisans" />
|
||||
</section>
|
||||
|
||||
<div className="mt-12">
|
||||
<CtaSiraForm />
|
||||
</div>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||
Önlisans Bölümleri (2 Yıllık — TYT)
|
||||
</h2>
|
||||
<KategoriGruplari bolumler={onlisans} seviye="onlisans" />
|
||||
</section>
|
||||
<Suspense fallback={<BolumKataloguSkeleton />}>
|
||||
<BolumKatalogu />
|
||||
</Suspense>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</>
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { getAllUniversiteSlugs } from "@/lib/katalog";
|
||||
import { UniversiteIcerik, uniSayfaMetadata } from "./universite-icerik";
|
||||
import {
|
||||
UniversiteIcerik,
|
||||
UniversiteIcerikSkeleton,
|
||||
uniSayfaMetadata,
|
||||
uniStaticSlugs,
|
||||
} from "@/features/katalog/components/universite-icerik";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return getAllUniversiteSlugs().map((slug) => ({ slug }));
|
||||
return uniStaticSlugs().map((slug) => ({ slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}): Promise<Metadata> {
|
||||
}: PageProps<"/universite/[slug]">): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
return uniSayfaMetadata(slug, 1);
|
||||
}
|
||||
|
||||
export default async function UniversitePage({
|
||||
export default function UniversitePage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
return <UniversiteIcerik slug={slug} sayfa={1} />;
|
||||
}: PageProps<"/universite/[slug]">) {
|
||||
return (
|
||||
<Suspense fallback={<UniversiteIcerikSkeleton />}>
|
||||
{params.then(({ slug }) => (
|
||||
<UniversiteIcerik slug={slug} sayfa={1} />
|
||||
))}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getAllUniversiteler } from "@/lib/katalog";
|
||||
import {
|
||||
UniversiteIcerik,
|
||||
UniversiteIcerikSkeleton,
|
||||
uniSayfaMetadata,
|
||||
uniToplamSayfa,
|
||||
} from "../../universite-icerik";
|
||||
uniSayfaStaticParams,
|
||||
} from "@/features/katalog/components/universite-icerik";
|
||||
|
||||
// Yalnızca 1'den fazla sayfası olan üniversiteler için 2..N üretilir;
|
||||
// 1. sayfa taban rota (/universite/[slug]) olduğundan burada üretilmez.
|
||||
export function generateStaticParams() {
|
||||
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),
|
||||
}));
|
||||
});
|
||||
return uniSayfaStaticParams();
|
||||
}
|
||||
|
||||
function sayfaNo(no: string): number | null {
|
||||
@@ -26,22 +21,23 @@ function sayfaNo(no: string): number | null {
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string; no: string }>;
|
||||
}): Promise<Metadata> {
|
||||
}: PageProps<"/universite/[slug]/sayfa/[no]">): Promise<Metadata> {
|
||||
const { slug, no } = await params;
|
||||
const n = sayfaNo(no);
|
||||
if (n == null) return {};
|
||||
return uniSayfaMetadata(slug, n);
|
||||
}
|
||||
|
||||
export default async function UniversiteSayfaPage({
|
||||
export default function UniversiteSayfaPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string; no: string }>;
|
||||
}) {
|
||||
const { slug, no } = await params;
|
||||
const n = sayfaNo(no);
|
||||
if (n == null) notFound();
|
||||
return <UniversiteIcerik slug={slug} sayfa={n} />;
|
||||
}: PageProps<"/universite/[slug]/sayfa/[no]">) {
|
||||
return (
|
||||
<Suspense fallback={<UniversiteIcerikSkeleton />}>
|
||||
{params.then(({ slug, no }) => {
|
||||
const n = sayfaNo(no);
|
||||
if (n == null) notFound();
|
||||
return <UniversiteIcerik slug={slug} sayfa={n} />;
|
||||
})}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { getUniversiteBySlug, bolumSlugFromIsim } from "@/lib/katalog";
|
||||
import { trBaslikDuzeni } from "@/lib/slug";
|
||||
import { JsonLd, breadcrumbJsonLd, SITE_URL } from "@/lib/seo";
|
||||
import { CtaSiraForm } from "@/components/cta-sira-form";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { Sayfalama, SAYFA_BOYU } from "@/components/sayfalama";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import { UniversiteKonumHaritasi } from "@/components/universite-konum-haritasi";
|
||||
import { UniversiteProgramTablosu } from "@/components/universite-program-tablosu";
|
||||
import { UniLogo } from "@/components/uni-logo";
|
||||
import { uniLogoYolu } from "@/lib/uni-logolar";
|
||||
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));
|
||||
}
|
||||
|
||||
export function uniSayfaMetadata(slug: string, sayfa: number): Metadata {
|
||||
const uni = getUniversiteBySlug(slug);
|
||||
if (!uni) return {};
|
||||
if (sayfa > 1) {
|
||||
return {
|
||||
title: `${uni.ad} Taban Puanları 2026 (Sayfa ${sayfa})`,
|
||||
description: `${uni.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${uni.programSayisi} programın 2025 yerleştirme verileri.`,
|
||||
alternates: { canonical: `/universite/${slug}/sayfa/${sayfa}` },
|
||||
};
|
||||
}
|
||||
return {
|
||||
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.`,
|
||||
alternates: { canonical: `/universite/${slug}` },
|
||||
};
|
||||
}
|
||||
|
||||
export function UniversiteIcerik({
|
||||
slug,
|
||||
sayfa,
|
||||
}: {
|
||||
slug: string;
|
||||
sayfa: number;
|
||||
}) {
|
||||
const uni = getUniversiteBySlug(slug);
|
||||
if (!uni) notFound();
|
||||
|
||||
// Sayfalama fakülte sıralı düz liste üzerinden; dilim içi yeniden gruplanır
|
||||
const tumProgramlar = uni.fakulteler.flatMap((f) => f.programlar);
|
||||
const toplamSayfa = uniToplamSayfa(tumProgramlar.length);
|
||||
if (sayfa < 1 || sayfa > toplamSayfa) notFound();
|
||||
const dilim = tumProgramlar.slice(
|
||||
(sayfa - 1) * SAYFA_BOYU,
|
||||
sayfa * SAYFA_BOYU,
|
||||
);
|
||||
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}`;
|
||||
|
||||
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
|
||||
? {
|
||||
address: {
|
||||
"@type": "PostalAddress",
|
||||
addressLocality: trBaslikDuzeni(uni.il),
|
||||
addressCountry: "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). Ü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} />
|
||||
|
||||
<p className="mt-3 text-xs leading-5 text-slate-500">
|
||||
"—": o yıl için yerleştirme verisi bulunmuyor (yeni açılan
|
||||
veya o yıl yerleşen çıkmayan program). Kaynak: YÖK Atlas 2025
|
||||
yerleştirme sonuçları.
|
||||
</p>
|
||||
|
||||
<Sayfalama
|
||||
tabanYol={`/universite/${slug}`}
|
||||
sayfa={sayfa}
|
||||
toplamSayfa={toplamSayfa}
|
||||
/>
|
||||
|
||||
<div className="mt-10">
|
||||
<CtaSiraForm
|
||||
baslik={`Sıralamanla ${uni.ad} programlarına yerleşir misin?`}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { getAllUniversiteler, type UniOzet } from "@/lib/katalog";
|
||||
import { trBaslikDuzeni } from "@/lib/slug";
|
||||
import { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||
import { CtaSiraForm } from "@/components/cta-sira-form";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import { UniLogo } from "@/components/uni-logo";
|
||||
import {
|
||||
UniversiteKatalogu,
|
||||
UniversiteKataloguSkeleton,
|
||||
} from "@/features/katalog/components/universite-listesi";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Üniversitelerin Taban Puanları ve Sıralamaları 2026",
|
||||
@@ -15,44 +14,7 @@ export const metadata: Metadata = {
|
||||
alternates: { canonical: "/universiteler" },
|
||||
};
|
||||
|
||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||
|
||||
function UniLinkleri({ uniler }: { uniler: UniOzet[] }) {
|
||||
return (
|
||||
<ul className="mt-4 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
||||
{uniler.map((u) => (
|
||||
<li key={u.slug}>
|
||||
<Link
|
||||
href={`/universite/${u.slug}`}
|
||||
className="group flex items-center gap-3 text-sm text-slate-700 hover:text-primary"
|
||||
>
|
||||
<UniLogo slug={u.slug} ad={u.ad} boy="sm" />
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate group-hover:underline">
|
||||
{u.ad}
|
||||
</span>
|
||||
<span className="block text-xs text-slate-400">
|
||||
{u.il ? `${trBaslikDuzeni(u.il)} · ` : ""}
|
||||
{sayi(u.programSayisi)} program
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UniversitelerPage() {
|
||||
const hepsi = getAllUniversiteler();
|
||||
const devlet = hepsi.filter((u) => u.unitur === "DEVLET");
|
||||
const vakif = hepsi.filter(
|
||||
(u) => u.unitur != null && ["VAKIF", "VAKIF MYO"].includes(u.unitur),
|
||||
);
|
||||
const digerUniler = hepsi.filter(
|
||||
(u) => !devlet.includes(u) && !vakif.includes(u),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
||||
@@ -66,51 +28,10 @@ export default function UniversitelerPage() {
|
||||
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
||||
Üniversitelerin Taban Puanları ve Sıralamaları 2026
|
||||
</h1>
|
||||
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
||||
{sayi(hepsi.length)} üniversitenin 2025 YKS yerleştirme verileri.
|
||||
Üniversiteye tıklayınca tüm programlarının taban puanlarını, başarı
|
||||
sıralamalarını ve kontenjanlarını fakülte fakülte görürsün. Veriler
|
||||
YÖK Atlas 2025 yerleştirme sonuçlarına dayanır; 2026 tercihleri için
|
||||
rehber niteliğindedir.
|
||||
</p>
|
||||
|
||||
<PagePixelDivider seed={63} className="mt-8" />
|
||||
|
||||
<section className="mt-8">
|
||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||
Devlet Üniversiteleri
|
||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||
{sayi(devlet.length)}
|
||||
</span>
|
||||
</h2>
|
||||
<UniLinkleri uniler={devlet} />
|
||||
</section>
|
||||
|
||||
<section className="mt-10">
|
||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||
Vakıf Üniversiteleri
|
||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||
{sayi(vakif.length)}
|
||||
</span>
|
||||
</h2>
|
||||
<UniLinkleri uniler={vakif} />
|
||||
</section>
|
||||
|
||||
{digerUniler.length > 0 ? (
|
||||
<section className="mt-10">
|
||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||
KKTC ve Yurt Dışı
|
||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||
{sayi(digerUniler.length)}
|
||||
</span>
|
||||
</h2>
|
||||
<UniLinkleri uniler={digerUniler} />
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<div className="mt-12">
|
||||
<CtaSiraForm />
|
||||
</div>
|
||||
<Suspense fallback={<UniversiteKataloguSkeleton />}>
|
||||
<UniversiteKatalogu />
|
||||
</Suspense>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user