Files
kolaytercih/src/features/katalog/components/sayfalama.tsx
bilalgursen 4dfb24bb13 feat(seo): derin katalog sayfalarında başlık/açıklama/H1'i sayfanın içeriğiyle ayrıştır
/bolum/*/sayfa/N: title + description + H1 o sayfanın taban sırası aralığını
taşır (dilimdeki min–max efektif sıra; veri yoksa "taban verisi olmayan
programlar"). Görünür breadcrumb üniversite sayfasındaki desenle eşitlendi
(üst bölüm linki + "Sayfa N").

/universite/*/sayfa/N: description + giriş paragrafı o sayfadaki birim adlarını
sayar (ilk 2 + "ve N birim daha"); dilim tek birimden ibaretse birim içi taban
sırası aralığı eklenir. <title> uzunluk nedeniyle değişmedi.

Dilim hesabı tek yardımcıda (sayfaDilimi); metadata ve gövde aynı hesabı
kullanır. Canonical, robots, sitemap, rota dosyaları ve 1. sayfaların
başlık/açıklaması değişmedi.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 09:22:59 +03:00

139 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ReactNode } from "react";
import Link from "next/link";
import { ChevronLeft, ChevronRight } from "lucide-react";
/** Programatik tablo sayfaları için satır dilimi boyutu. */
export const SAYFA_BOYU = 50;
/**
* Düz listenin `sayfa` numaralı dilimi. Metadata (title/description) ile sayfa
* gövdesi aynı satırları anlatsın diye dilim hesabı tek yerde durur.
*/
export function sayfaDilimi<T>(liste: readonly T[], sayfa: number): T[] {
return liste.slice((sayfa - 1) * SAYFA_BOYU, sayfa * SAYFA_BOYU);
}
/**
* Statik path tabanlı sayfalama navigasyonu. 1. sayfa taban URL'in
* kendisidir (/bolum/x), sonrakiler /bolum/x/sayfa/2 ... — hepsi gerçek
* <Link>, Google her sayfayı ayrı statik URL olarak tarar.
*
* `onSayfa` verilirse Link yerine aynı görünümde butonlar render edilir —
* istemci tarafında sıralanmış listelerin sayfalaması URL değiştirmeden
* bu geri çağrıyla ilerler.
*/
export function Sayfalama({
tabanYol,
sayfa,
toplamSayfa,
sorgu,
onSayfa,
}: {
tabanYol: string;
sayfa: number;
toplamSayfa: number;
sorgu?: string;
onSayfa?: (n: number) => void;
}) {
if (toplamSayfa <= 1) return null;
const yol = (n: number) => {
const path = n === 1 ? tabanYol : `${tabanYol}/sayfa/${n}`;
return sorgu ? `${path}?${sorgu}` : path;
};
const gecis = (
n: number,
className: string,
icerik: ReactNode,
ozellikler?: { rel?: string; aktif?: boolean },
) =>
onSayfa ? (
<button
key={n}
type="button"
onClick={() => onSayfa(n)}
aria-current={ozellikler?.aktif ? "page" : undefined}
className={`${className} cursor-pointer`}
>
{icerik}
</button>
) : (
<Link
key={n}
href={yol(n)}
rel={ozellikler?.rel}
aria-current={ozellikler?.aktif ? "page" : undefined}
className={className}
>
{icerik}
</Link>
);
// 7'den çok sayfada pencere: 1 … (c-1) c (c+1) … son
const numaralar: (number | "...")[] = [];
if (toplamSayfa <= 7) {
for (let n = 1; n <= toplamSayfa; n++) numaralar.push(n);
} else {
numaralar.push(1);
if (sayfa > 3) numaralar.push("...");
for (
let n = Math.max(2, sayfa - 1);
n <= Math.min(toplamSayfa - 1, sayfa + 1);
n++
) {
numaralar.push(n);
}
if (sayfa < toplamSayfa - 2) numaralar.push("...");
numaralar.push(toplamSayfa);
}
return (
<nav
aria-label="Sayfalama"
className="mt-6 flex flex-wrap items-center justify-center gap-1.5"
>
{sayfa > 1
? gecis(
sayfa - 1,
"inline-flex h-9 items-center gap-1 rounded-full border border-slate-200 bg-white px-3.5 text-sm text-slate-700 hover:border-primary/40 hover:text-primary",
<>
<ChevronLeft className="size-4" aria-hidden />
Önceki
</>,
{ rel: "prev" },
)
: null}
{numaralar.map((n, i) =>
n === "..." ? (
<span
key={`bosluk-${i}`}
className="px-1.5 text-sm text-slate-400"
aria-hidden
>
…
</span>
) : (
gecis(
n,
n === sayfa
? "inline-flex h-9 min-w-9 items-center justify-center rounded-full bg-primary px-2 text-sm font-semibold text-primary-foreground"
: "inline-flex h-9 min-w-9 items-center justify-center rounded-full border border-slate-200 bg-white px-2 text-sm text-slate-700 hover:border-primary/40 hover:text-primary",
n,
{ aktif: n === sayfa },
)
),
)}
{sayfa < toplamSayfa
? gecis(
sayfa + 1,
"inline-flex h-9 items-center gap-1 rounded-full border border-slate-200 bg-white px-3.5 text-sm text-slate-700 hover:border-primary/40 hover:text-primary",
<>
Sonraki
<ChevronRight className="size-4" aria-hidden />
</>,
{ rel: "next" },
)
: null}
</nav>
);
}