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:
165
src/features/katalog/components/bolum-icerik.tsx
Normal file
165
src/features/katalog/components/bolum-icerik.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import {
|
||||
getAllBolumler,
|
||||
getAllBolumSlugs,
|
||||
getBolumBySlug,
|
||||
uniSlugFromAd,
|
||||
} from "../katalog-queries";
|
||||
import { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||
import { SAYFA_BOYU } from "./sayfalama";
|
||||
import { BolumProgramListesi } from "./bolum-program-listesi";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||
|
||||
export function bolumToplamSayfa(programSayisi: number): number {
|
||||
return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU));
|
||||
}
|
||||
|
||||
// Sayfaların generateStaticParams'ı için — sayfalar *-queries import etmez.
|
||||
export function bolumStaticSlugs(): string[] {
|
||||
return getAllBolumSlugs();
|
||||
}
|
||||
|
||||
// 2..N sayfaları; 1. sayfa taban rota (/bolum/[slug]) olduğundan üretilmez.
|
||||
export function bolumSayfaStaticParams(): { slug: string; no: string }[] {
|
||||
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),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function BolumIcerikSkeleton() {
|
||||
return (
|
||||
<main className="mx-auto w-full max-w-5xl px-4 py-10 sm:py-14">
|
||||
<Skeleton className="h-5 w-56" />
|
||||
<div className="border-b pb-7 sm:pb-9">
|
||||
<Skeleton className="mt-5 h-10 w-2/3 max-w-xl" />
|
||||
<Skeleton className="mt-3 h-5 w-full max-w-2xl" />
|
||||
</div>
|
||||
<div className="mt-8 space-y-3">
|
||||
<Skeleton className="h-7 w-64" />
|
||||
{Array.from({ length: 5 }, (_, i) => (
|
||||
<Skeleton key={i} className="h-12 w-full" />
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user