Files
kolaytercih/src/features/katalog/components/sayfalama.tsx
bilalgursen 5b51fd16e0 feat(tasarim): bölüm sayfası "şişko ve sade" dile göre
- Başlık bloğu lg altında ortalı, kimlik hapları (düzey + puan türü),
  dört özet kartı (Program, Devlet, Vakıf, En iyi sıralama), piksel ayraç
- OzetKart src/components/ozet-kart.tsx'e çıkarıldı; üniversite ve bölüm
  sayfası oradan kullanıyor. 6-7 haneli sayı birimle kısalıyor (ölçüldü)
- Program listesi §4.15: ad sarar, varyant ayrı satırda, 44 px butonlar,
  telefonda taban sıra adın altında, butonlar alt alta
- Sayfalama 44 px, telefonda tek satır; hover'lar tek turuncu
- Denetim: kanonik liste satırı adının mavi hover'ı artık uyarı vermiyor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 00:39:21 +03:00

168 lines
5.9 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";
import { ONCEKI_YIL, SON_YIL } from "@/lib/veri-yillari";
/** 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);
}
/**
* Aralıktaki sıraların hangi yıldan geldiği: hepsi son yıl / hepsi bir önceki
* yıl / karışık. `yalnizOnceki`: sırası son yılda olmayıp bir önceki yıldan
* gelen satır sayısı; `siraliSayi`: sırası olan toplam satır.
*/
export function siraYiliEtiketi(
yalnizOnceki: number,
siraliSayi: number,
): string {
if (yalnizOnceki === 0) return String(SON_YIL);
return yalnizOnceki === siraliSayi
? String(ONCEKI_YIL)
: `${ONCEKI_YIL}–${SON_YIL}`;
}
/**
* Üniversite ve bölüm tablo sayfalarının altındaki CtaSiraForm kartının köprü
* cümlesi — iki sayfa birebir aynı metni kullansın diye tek kaynak. Sunucu
* bileşenlerinden okunduğu için "use client" olan cta-sira-form.tsx'te
* duramaz (istemci modülünün sabitleri sunucuda değer olarak okunamaz).
* Vaat içermez: tablo geçmiş veridir, ürün sıralamaya yakın programları
* risk seviyesine göre ayırıp listeler.
*/
export const KATALOG_CTA_ACIKLAMA =
"Bu tablodaki tabanlar geçmiş yerleştirmelerin sonucu; bu yıl değişebilir. Sıralamanı gir, tabanı sıralamana yakın programları risk seviyesine göre ayrılmış hâlde listeleyelim. Ücretsiz, üyelik gerekmez.";
/**
* 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. Telefonda
// (sm altı) tek satır kalsın diye yalnız Önceki · aktif · Sonraki görünür;
// diğer numaralar HTML'de durur (tarayıcı botları tüm sayfaları görür).
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-8 flex flex-wrap items-center justify-center gap-2"
>
{sayfa > 1
? gecis(
sayfa - 1,
"inline-flex h-11 items-center gap-1 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-700 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]",
<>
<ChevronLeft className="size-4" aria-hidden />
Önceki
</>,
{ rel: "prev" },
)
: null}
{numaralar.map((n, i) =>
n === "..." ? (
<span
key={`bosluk-${i}`}
className="hidden px-1.5 text-sm text-slate-400 sm:inline"
aria-hidden
>
…
</span>
) : (
gecis(
n,
n === sayfa
? "inline-flex h-11 min-w-11 items-center justify-center rounded-full bg-primary px-2 text-sm font-semibold text-primary-foreground"
: "hidden h-11 min-w-11 items-center justify-center rounded-full border border-slate-200 bg-white px-2 text-sm font-semibold text-slate-700 sm:inline-flex transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]",
n,
{ aktif: n === sayfa },
)
),
)}
{sayfa < toplamSayfa
? gecis(
sayfa + 1,
"inline-flex h-11 items-center gap-1 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-700 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]",
<>
Sonraki
<ChevronRight className="size-4" aria-hidden />
</>,
{ rel: "next" },
)
: null}
</nav>
);
}