Files
kolaytercih/src/features/katalog/components/il-icerik.tsx
bilalgursen a43c045828
All checks were successful
Deploy / deploy (push) Successful in 5m1s
feat(katalog+tasarim): devlet/vakıf tür sayfaları, tür çipleri link; kayıtlı-sıra satırı şişko ve sade
- /universiteler/devlet ve /universiteler/vakif: şehir sayfası iskeleti,
  haritada bu türden üniversitesi olan her il turuncu (yakınlaşma yok), üç
  özet kartı, şehir çipleri (sayfa içi çapa), şehir şehir §4.15 satırları,
  öteki türe geçiş hapı; metadata + sitemap.
- TurCipi tek kaynak: devlet/vakıf çipi tür sayfasına link (sonuç, listem,
  tadımlık, bölüm, şehir/tür satırları); üniversite sayfası tür hapı ve
  /universiteler başlıkları da link. UniSatirlari ortak dosyada: ad, tür ve
  il ayrı linkler.
- Kompakt profilli satır (DESIGN.md §10.9): iri kupa kutusu + text-3xl/4xl
  sıra, text-xl/2xl başlık, 48 px buton; telefonda alt alta, lg'de tek satır.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 15:53:54 +03:00

229 lines
8.3 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 { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { Building2, ChevronRight, Landmark, MapPin } from "lucide-react";
import {
getAllIller,
getAllIlSlugs,
getIlBySlug,
type UniOzet,
} from "../katalog-queries";
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
import { PagePixelDivider } from "@/components/pixel-decor";
import { Skeleton } from "@/components/ui/skeleton";
import {
JsonLd,
breadcrumbJsonLd,
sayfaMetadata,
ustSegmentOgGorseli,
} from "@/lib/seo";
import { BASLIK_YILI, SON_YIL, TERCIH_YILI } from "@/lib/veri-yillari";
import { NavBaslik } from "./nav-baslik";
import { SehirCipleri, oneCikanIller } from "./sehir-cipleri";
import { SehirPikselHaritasi } from "./sehir-piksel-haritasi";
import { SehirYuzeyi } from "./sehir-yuzeyi";
import { UniSatirlari } from "./uni-satirlari";
const sayi = (n: number) => n.toLocaleString("tr-TR");
export function ilStaticSlugs(): string[] {
return getAllIlSlugs();
}
export function ilSayfaMetadata(slug: string): Metadata {
const il = getIlBySlug(slug);
if (!il) return {};
return sayfaMetadata({
title: `${il.ad} Üniversiteleri Taban Puanları ve Sıralamaları ${BASLIK_YILI}`,
description: `${il.ad}'daki ${il.uniler.length} üniversitenin ${SON_YIL} YKS yerleştirme verileri: ${sayi(il.programSayisi)} programın taban puanları, başarı sıralamaları ve kontenjanları. ${TERCIH_YILI} tercih rehberi.`,
path: `/universiteler/${slug}`,
openGraph: {
images: ustSegmentOgGorseli(
"/universiteler",
"Üniversitelerin taban puanları ve sıralamaları — KolayTercih",
),
},
});
}
function UniGrubu({
baslik,
uniler,
buIl,
}: {
baslik: string;
uniler: UniOzet[];
buIl: string;
}) {
if (uniler.length === 0) return null;
return (
<section className="mt-10 text-center lg:text-left">
<h2 className="font-heading text-2xl font-bold text-slate-900">
{baslik}
<span className="ml-2 text-sm font-normal text-slate-400">
{sayi(uniler.length)}
</span>
</h2>
<UniSatirlari uniler={uniler} gizliIl={buIl} />
</section>
);
}
/**
* /universiteler/[il]: bir şehirdeki üniversiteler, devlet / vakıf / diğer
* öbekleriyle. Düzen katalog detay sayfalarıyla aynı (DESIGN.md §5.2):
* yol satırı, başlık, kimlik hapları ve özet lg altında ortalı; listeler
* her zaman sola yaslı.
*/
export function IlIcerik({ slug }: { slug: string }) {
const il = getIlBySlug(slug);
if (!il) notFound();
const devlet: UniOzet[] = [];
const vakif: UniOzet[] = [];
const diger: UniOzet[] = [];
for (const u of il.uniler) {
if (u.unitur === "DEVLET") devlet.push(u);
else if (u.unitur === "VAKIF" || u.unitur === "VAKIF MYO") vakif.push(u);
else diger.push(u);
}
const ozetParcalar = [
devlet.length > 0 ? `${sayi(devlet.length)} devlet` : null,
vakif.length > 0 ? `${sayi(vakif.length)} vakıf` : null,
diger.length > 0 ? `${sayi(diger.length)} KKTC/yurt dışı` : null,
].filter(Boolean);
const ctaBaslik = `Sıralaman ${il.ad} üniversitelerinin neresinde?`;
const digerSehirler = oneCikanIller(getAllIller());
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: il.ad, path: `/universiteler/${slug}` },
])}
/>
<nav
aria-label="İçerik yolu"
className="flex flex-wrap items-center justify-center gap-1.5 text-sm text-slate-500 lg:justify-start"
>
<Link
href="/"
className="transition-colors hover:text-orange-500 hover:underline"
>
Ana Sayfa
</Link>
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
<Link
href="/universiteler"
className="transition-colors hover:text-orange-500 hover:underline"
>
Üniversiteler
</Link>
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
<span className="text-slate-700">{il.ad}</span>
</nav>
<NavBaslik baslikId="il-baslik" ad={`${il.ad} üniversiteleri`} />
{/* Başlık yüzeyi: beyaz kart, arka planı kaplayan dot-matrix şehir
haritası (DESIGN.md §4.17). lg altında metin ortalı, harita altta. */}
<SehirYuzeyi
harita={
<SehirPikselHaritasi
il={il.il}
ad={il.ad}
doldur
odakHiza={{ lg: { x: 0.74, y: 0.5 }, dar: { x: 0.5, y: 0.78 } }}
/>
}
metin={
<div className="text-center lg:text-left">
<h1
id="il-baslik"
className="font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl"
>
{il.ad} Üniversiteleri Taban Puanları {BASLIK_YILI}
</h1>
{/* Kimlik hapları (DESIGN.md §4.12): il + üniversite sayısı */}
<p className="mt-5 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800">
<MapPin className="size-4 text-primary" aria-hidden />
{il.ad}
</span>
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800">
{devlet.length >= vakif.length ? (
<Landmark className="size-4 text-primary" aria-hidden />
) : (
<Building2 className="size-4 text-primary" aria-hidden />
)}
{sayi(il.uniler.length)} üniversite
</span>
</p>
<p className="mx-auto mt-6 max-w-2xl text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0">
{il.ad}&apos;da {SON_YIL} YKS yerleştirmesinde öğrenci alan{" "}
<strong>{sayi(il.uniler.length)} üniversite</strong>
{ozetParcalar.length > 1 ? ` (${ozetParcalar.join(", ")})` : ""}{" "}
ve toplam <strong>{sayi(il.programSayisi)} program</strong> var.
Üniversiteye tıklayınca 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 {SON_YIL} yerleştirme sonuçlarına dayanır;{" "}
{TERCIH_YILI} tercihleri için rehber niteliğindedir.
</p>
</div>
}
/>
<div className="mt-8">
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
</div>
<PagePixelDivider seed={71} className="mx-auto mt-8 lg:mx-0" />
<UniGrubu baslik="Devlet Üniversiteleri" uniler={devlet} buIl={il.il} />
<UniGrubu baslik="Vakıf Üniversiteleri" uniler={vakif} buIl={il.il} />
<UniGrubu baslik="KKTC ve Yurt Dışı" uniler={diger} buIl={il.il} />
<section className="mt-12 text-center lg:text-left" aria-labelledby="diger-sehirler">
<h2
id="diger-sehirler"
className="font-heading text-2xl font-bold text-slate-900"
>
Diğer şehirler
</h2>
<SehirCipleri
iller={digerSehirler}
aktifSlug={slug}
tumuHref="/universiteler#tum-sehirler"
className="mt-4"
/>
</section>
<div className="mt-12">
<CtaSiraForm
baslik={ctaBaslik}
aciklama={`YKS başarı sıralamanı gir; ${il.ad} dahil tüm şehirlerde sıralamana uygun hayal, dengeli ve güvenli dilimlerindeki programları anında listele. Ücretsiz, üyelik gerekmez.`}
/>
</div>
</main>
);
}
export function IlIcerikSkeleton() {
return (
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
<Skeleton className="mx-auto h-4 w-64 lg:mx-0" />
<Skeleton className="mt-6 h-72 w-full rounded-3xl" />
<div className="mt-10 space-y-4">
<Skeleton className="h-8 w-72" />
{Array.from({ length: 6 }, (_, i) => (
<Skeleton key={i} className="h-9 w-full max-w-2xl" />
))}
</div>
</main>
);
}