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:
bilalgursen
2026-09-24 14:19:09 +03:00
parent f9b6f0c235
commit d8a28b7c3a
7 changed files with 462 additions and 3 deletions

View File

@@ -62,6 +62,8 @@ const globalKatalog = globalThis as unknown as {
bolumOzetler?: BolumOzet[];
uniMap?: Map<string, UniKayit>;
uniOzetler?: UniOzet[];
iller?: IlOzet[];
ilMap?: Map<string, IlOzet>;
};
function bolumMapGetir(): Map<string, BolumKayit> {
@@ -369,6 +371,68 @@ export function getAllUniversiteSlugs(): string[] {
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 {
const kayit = uniMapGetir().get(slug);
if (!kayit) return null;