feat(katalog): şehre göre üniversiteler — /universiteler/[il] sayfaları ve şehir çipleri
- Veri: getAllIller/getIlBySlug (üniversite en çok programının olduğu ile sayılır; ili boş olan hiçbir şehre girmez; tireli yurt dışı adlar baş harfli). - /universiteler: "Şehre göre" çip satırı (2+ üniversiteli 28 şehir) + "Tüm şehirler" A–Z üç sütunlu liste (#tum-sehirler). - /universiteler/[il]: 92 statik şehir sayfası — katalog detay düzeni, il ve üniversite sayısı hapları, kompakt sıra satırı, devlet/vakıf/KKTC-yurt dışı öbekleri, diğer şehir çipleri, CTA bandı; metadata + breadcrumb JSON-LD. - SehirCipleri: sihirbaz il çipiyle aynı kalıp, aktif şehir mavi dolu. - Sitemap'e şehir sayfaları eklendi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
import { KATALOG_VERI_TARIHI, SITE_URL } from "@/lib/seo";
|
import { KATALOG_VERI_TARIHI, SITE_URL } from "@/lib/seo";
|
||||||
import { getAllBolumSlugs, getAllUniversiteSlugs } from "@/lib/katalog";
|
import {
|
||||||
|
getAllBolumSlugs,
|
||||||
|
getAllIlSlugs,
|
||||||
|
getAllUniversiteSlugs,
|
||||||
|
} from "@/lib/katalog";
|
||||||
import { getAllRehberler } from "@/lib/rehber";
|
import { getAllRehberler } from "@/lib/rehber";
|
||||||
|
|
||||||
// Bilinçli olarak sitemap DIŞI: /sonuc (noindex), /giris, /listem,
|
// Bilinçli olarak sitemap DIŞI: /sonuc (noindex), /giris, /listem,
|
||||||
@@ -46,6 +50,12 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const iller: MetadataRoute.Sitemap = getAllIlSlugs().map((slug) => ({
|
||||||
|
url: `${SITE_URL}/universiteler/${slug}`,
|
||||||
|
lastModified: KATALOG_VERI_TARIHI,
|
||||||
|
priority: 0.6,
|
||||||
|
changeFrequency: "monthly",
|
||||||
|
}));
|
||||||
const yazilar = getAllRehberler();
|
const yazilar = getAllRehberler();
|
||||||
const rehberler: MetadataRoute.Sitemap = yazilar.map((y) => ({
|
const rehberler: MetadataRoute.Sitemap = yazilar.map((y) => ({
|
||||||
url: `${SITE_URL}/rehber/${y.slug}`,
|
url: `${SITE_URL}/rehber/${y.slug}`,
|
||||||
@@ -72,6 +82,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
rehberDizini,
|
rehberDizini,
|
||||||
...bolumler,
|
...bolumler,
|
||||||
...universiteler,
|
...universiteler,
|
||||||
|
...iller,
|
||||||
...rehberler,
|
...rehberler,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/app/universiteler/[il]/page.tsx
Normal file
33
src/app/universiteler/[il]/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
|
import {
|
||||||
|
IlIcerik,
|
||||||
|
IlIcerikSkeleton,
|
||||||
|
ilSayfaMetadata,
|
||||||
|
ilStaticSlugs,
|
||||||
|
} from "@/features/katalog/components/il-icerik";
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return ilStaticSlugs().map((il) => ({ il }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: PageProps<"/universiteler/[il]">): Promise<Metadata> {
|
||||||
|
const { il } = await params;
|
||||||
|
return ilSayfaMetadata(il);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IlPage({ params }: PageProps<"/universiteler/[il]">) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Suspense fallback={<IlIcerikSkeleton />}>
|
||||||
|
{params.then(({ il }) => (
|
||||||
|
<IlIcerik slug={il} />
|
||||||
|
))}
|
||||||
|
</Suspense>
|
||||||
|
<SiteFooter />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
213
src/features/katalog/components/il-icerik.tsx
Normal file
213
src/features/katalog/components/il-icerik.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
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 { UniLinkleri } from "./universite-listesi";
|
||||||
|
|
||||||
|
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,
|
||||||
|
}: {
|
||||||
|
baslik: string;
|
||||||
|
uniler: UniOzet[];
|
||||||
|
}) {
|
||||||
|
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>
|
||||||
|
<UniLinkleri uniler={uniler} />
|
||||||
|
</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`} />
|
||||||
|
<h1
|
||||||
|
id="il-baslik"
|
||||||
|
className="mt-6 text-center font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl lg:text-left"
|
||||||
|
>
|
||||||
|
{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-3xl text-center text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0 lg:text-left">
|
||||||
|
{il.ad}'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 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} />
|
||||||
|
<UniGrubu baslik="Vakıf Üniversiteleri" uniler={vakif} />
|
||||||
|
<UniGrubu baslik="KKTC ve Yurt Dışı" uniler={diger} />
|
||||||
|
|
||||||
|
<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="mx-auto mt-6 h-10 w-full max-w-xl lg:mx-0" />
|
||||||
|
<div className="mt-5 flex justify-center gap-2 lg:justify-start">
|
||||||
|
<Skeleton className="h-10 w-28 rounded-full" />
|
||||||
|
<Skeleton className="h-10 w-32 rounded-full" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="mx-auto mt-6 h-5 w-full max-w-3xl lg:mx-0" />
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
80
src/features/katalog/components/sehir-cipleri.tsx
Normal file
80
src/features/katalog/components/sehir-cipleri.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import type { IlOzet } from "../katalog-queries";
|
||||||
|
|
||||||
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Şehir çipi (DESIGN.md §4.17): sihirbazın il çipiyle aynı kalıp — 40 px,
|
||||||
|
* tam yuvarlak, hover §2.1 tek turuncu; seçili/aktif hâl mavi dolu.
|
||||||
|
* Etkileşimli çip olduğu için rozet (§4.2) kuralının dışındadır.
|
||||||
|
*/
|
||||||
|
const CIP_TEMEL =
|
||||||
|
"inline-flex h-10 cursor-pointer items-center gap-1.5 rounded-full border px-3.5 text-sm font-semibold transition-[background-color,border-color,color,transform] duration-150 active:scale-[0.97]";
|
||||||
|
const CIP_PASIF =
|
||||||
|
"border-slate-200 bg-white text-slate-700 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500";
|
||||||
|
const CIP_AKTIF = "border-primary bg-primary text-white";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Şehre göre üniversite gezinme çipleri. `iller` verilen sırayla dizilir
|
||||||
|
* (çağıran genelde üniversite sayısına göre azalan verir). `aktifSlug`
|
||||||
|
* bulunulan şehir sayfasıdır: mavi dolu ve `aria-current="page"`.
|
||||||
|
* `tumuHref` verilirse sona "Tüm şehirler" çipi eklenir (A–Z listesine).
|
||||||
|
*/
|
||||||
|
export function SehirCipleri({
|
||||||
|
iller,
|
||||||
|
aktifSlug,
|
||||||
|
tumuHref,
|
||||||
|
tumuEtiket,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
iller: IlOzet[];
|
||||||
|
aktifSlug?: string;
|
||||||
|
tumuHref?: string;
|
||||||
|
tumuEtiket?: string;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
className={cn(
|
||||||
|
"flex flex-wrap items-center justify-center gap-2 lg:justify-start",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{iller.map((il) => {
|
||||||
|
const aktif = il.slug === aktifSlug;
|
||||||
|
return (
|
||||||
|
<li key={il.slug}>
|
||||||
|
<Link
|
||||||
|
href={`/universiteler/${il.slug}`}
|
||||||
|
aria-current={aktif ? "page" : undefined}
|
||||||
|
className={cn(CIP_TEMEL, aktif ? CIP_AKTIF : CIP_PASIF)}
|
||||||
|
>
|
||||||
|
{il.ad}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"font-normal tabular-nums",
|
||||||
|
aktif ? "text-white/80" : "text-slate-400",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{sayi(il.uniler.length)}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{tumuHref ? (
|
||||||
|
<li>
|
||||||
|
<Link href={tumuHref} className={cn(CIP_TEMEL, CIP_PASIF)}>
|
||||||
|
{tumuEtiket ?? "Tüm şehirler"}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Çip satırında gösterilecek şehirler: en az iki üniversitesi olanlar. */
|
||||||
|
export function oneCikanIller(iller: IlOzet[]): IlOzet[] {
|
||||||
|
return iller.filter((il) => il.uniler.length >= 2);
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getAllUniversiteler, type UniOzet } from "../katalog-queries";
|
import {
|
||||||
|
getAllIller,
|
||||||
|
getAllUniversiteler,
|
||||||
|
type UniOzet,
|
||||||
|
} from "../katalog-queries";
|
||||||
import { trBaslikDuzeni } from "@/lib/slug";
|
import { trBaslikDuzeni } from "@/lib/slug";
|
||||||
|
import { SehirCipleri, oneCikanIller } from "./sehir-cipleri";
|
||||||
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
||||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||||
import { UniLogo } from "@/components/uni-logo";
|
import { UniLogo } from "@/components/uni-logo";
|
||||||
@@ -9,7 +14,7 @@ import { SON_YIL, TERCIH_YILI } from "@/lib/veri-yillari";
|
|||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
function UniLinkleri({ uniler }: { uniler: UniOzet[] }) {
|
export function UniLinkleri({ uniler }: { uniler: UniOzet[] }) {
|
||||||
return (
|
return (
|
||||||
<ul className="mt-4 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
<ul className="mt-4 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
||||||
{uniler.map((u) => (
|
{uniler.map((u) => (
|
||||||
@@ -47,6 +52,10 @@ export function UniversiteKatalogu() {
|
|||||||
else digerUniler.push(u);
|
else digerUniler.push(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const iller = getAllIller();
|
||||||
|
const oneCikan = oneCikanIller(iller);
|
||||||
|
const illerAz = [...iller].sort((a, b) => a.ad.localeCompare(b.ad, "tr-TR"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
||||||
@@ -57,6 +66,27 @@ export function UniversiteKatalogu() {
|
|||||||
için rehber niteliğindedir.
|
için rehber niteliğindedir.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Şehre göre gezinme: sık bakılan (2+ üniversiteli) şehirler çip
|
||||||
|
olarak önde, tam A–Z liste sayfanın altında (DESIGN.md §1.1 "sık
|
||||||
|
bakılan önde, ayrıntı bir dokunuş derinde"). */}
|
||||||
|
<section className="mt-8" aria-labelledby="sehre-gore">
|
||||||
|
<h2
|
||||||
|
id="sehre-gore"
|
||||||
|
className="font-heading text-2xl font-bold text-slate-900"
|
||||||
|
>
|
||||||
|
Şehre göre
|
||||||
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
|
{sayi(iller.length)} şehir
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<SehirCipleri
|
||||||
|
iller={oneCikan}
|
||||||
|
tumuHref="#tum-sehirler"
|
||||||
|
tumuEtiket={`Tüm şehirler (${sayi(iller.length)})`}
|
||||||
|
className="mt-4 justify-start"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
<PagePixelDivider seed={63} className="mt-8" />
|
<PagePixelDivider seed={63} className="mt-8" />
|
||||||
|
|
||||||
<section className="mt-8">
|
<section className="mt-8">
|
||||||
@@ -91,6 +121,30 @@ export function UniversiteKatalogu() {
|
|||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<section id="tum-sehirler" className="mt-10 scroll-mt-24">
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
Tüm şehirler A–Z
|
||||||
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
|
{sayi(iller.length)}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<ul className="mt-4 grid gap-x-6 gap-y-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{illerAz.map((il) => (
|
||||||
|
<li key={il.slug}>
|
||||||
|
<Link
|
||||||
|
href={`/universiteler/${il.slug}`}
|
||||||
|
className="group inline-flex items-baseline gap-2 text-sm text-slate-700 transition-colors hover:text-orange-500"
|
||||||
|
>
|
||||||
|
<span className="group-hover:underline">{il.ad}</span>
|
||||||
|
<span className="text-xs text-slate-400">
|
||||||
|
{sayi(il.uniler.length)} üniversite
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="mt-12">
|
<div className="mt-12">
|
||||||
<CtaSiraForm />
|
<CtaSiraForm />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ export {
|
|||||||
getAllBolumSlugs,
|
getAllBolumSlugs,
|
||||||
getAllUniversiteler,
|
getAllUniversiteler,
|
||||||
getAllUniversiteSlugs,
|
getAllUniversiteSlugs,
|
||||||
|
getAllIller,
|
||||||
|
getAllIlSlugs,
|
||||||
getBolumBySlug,
|
getBolumBySlug,
|
||||||
|
getIlBySlug,
|
||||||
getUniversiteBySlug,
|
getUniversiteBySlug,
|
||||||
uniSlugFromAd,
|
uniSlugFromAd,
|
||||||
bolumSlugFromIsim,
|
bolumSlugFromIsim,
|
||||||
type BolumOzet,
|
type BolumOzet,
|
||||||
|
type IlOzet,
|
||||||
type UniOzet,
|
type UniOzet,
|
||||||
} from "@/lib/katalog";
|
} from "@/lib/katalog";
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ const globalKatalog = globalThis as unknown as {
|
|||||||
bolumOzetler?: BolumOzet[];
|
bolumOzetler?: BolumOzet[];
|
||||||
uniMap?: Map<string, UniKayit>;
|
uniMap?: Map<string, UniKayit>;
|
||||||
uniOzetler?: UniOzet[];
|
uniOzetler?: UniOzet[];
|
||||||
|
iller?: IlOzet[];
|
||||||
|
ilMap?: Map<string, IlOzet>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function bolumMapGetir(): Map<string, BolumKayit> {
|
function bolumMapGetir(): Map<string, BolumKayit> {
|
||||||
@@ -369,6 +371,68 @@ export function getAllUniversiteSlugs(): string[] {
|
|||||||
return [...uniMapGetir().keys()];
|
return [...uniMapGetir().keys()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- İl (şehir) tarafı: /universiteler/[il] ----------
|
||||||
|
|
||||||
|
export type IlOzet = {
|
||||||
|
/** turkishSlugify(il): "istanbul", "saraybosna-bosna-hersek" */
|
||||||
|
slug: string;
|
||||||
|
/** Görünen ad: İstanbul, Bakü-Azerbaycan */
|
||||||
|
ad: string;
|
||||||
|
/** Bu ilde yer alan üniversiteler (UniOzet.il esas; ad sırasıyla) */
|
||||||
|
uniler: UniOzet[];
|
||||||
|
programSayisi: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "BAKÜ-AZERBAYCAN" → "Bakü-Azerbaycan": trBaslikDuzeni yalnız boşlukta
|
||||||
|
* böler; yurt dışı şehirlerin tireli ülke eki de baş harfli yazılır.
|
||||||
|
*/
|
||||||
|
function ilGorunenAd(il: string): string {
|
||||||
|
return trBaslikDuzeni(il).replace(/-(\p{L})/gu, (_, harf: string) =>
|
||||||
|
`-${harf.toLocaleUpperCase("tr-TR")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Üniversiteleri iline göre kümeler; sıralama üniversite sayısına göre
|
||||||
|
* azalan, eşitlikte ad. Her üniversite tek ilde sayılır (UniOzet.il: en çok
|
||||||
|
* programının bulunduğu il). İli olmayan (NULL/boş) üniversite hiçbir ile
|
||||||
|
* girmez. KKTC ve yurt dışı şehirler de birer ildir (LEFKOŞA, BAKÜ-…).
|
||||||
|
*/
|
||||||
|
export function getAllIller(): IlOzet[] {
|
||||||
|
if (!globalKatalog.iller) {
|
||||||
|
const bySlug = new Map<string, IlOzet>();
|
||||||
|
for (const u of getAllUniversiteler()) {
|
||||||
|
const il = u.il?.trim();
|
||||||
|
if (!il) continue;
|
||||||
|
const slug = turkishSlugify(il);
|
||||||
|
if (!slug) continue;
|
||||||
|
let kayit = bySlug.get(slug);
|
||||||
|
if (!kayit) {
|
||||||
|
kayit = { slug, ad: ilGorunenAd(il), uniler: [], programSayisi: 0 };
|
||||||
|
bySlug.set(slug, kayit);
|
||||||
|
}
|
||||||
|
kayit.uniler.push(u);
|
||||||
|
kayit.programSayisi += u.programSayisi;
|
||||||
|
}
|
||||||
|
globalKatalog.ilMap = bySlug;
|
||||||
|
globalKatalog.iller = [...bySlug.values()].sort(
|
||||||
|
(a, b) =>
|
||||||
|
b.uniler.length - a.uniler.length || a.ad.localeCompare(b.ad, "tr-TR"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return globalKatalog.iller;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllIlSlugs(): string[] {
|
||||||
|
return getAllIller().map((il) => il.slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIlBySlug(slug: string): IlOzet | null {
|
||||||
|
getAllIller();
|
||||||
|
return globalKatalog.ilMap!.get(slug) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
export function getUniversiteBySlug(slug: string): UniDetay | null {
|
export function getUniversiteBySlug(slug: string): UniDetay | null {
|
||||||
const kayit = uniMapGetir().get(slug);
|
const kayit = uniMapGetir().get(slug);
|
||||||
if (!kayit) return null;
|
if (!kayit) return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user