feat(katalog+tasarim): devlet/vakıf tür sayfaları, tür çipleri link; kayıtlı-sıra satırı şişko ve sade
All checks were successful
Deploy / deploy (push) Successful in 5m1s
All checks were successful
Deploy / deploy (push) Successful in 5m1s
- /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>
This commit is contained in:
@@ -352,8 +352,8 @@ aşağı kaymış, kare boyları farklı — ne tepeler ne bitişler hizalı.**
|
|||||||
Başlığın altında kurumun kimliği; navbar haplarının ölçüsünde:
|
Başlığın altında kurumun kimliği; navbar haplarının ölçüsünde:
|
||||||
`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`
|
`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`
|
||||||
+ başta `size-4 text-primary` ikon (`Landmark` devlet, `Building2` vakıf, `MapPin` il). Tür
|
+ başta `size-4 text-primary` ikon (`Landmark` devlet, `Building2` vakıf, `MapPin` il). Tür
|
||||||
renkle değil ikonla ayrılır. Rozet (§4.2) değildir. Tür hapı etkileşimsizdir; **il hapı şehir
|
renkle değil ikonla ayrılır. Rozet (§4.2) değildir. **Devlet/vakıf hapı tür sayfasına**
|
||||||
sayfasına link**tir (Bilal, 24 Eyl 2026: "şehirlerin geçtiği her yer tıklanabilir") — aynı
|
(§4.18), **il hapı şehir sayfasına link**tir (Bilal, 24 Eyl 2026: "şehirlerin geçtiği her yer tıklanabilir") — aynı
|
||||||
sınıf + `hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]`.
|
sınıf + `hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]`.
|
||||||
Kural genel: bir şehir adı nerede geçerse geçsin (`/universiteler` listesi, bölüm/sonuç/listem/
|
Kural genel: bir şehir adı nerede geçerse geçsin (`/universiteler` listesi, bölüm/sonuç/listem/
|
||||||
tadımlık satırlarındaki tür-il-süre metası) `ilSayfaHref(il)` ile `/universiteler/[il]`'e
|
tadımlık satırlarındaki tür-il-süre metası) `ilSayfaHref(il)` ile `/universiteler/[il]`'e
|
||||||
@@ -493,6 +493,22 @@ bulunulan şehir `border-primary bg-primary text-white` + `aria-current="page"`.
|
|||||||
üst satır). Kareler büyür, il kadraja gelir; ısı fırçası viewBox'ı
|
üst satır). Kareler büyür, il kadraja gelir; ısı fırçası viewBox'ı
|
||||||
canlı okur. Hareketi azalt açıkken kutuya doğrudan atlanır. Sayfa başına tek yakınlaşma.
|
canlı okur. Hareketi azalt açıkken kutuya doğrudan atlanır. Sayfa başına tek yakınlaşma.
|
||||||
|
|
||||||
|
### 4.18 Tür sayfaları — `/universiteler/devlet`, `/universiteler/vakif` (`tur-icerik.tsx`)
|
||||||
|
Bilal (24 Eyl 2026): "vakıf ve devlet de tıklanabilir olmalı, özel sayfalar yapabilirsin,
|
||||||
|
kreatif". Şehir sayfasının iskeleti (§4.17) ile: yol satırı, `SehirYuzeyi` başlık yüzeyi,
|
||||||
|
kimlik hapları (tür + öteki türe geçiş hapı), özet paragraf, kompakt `CtaSiraForm`, üç
|
||||||
|
`OzetKart` (üniversite / program / şehir; `grid grid-cols-2 gap-4 lg:grid-cols-3`, üçüncü
|
||||||
|
telefonda tam genişlik), "Şehre göre" çipleri (sayfa içi `#sehir-…` çapaları), şehir şehir
|
||||||
|
§4.15 `UniSatirlari`, altta mavi CTA bandı.
|
||||||
|
- **Kreatif:** başlık yüzeyindeki dot-matrix Türkiye'de bu türden üniversitesi olan **her il**
|
||||||
|
turuncu yanar (`TurkiyePikselHaritasi iller=[…]`), yakınlaşma yok — "devlet üniversiteleri
|
||||||
|
Türkiye'nin neresinde" tek bakışta. Vakıfta harita seyrek yanar; bu bilginin kendisidir.
|
||||||
|
- Tür çipi tek kaynak `TurCipi` (`src/components/tur-cipi.tsx`): devlet/vakıf (ve Vakıf MYO)
|
||||||
|
tür sayfasına link, KKTC/yurt dışı düz çip; `/sonuc`, `/listem`, tadımlık, bölüm sayfası ve
|
||||||
|
şehir/tür satırları bunu kullanır. Satırın tamamı link olan yerde kullanılmaz.
|
||||||
|
- `/universiteler` dizinindeki "Devlet Üniversiteleri" / "Vakıf Üniversiteleri" başlıkları da
|
||||||
|
tür sayfasına link.
|
||||||
|
|
||||||
## 5. Yerleşim
|
## 5. Yerleşim
|
||||||
|
|
||||||
### 5.1 Genel
|
### 5.1 Genel
|
||||||
@@ -812,3 +828,14 @@ yok** (Bilal, 24 Eyl 2026: kaldırıldı); `FUNNEL_METINLERI` yalnız arama penc
|
|||||||
- Çekmece başlığında denenen "gerçek liste" pikseli (12×2, her kare tercihin riski) **kaldırıldı**:
|
- Çekmece başlığında denenen "gerçek liste" pikseli (12×2, her kare tercihin riski) **kaldırıldı**:
|
||||||
başlığı çok dolu gösteriyordu (Bilal, 24 Eyl 2026). Başlık sayı + ilerleme çubuğu + risk
|
başlığı çok dolu gösteriyordu (Bilal, 24 Eyl 2026). Başlık sayı + ilerleme çubuğu + risk
|
||||||
dağılımı metniyle kalır.
|
dağılımı metniyle kalır.
|
||||||
|
|
||||||
|
### 10.9 Kayıtlı sıra satırı (katalog üstü, profilli) — `cta-sira-form.tsx` kompakt
|
||||||
|
Bilal (24 Eyl 2026): "bu kısım UI kötü, Apple görünümü yok, yazılar küçük". §1.1 "sayı
|
||||||
|
kahramandır" uygulanır: mavi bantta (§10.8 kompakt kabuk + `PikselZemin`) **iri kupa kutusu**
|
||||||
|
(`flex size-14 items-center justify-center rounded-2xl bg-white/15` + `Trophy size-7`), yanında
|
||||||
|
sıra `font-heading text-3xl font-bold leading-none tracking-tight sm:text-4xl` (Cal Sans'ta
|
||||||
|
tabular-nums yok, §3) ve
|
||||||
|
altında `text-sm font-semibold text-white/80` "sıra · Sayısal"; başlık `font-heading text-xl
|
||||||
|
font-bold leading-tight text-balance sm:text-2xl`; buton 48 px turuncu, ok sonda. Telefonda
|
||||||
|
başlık → sayı → buton alt alta ortalı; lg'de tek satır (sayı · başlık · buton). Yükseklik her
|
||||||
|
iki hâlde formu aşmaz (CLS).
|
||||||
|
|||||||
@@ -23,6 +23,18 @@ const staticEntries: MetadataRoute.Sitemap = [
|
|||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
changeFrequency: "weekly",
|
changeFrequency: "weekly",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
url: `${SITE_URL}/universiteler/devlet`,
|
||||||
|
lastModified: KATALOG_VERI_TARIHI,
|
||||||
|
priority: 0.7,
|
||||||
|
changeFrequency: "monthly",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${SITE_URL}/universiteler/vakif`,
|
||||||
|
lastModified: KATALOG_VERI_TARIHI,
|
||||||
|
priority: 0.7,
|
||||||
|
changeFrequency: "monthly",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: `${SITE_URL}/universite-siralamasi`,
|
url: `${SITE_URL}/universite-siralamasi`,
|
||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
|
|||||||
22
src/app/universiteler/devlet/page.tsx
Normal file
22
src/app/universiteler/devlet/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
|
import {
|
||||||
|
TurIcerik,
|
||||||
|
TurIcerikSkeleton,
|
||||||
|
turSayfaMetadata,
|
||||||
|
} from "@/features/katalog/components/tur-icerik";
|
||||||
|
|
||||||
|
// Statik segment: /universiteler/[il] dinamik rotasından önce eşleşir.
|
||||||
|
export const metadata: Metadata = turSayfaMetadata("devlet");
|
||||||
|
|
||||||
|
export default function DevletPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Suspense fallback={<TurIcerikSkeleton />}>
|
||||||
|
<TurIcerik tur="devlet" />
|
||||||
|
</Suspense>
|
||||||
|
<SiteFooter />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/app/universiteler/vakif/page.tsx
Normal file
22
src/app/universiteler/vakif/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
|
import {
|
||||||
|
TurIcerik,
|
||||||
|
TurIcerikSkeleton,
|
||||||
|
turSayfaMetadata,
|
||||||
|
} from "@/features/katalog/components/tur-icerik";
|
||||||
|
|
||||||
|
// Statik segment: /universiteler/[il] dinamik rotasından önce eşleşir.
|
||||||
|
export const metadata: Metadata = turSayfaMetadata("vakif");
|
||||||
|
|
||||||
|
export default function VakifPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Suspense fallback={<TurIcerikSkeleton />}>
|
||||||
|
<TurIcerik tur="vakif" />
|
||||||
|
</Suspense>
|
||||||
|
<SiteFooter />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
src/components/tur-cipi.tsx
Normal file
41
src/components/tur-cipi.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { SATIR_CIP } from "@/components/liste-satiri";
|
||||||
|
import { UNITUR_ETIKET, turSayfaHref } from "@/lib/unitur";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste satırlarındaki tür çipi (DESIGN.md §4.15) — tek kaynak. Devlet ve
|
||||||
|
* vakıf tür sayfasına link (Bilal, 24 Eyl 2026: "vakıf ve devlet de
|
||||||
|
* tıklanabilir olmalı"); KKTC/yurt dışı gibi tür sayfası olmayanlar düz çip.
|
||||||
|
* Hover §2.1 tek turuncu. Satırın tamamı link olan yerde kullanılmaz
|
||||||
|
* (iç içe <a>).
|
||||||
|
*/
|
||||||
|
export function TurCipi({
|
||||||
|
unitur,
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
unitur: string | null | undefined;
|
||||||
|
className?: string;
|
||||||
|
/** Tıklanabilir satırın içinde olayın satıra yayılmasını kesmek için */
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||||
|
}) {
|
||||||
|
if (!unitur) return null;
|
||||||
|
const etiket = UNITUR_ETIKET[unitur]?.cip ?? unitur;
|
||||||
|
const href = turSayfaHref(unitur);
|
||||||
|
if (!href) return <span className={cn(SATIR_CIP, className)}>{etiket}</span>;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
title={`Tüm ${etiket.toLocaleLowerCase("tr-TR")} üniversiteleri`}
|
||||||
|
onClick={onClick}
|
||||||
|
className={cn(
|
||||||
|
SATIR_CIP,
|
||||||
|
"transition-colors hover:bg-orange-50/60 hover:text-orange-500",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{etiket}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ import Link from "next/link";
|
|||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
||||||
|
import { TurCipi } from "@/components/tur-cipi";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
|
||||||
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||||
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
|
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
|
||||||
import {
|
import {
|
||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
SATIR_BUTON_BASLIK,
|
SATIR_BUTON_BASLIK,
|
||||||
SATIR_BUTON_HUCRE,
|
SATIR_BUTON_HUCRE,
|
||||||
SATIR_BUTON_KABI,
|
SATIR_BUTON_KABI,
|
||||||
SATIR_CIP,
|
|
||||||
SATIR_GRUP,
|
SATIR_GRUP,
|
||||||
SATIR_IKINCIL_LINK,
|
SATIR_IKINCIL_LINK,
|
||||||
SATIR_META,
|
SATIR_META,
|
||||||
@@ -242,12 +241,7 @@ export function BolumProgramListesi({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className={SATIR_META}>
|
<span className={SATIR_META}>
|
||||||
{p.unitur ? (
|
<TurCipi unitur={p.unitur} />
|
||||||
<span className={SATIR_CIP}>
|
|
||||||
{UNITUR_ETIKET[p.unitur]?.cip ??
|
|
||||||
trBaslikDuzeni(p.unitur)}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
{p.il ? (
|
{p.il ? (
|
||||||
<Link
|
<Link
|
||||||
href={ilSayfaHref(p.il)}
|
href={ilSayfaHref(p.il)}
|
||||||
|
|||||||
@@ -2,16 +2,6 @@ import type { Metadata } from "next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { Building2, ChevronRight, Landmark, MapPin } from "lucide-react";
|
import { Building2, ChevronRight, Landmark, MapPin } from "lucide-react";
|
||||||
import {
|
|
||||||
SATIR_AD_DUZ,
|
|
||||||
SATIR_CIP,
|
|
||||||
SATIR_GRUP,
|
|
||||||
SATIR_META,
|
|
||||||
} from "@/components/liste-satiri";
|
|
||||||
import { UniLogo } from "@/components/uni-logo";
|
|
||||||
import { trBaslikDuzeni } from "@/lib/slug";
|
|
||||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
import {
|
||||||
getAllIller,
|
getAllIller,
|
||||||
getAllIlSlugs,
|
getAllIlSlugs,
|
||||||
@@ -32,6 +22,7 @@ import { NavBaslik } from "./nav-baslik";
|
|||||||
import { SehirCipleri, oneCikanIller } from "./sehir-cipleri";
|
import { SehirCipleri, oneCikanIller } from "./sehir-cipleri";
|
||||||
import { SehirPikselHaritasi } from "./sehir-piksel-haritasi";
|
import { SehirPikselHaritasi } from "./sehir-piksel-haritasi";
|
||||||
import { SehirYuzeyi } from "./sehir-yuzeyi";
|
import { SehirYuzeyi } from "./sehir-yuzeyi";
|
||||||
|
import { UniSatirlari } from "./uni-satirlari";
|
||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
@@ -55,57 +46,6 @@ export function ilSayfaMetadata(slug: string): Metadata {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Şehir sayfasının üniversite satırları — DESIGN.md §4.15 liste satırı
|
|
||||||
* (Bilal, 24 Eyl 2026: "il kısmında bulunan üniler küçük gözüküyor"): 48 px
|
|
||||||
* logo, `text-base font-semibold` ad, altında tür çipi + program sayısı,
|
|
||||||
* satırın tamamı üniversite sayfasına link (44 px'ten iri dokunma hedefi).
|
|
||||||
* Ad hover'ı §2.1 istisnasıyla mavi, satır zemini turuncuya döner, sağdaki
|
|
||||||
* ok kayar. Üniversitenin ana ili bu sayfadan farklıysa (ODTÜ KKTC sayfasında)
|
|
||||||
* il de metada yazar.
|
|
||||||
*/
|
|
||||||
function UniSatirlari({ uniler, buIl }: { uniler: UniOzet[]; buIl: string }) {
|
|
||||||
return (
|
|
||||||
<ol className="mt-4 divide-y divide-slate-100 overflow-hidden rounded-xl border border-slate-200 bg-white text-left">
|
|
||||||
{uniler.map((u) => {
|
|
||||||
const tur = u.unitur ? UNITUR_ETIKET[u.unitur]?.cip : null;
|
|
||||||
const digerIl = u.il && u.il !== buIl ? trBaslikDuzeni(u.il) : null;
|
|
||||||
return (
|
|
||||||
<li key={u.slug}>
|
|
||||||
<Link
|
|
||||||
href={`/universite/${u.slug}`}
|
|
||||||
className="group flex items-start gap-3 px-3 py-4 transition-[background-color,transform] duration-150 hover:bg-orange-50/60 active:scale-[0.99] sm:items-center sm:gap-4 sm:px-5 lg:py-3"
|
|
||||||
>
|
|
||||||
<UniLogo slug={u.slug} ad={u.ad} boy="sm" />
|
|
||||||
<span className="min-w-0 flex-1">
|
|
||||||
<span className={SATIR_GRUP}>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
SATIR_AD_DUZ,
|
|
||||||
"transition-colors group-hover:text-primary group-hover:underline",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{u.ad}
|
|
||||||
</span>
|
|
||||||
<span className={SATIR_META}>
|
|
||||||
{tur ? <span className={SATIR_CIP}>{tur}</span> : null}
|
|
||||||
{digerIl ? <span className="shrink-0">{digerIl}</span> : null}
|
|
||||||
<span className="shrink-0">{sayi(u.programSayisi)} program</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<ChevronRight
|
|
||||||
className="mt-3 size-4 shrink-0 text-slate-400 transition-[color,transform] duration-150 group-hover:translate-x-0.5 group-hover:text-orange-500 sm:mt-0"
|
|
||||||
aria-hidden
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ol>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function UniGrubu({
|
function UniGrubu({
|
||||||
baslik,
|
baslik,
|
||||||
uniler,
|
uniler,
|
||||||
@@ -124,7 +64,7 @@ function UniGrubu({
|
|||||||
{sayi(uniler.length)}
|
{sayi(uniler.length)}
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<UniSatirlari uniler={uniler} buIl={buIl} />
|
<UniSatirlari uniler={uniler} gizliIl={buIl} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,14 +237,23 @@ function PikselSvg({
|
|||||||
|
|
||||||
const TUM_IL_HUCRELERI = Object.values(IL_HUCRELERI).flat();
|
const TUM_IL_HUCRELERI = Object.values(IL_HUCRELERI).flat();
|
||||||
|
|
||||||
/** Türkiye; `il` (DB biçimi, ör. "İZMİR") turuncu karelerle yanar. */
|
/**
|
||||||
|
* Türkiye; `il` (DB biçimi, ör. "İZMİR") turuncu karelerle yanar. `iller`
|
||||||
|
* verilirse hepsi yanar (tür sayfası: devlet üniversitesi olan her il) ve
|
||||||
|
* yakınlaşma yapılmaz — ülkenin tamamı görünür.
|
||||||
|
*/
|
||||||
export function TurkiyePikselHaritasi({
|
export function TurkiyePikselHaritasi({
|
||||||
il,
|
il,
|
||||||
|
iller,
|
||||||
...props
|
...props
|
||||||
}: HaritaProps & { il: string }) {
|
}: HaritaProps & { il?: string; iller?: readonly string[] }) {
|
||||||
const harita = markup(`tr:${il}`, () =>
|
const liste = iller ?? (il ? [il] : []);
|
||||||
markupUret(TUM_IL_HUCRELERI, IL_HUCRELERI[il] ?? [], null),
|
const anahtar = `tr:${liste.join("|")}`;
|
||||||
);
|
const harita = markup(anahtar, () => {
|
||||||
|
const vurgu = liste.flatMap((ad) => IL_HUCRELERI[ad] ?? []);
|
||||||
|
const h = markupUret(TUM_IL_HUCRELERI, vurgu, null);
|
||||||
|
return iller ? { ...h, sinir: null } : h;
|
||||||
|
});
|
||||||
return <PikselSvg harita={harita} {...props} />;
|
return <PikselSvg harita={harita} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
279
src/features/katalog/components/tur-icerik.tsx
Normal file
279
src/features/katalog/components/tur-icerik.tsx
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
Building2,
|
||||||
|
ChevronRight,
|
||||||
|
Landmark,
|
||||||
|
LayoutList,
|
||||||
|
MapPin,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { getAllUniversiteler, type UniOzet } from "../katalog-queries";
|
||||||
|
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
||||||
|
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||||
|
import { OzetKart } from "@/components/ozet-kart";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import {
|
||||||
|
JsonLd,
|
||||||
|
breadcrumbJsonLd,
|
||||||
|
sayfaMetadata,
|
||||||
|
ustSegmentOgGorseli,
|
||||||
|
} from "@/lib/seo";
|
||||||
|
import { ilSayfaHref, trBaslikDuzeni, turkishSlugify } from "@/lib/slug";
|
||||||
|
import { TUR_SAYFALARI, type TurSlug } from "@/lib/unitur";
|
||||||
|
import { BASLIK_YILI, SON_YIL, TERCIH_YILI } from "@/lib/veri-yillari";
|
||||||
|
import { NavBaslik } from "./nav-baslik";
|
||||||
|
import { TurkiyePikselHaritasi } from "./sehir-piksel-haritasi";
|
||||||
|
import { SehirYuzeyi } from "./sehir-yuzeyi";
|
||||||
|
import { UniSatirlari } from "./uni-satirlari";
|
||||||
|
|
||||||
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
|
function turUnileri(tur: TurSlug): UniOzet[] {
|
||||||
|
const set = new Set<string>(TUR_SAYFALARI[tur].uniturler);
|
||||||
|
return getAllUniversiteler().filter((u) => u.unitur && set.has(u.unitur));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function turSayfaMetadata(tur: TurSlug): Metadata {
|
||||||
|
const t = TUR_SAYFALARI[tur];
|
||||||
|
const uniler = turUnileri(tur);
|
||||||
|
const program = uniler.reduce((a, u) => a + u.programSayisi, 0);
|
||||||
|
return sayfaMetadata({
|
||||||
|
title: `${t.ad} Taban Puanları ve Sıralamaları ${BASLIK_YILI}`,
|
||||||
|
description: `Türkiye'deki ${uniler.length} ${t.tekil}nin ${SON_YIL} YKS yerleştirme verileri: ${sayi(program)} programın taban puanları, başarı sıralamaları ve kontenjanları, şehir şehir. ${TERCIH_YILI} tercih rehberi.`,
|
||||||
|
path: `/universiteler/${t.slug}`,
|
||||||
|
openGraph: {
|
||||||
|
images: ustSegmentOgGorseli(
|
||||||
|
"/universiteler",
|
||||||
|
"Üniversitelerin taban puanları ve sıralamaları — KolayTercih",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /universiteler/devlet ve /universiteler/vakif (DESIGN.md §4.18). Şehir
|
||||||
|
* sayfasıyla aynı iskelet; kreatif fark: başlık yüzeyindeki dot-matrix
|
||||||
|
* Türkiye'de bu türden üniversitesi olan **her il** turuncu yanar, yakınlaşma
|
||||||
|
* yok — "devlet üniversiteleri Türkiye'nin neresinde" tek bakışta. Altında
|
||||||
|
* üç özet kartı, şehir çipleri (bu türün il başına sayısı) ve şehir şehir
|
||||||
|
* §4.15 liste satırları.
|
||||||
|
*/
|
||||||
|
export function TurIcerik({ tur }: { tur: TurSlug }) {
|
||||||
|
const t = TUR_SAYFALARI[tur];
|
||||||
|
const uniler = turUnileri(tur);
|
||||||
|
const program = uniler.reduce((a, u) => a + u.programSayisi, 0);
|
||||||
|
// Şehir öbekleri: üniversitenin ana ili (UniOzet.il); ili boş olan sona.
|
||||||
|
const ilOzet = new Map<string, { il: string; ad: string; uniler: UniOzet[] }>();
|
||||||
|
const ilsiz: UniOzet[] = [];
|
||||||
|
for (const u of uniler) {
|
||||||
|
if (!u.il) {
|
||||||
|
ilsiz.push(u);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let k = ilOzet.get(u.il);
|
||||||
|
if (!k) {
|
||||||
|
k = { il: u.il, ad: trBaslikDuzeni(u.il), uniler: [] };
|
||||||
|
ilOzet.set(u.il, k);
|
||||||
|
}
|
||||||
|
k.uniler.push(u);
|
||||||
|
}
|
||||||
|
const sehirler = [...ilOzet.values()].sort((a, b) =>
|
||||||
|
a.ad.localeCompare(b.ad, "tr-TR"),
|
||||||
|
);
|
||||||
|
const cipSehirler = [...sehirler].sort(
|
||||||
|
(a, b) =>
|
||||||
|
b.uniler.length - a.uniler.length || a.ad.localeCompare(b.ad, "tr-TR"),
|
||||||
|
);
|
||||||
|
const Ikon = tur === "devlet" ? Landmark : Building2;
|
||||||
|
const DigerIkon = tur === "devlet" ? Building2 : Landmark;
|
||||||
|
const ctaBaslik = `Sıralaman ${t.tekil} tabanlarının neresinde?`;
|
||||||
|
const digerTur = tur === "devlet" ? TUR_SAYFALARI.vakif : TUR_SAYFALARI.devlet;
|
||||||
|
|
||||||
|
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: t.ad, path: `/universiteler/${t.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">{t.ad}</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<NavBaslik baslikId="tur-baslik" ad={t.ad} />
|
||||||
|
<SehirYuzeyi
|
||||||
|
harita={
|
||||||
|
<TurkiyePikselHaritasi
|
||||||
|
iller={sehirler.map((s) => s.il)}
|
||||||
|
etiket={`${t.ad} Türkiye haritasında: ${sehirler.length} il`}
|
||||||
|
doldur
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
metin={
|
||||||
|
<div className="text-center lg:text-left">
|
||||||
|
<h1
|
||||||
|
id="tur-baslik"
|
||||||
|
className="font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl"
|
||||||
|
>
|
||||||
|
{t.ad} Taban Puanları {BASLIK_YILI}
|
||||||
|
</h1>
|
||||||
|
{/* Kimlik hapları (§4.12): tür + öteki türe geçiş */}
|
||||||
|
<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">
|
||||||
|
<Ikon className="size-4 text-primary" aria-hidden />
|
||||||
|
{tur === "devlet" ? "Devlet" : "Vakıf"}
|
||||||
|
</span>
|
||||||
|
<Link
|
||||||
|
href={`/universiteler/${digerTur.slug}`}
|
||||||
|
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 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]"
|
||||||
|
>
|
||||||
|
<DigerIkon className="size-4 text-primary" aria-hidden />
|
||||||
|
{digerTur.ad}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
<p className="mx-auto mt-6 max-w-2xl text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0">
|
||||||
|
Türkiye'de {SON_YIL} YKS yerleştirmesinde öğrenci alan{" "}
|
||||||
|
<strong>
|
||||||
|
{sayi(uniler.length)} {t.tekil}
|
||||||
|
</strong>
|
||||||
|
, <strong>{sayi(sehirler.length)} şehirde</strong> toplam{" "}
|
||||||
|
<strong>{sayi(program)} program</strong> açtı. Haritada turuncu
|
||||||
|
yanan her ilde en az bir {t.tekil} 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; {TERCIH_YILI} tercihleri
|
||||||
|
için rehber niteliğindedir.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-8">
|
||||||
|
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-3">
|
||||||
|
<OzetKart
|
||||||
|
ikon={Ikon}
|
||||||
|
etiket="Üniversite"
|
||||||
|
deger={sayi(uniler.length)}
|
||||||
|
alt={`${SON_YIL}'de öğrenci alan`}
|
||||||
|
/>
|
||||||
|
<OzetKart
|
||||||
|
ikon={LayoutList}
|
||||||
|
etiket="Program"
|
||||||
|
deger={sayi(program)}
|
||||||
|
alt="lisans ve önlisans"
|
||||||
|
/>
|
||||||
|
<OzetKart
|
||||||
|
ikon={MapPin}
|
||||||
|
etiket="Şehir"
|
||||||
|
deger={sayi(sehirler.length)}
|
||||||
|
alt="en az bir kampüs"
|
||||||
|
className="col-span-2 lg:col-span-1"
|
||||||
|
/>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<PagePixelDivider seed={73} className="mx-auto mt-8 lg:mx-0" />
|
||||||
|
|
||||||
|
<section
|
||||||
|
className="mt-8 text-center lg:text-left"
|
||||||
|
aria-labelledby="tur-sehirler"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
id="tur-sehirler"
|
||||||
|
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(sehirler.length)} şehir
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<ul className="mt-4 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
|
||||||
|
{cipSehirler.map((s) => (
|
||||||
|
<li key={s.il}>
|
||||||
|
<a
|
||||||
|
href={`#sehir-${turkishSlugify(s.il)}`}
|
||||||
|
className="inline-flex h-10 cursor-pointer items-center gap-1.5 rounded-full border border-slate-200 bg-white px-3.5 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]"
|
||||||
|
>
|
||||||
|
{s.ad}
|
||||||
|
<span className="font-normal tabular-nums text-slate-400">
|
||||||
|
{sayi(s.uniler.length)}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{sehirler.map((s) => (
|
||||||
|
<section
|
||||||
|
key={s.il}
|
||||||
|
id={`sehir-${turkishSlugify(s.il)}`}
|
||||||
|
className="mt-10 scroll-mt-24 text-center lg:text-left"
|
||||||
|
>
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
<Link
|
||||||
|
href={ilSayfaHref(s.il)}
|
||||||
|
className="transition-colors hover:text-orange-500 hover:underline"
|
||||||
|
>
|
||||||
|
{s.ad}
|
||||||
|
</Link>
|
||||||
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
|
{sayi(s.uniler.length)}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<UniSatirlari uniler={s.uniler} gizliIl={s.il} />
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
{ilsiz.length > 0 ? (
|
||||||
|
<section className="mt-10 text-center lg:text-left">
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
Diğer
|
||||||
|
</h2>
|
||||||
|
<UniSatirlari uniler={ilsiz} />
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="mt-12">
|
||||||
|
<CtaSiraForm
|
||||||
|
baslik={ctaBaslik}
|
||||||
|
aciklama={`YKS başarı sıralamanı gir; ${t.tekil} programları dahil sıralamana uygun hayal, dengeli ve güvenli dilimlerindeki programları anında listele. Ücretsiz, üyelik gerekmez.`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TurIcerikSkeleton() {
|
||||||
|
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-8 grid grid-cols-2 gap-4 lg:grid-cols-3">
|
||||||
|
<Skeleton className="h-28 rounded-2xl" />
|
||||||
|
<Skeleton className="h-28 rounded-2xl" />
|
||||||
|
<Skeleton className="col-span-2 h-28 rounded-2xl lg:col-span-1" />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
67
src/features/katalog/components/uni-satirlari.tsx
Normal file
67
src/features/katalog/components/uni-satirlari.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
SATIR_AD,
|
||||||
|
SATIR_GRUP,
|
||||||
|
SATIR_IKINCIL_LINK,
|
||||||
|
SATIR_META,
|
||||||
|
} from "@/components/liste-satiri";
|
||||||
|
import { TurCipi } from "@/components/tur-cipi";
|
||||||
|
import { UniLogo } from "@/components/uni-logo";
|
||||||
|
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
||||||
|
import type { UniOzet } from "../katalog-queries";
|
||||||
|
|
||||||
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Şehir ve tür sayfalarının üniversite satırları — DESIGN.md §4.15 liste
|
||||||
|
* satırı (Bilal, 24 Eyl 2026: "il kısmında üniler küçük gözüküyor"): 48 px
|
||||||
|
* logo, `SATIR_AD` ad (hover mavi, §2.1 istisnası), altında tür çipi
|
||||||
|
* (tür sayfasına link) + il (şehir sayfasına link) + program sayısı. Satırın
|
||||||
|
* tamamı link değildir: tür ve il ayrı hedeflere gider (iç içe <a> olmaz).
|
||||||
|
* `gizliIl`: bulunulan şehir sayfasında aynı il yazılmaz.
|
||||||
|
*/
|
||||||
|
export function UniSatirlari({
|
||||||
|
uniler,
|
||||||
|
gizliIl,
|
||||||
|
}: {
|
||||||
|
uniler: UniOzet[];
|
||||||
|
gizliIl?: string | null;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ol className="mt-4 divide-y divide-slate-100 overflow-hidden rounded-xl border border-slate-200 bg-white text-left">
|
||||||
|
{uniler.map((u) => {
|
||||||
|
const il = u.il && u.il !== gizliIl ? u.il : null;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={u.slug}
|
||||||
|
className="flex items-start gap-3 px-3 py-4 sm:items-center sm:gap-4 sm:px-5 lg:py-3"
|
||||||
|
>
|
||||||
|
<Link href={`/universite/${u.slug}`} tabIndex={-1} aria-hidden>
|
||||||
|
<UniLogo slug={u.slug} ad={u.ad} boy="sm" />
|
||||||
|
</Link>
|
||||||
|
<span className="min-w-0 flex-1">
|
||||||
|
<span className={SATIR_GRUP}>
|
||||||
|
<Link href={`/universite/${u.slug}`} className={SATIR_AD}>
|
||||||
|
{u.ad}
|
||||||
|
</Link>
|
||||||
|
<span className={SATIR_META}>
|
||||||
|
<TurCipi unitur={u.unitur} />
|
||||||
|
{il ? (
|
||||||
|
<Link
|
||||||
|
href={ilSayfaHref(il)}
|
||||||
|
className={`shrink-0 ${SATIR_IKINCIL_LINK}`}
|
||||||
|
title={`${trBaslikDuzeni(il)} üniversiteleri`}
|
||||||
|
>
|
||||||
|
{trBaslikDuzeni(il)}
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
<span className="shrink-0">{sayi(u.programSayisi)} program</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
bolumSlugFromIsim,
|
bolumSlugFromIsim,
|
||||||
} from "../katalog-queries";
|
} from "../katalog-queries";
|
||||||
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
||||||
import { UNITUR_ETIKET, uniturDevletMi } from "@/lib/unitur";
|
import { UNITUR_ETIKET, turSayfaHref, uniturDevletMi } from "@/lib/unitur";
|
||||||
import {
|
import {
|
||||||
JsonLd,
|
JsonLd,
|
||||||
breadcrumbJsonLd,
|
breadcrumbJsonLd,
|
||||||
@@ -569,14 +569,31 @@ export function UniversiteIcerik({
|
|||||||
{turEtiketi || uni.il ? (
|
{turEtiketi || uni.il ? (
|
||||||
<p className="mt-5 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
|
<p className="mt-5 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
|
||||||
{turEtiketi ? (
|
{turEtiketi ? (
|
||||||
<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/vakıf hapı tür sayfasına gider (DESIGN.md §4.12); KKTC
|
||||||
{uniturDevletMi(uni.unitur) ? (
|
// ve yurt dışında tür sayfası yok, hap düz kalır.
|
||||||
|
(() => {
|
||||||
|
const href = turSayfaHref(uni.unitur);
|
||||||
|
const ikon = uniturDevletMi(uni.unitur) ? (
|
||||||
<Landmark className="size-4 text-primary" aria-hidden />
|
<Landmark className="size-4 text-primary" aria-hidden />
|
||||||
) : (
|
) : (
|
||||||
<Building2 className="size-4 text-primary" aria-hidden />
|
<Building2 className="size-4 text-primary" aria-hidden />
|
||||||
)}
|
);
|
||||||
|
return href ? (
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
title={`Tüm ${turEtiketi.cip.toLocaleLowerCase("tr-TR")} üniversiteleri`}
|
||||||
|
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 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]"
|
||||||
|
>
|
||||||
|
{ikon}
|
||||||
|
{turEtiketi.cip}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<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">
|
||||||
|
{ikon}
|
||||||
{turEtiketi.cip}
|
{turEtiketi.cip}
|
||||||
</span>
|
</span>
|
||||||
|
);
|
||||||
|
})()
|
||||||
) : null}
|
) : null}
|
||||||
{uni.il ? (
|
{uni.il ? (
|
||||||
// İl hapı şehir sayfasına gider (Bilal, 24 Eyl 2026: "şehirlerin
|
// İl hapı şehir sayfasına gider (Bilal, 24 Eyl 2026: "şehirlerin
|
||||||
|
|||||||
@@ -107,7 +107,12 @@ export function UniversiteKatalogu() {
|
|||||||
|
|
||||||
<section className="mt-8">
|
<section className="mt-8">
|
||||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
<Link
|
||||||
|
href="/universiteler/devlet"
|
||||||
|
className="transition-colors hover:text-orange-500 hover:underline"
|
||||||
|
>
|
||||||
Devlet Üniversiteleri
|
Devlet Üniversiteleri
|
||||||
|
</Link>
|
||||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
{sayi(devlet.length)}
|
{sayi(devlet.length)}
|
||||||
</span>
|
</span>
|
||||||
@@ -117,7 +122,12 @@ export function UniversiteKatalogu() {
|
|||||||
|
|
||||||
<section className="mt-10">
|
<section className="mt-10">
|
||||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
<Link
|
||||||
|
href="/universiteler/vakif"
|
||||||
|
className="transition-colors hover:text-orange-500 hover:underline"
|
||||||
|
>
|
||||||
Vakıf Üniversiteleri
|
Vakıf Üniversiteleri
|
||||||
|
</Link>
|
||||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
{sayi(vakif.length)}
|
{sayi(vakif.length)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import type {
|
|||||||
} from "@/types/yokatlas";
|
} from "@/types/yokatlas";
|
||||||
import { DILIM_ETIKET, riskHesapla } from "@/lib/risk";
|
import { DILIM_ETIKET, riskHesapla } from "@/lib/risk";
|
||||||
import { bolumSayfaSlug, ilSayfaHref, trBaslikDuzeni, uniSayfaSlug } from "@/lib/slug";
|
import { bolumSayfaSlug, ilSayfaHref, trBaslikDuzeni, uniSayfaSlug } from "@/lib/slug";
|
||||||
|
import { TurCipi } from "@/components/tur-cipi";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||||
import {
|
import {
|
||||||
@@ -52,7 +53,6 @@ import {
|
|||||||
SATIR_BUTON_BASLIK,
|
SATIR_BUTON_BASLIK,
|
||||||
SATIR_BUTON_HUCRE,
|
SATIR_BUTON_HUCRE,
|
||||||
SATIR_BUTON_KABI,
|
SATIR_BUTON_KABI,
|
||||||
SATIR_CIP,
|
|
||||||
SATIR_ALT_GRUP,
|
SATIR_ALT_GRUP,
|
||||||
SATIR_IKINCIL_LINK,
|
SATIR_IKINCIL_LINK,
|
||||||
SATIR_META,
|
SATIR_META,
|
||||||
@@ -334,7 +334,6 @@ function ProgramSatiri({
|
|||||||
}) {
|
}) {
|
||||||
const taban = programEtkinSira(p);
|
const taban = programEtkinSira(p);
|
||||||
const risk = riskHesapla(taban, adaySira);
|
const risk = riskHesapla(taban, adaySira);
|
||||||
const devlet = p.unitur === "DEVLET";
|
|
||||||
const tabanMetni = taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—";
|
const tabanMetni = taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—";
|
||||||
const bosKaldi =
|
const bosKaldi =
|
||||||
p.kontenjanSon != null &&
|
p.kontenjanSon != null &&
|
||||||
@@ -373,7 +372,10 @@ function ProgramSatiri({
|
|||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
<span className={SATIR_META}>
|
<span className={SATIR_META}>
|
||||||
<span className={SATIR_CIP}>{devlet ? "Devlet" : "Vakıf"}</span>
|
<TurCipi
|
||||||
|
unitur={p.unitur}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
{p.il ? (
|
{p.il ? (
|
||||||
<Link
|
<Link
|
||||||
href={ilSayfaHref(p.il)}
|
href={ilSayfaHref(p.il)}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { bolumSayfaSlug, ilSayfaHref, trBaslikDuzeni, uniSayfaSlug } from "@/lib/slug";
|
import { bolumSayfaSlug, ilSayfaHref, trBaslikDuzeni, uniSayfaSlug } from "@/lib/slug";
|
||||||
|
import { TurCipi } from "@/components/tur-cipi";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
import { UNITUR_ETIKET } from "@/lib/unitur";
|
||||||
import { RiskEtiketi } from "@/components/risk-etiketi";
|
import { RiskEtiketi } from "@/components/risk-etiketi";
|
||||||
@@ -38,7 +39,6 @@ import {
|
|||||||
SATIR_BASLIK,
|
SATIR_BASLIK,
|
||||||
SATIR_BUTON_BASLIK,
|
SATIR_BUTON_BASLIK,
|
||||||
SATIR_BUTON_HUCRE,
|
SATIR_BUTON_HUCRE,
|
||||||
SATIR_CIP,
|
|
||||||
SATIR_DUGME_BOYU,
|
SATIR_DUGME_BOYU,
|
||||||
SATIR_ALT_GRUP,
|
SATIR_ALT_GRUP,
|
||||||
SATIR_IKINCIL_LINK,
|
SATIR_IKINCIL_LINK,
|
||||||
@@ -201,7 +201,6 @@ export function RaporListesi({
|
|||||||
const seri = raporSiraSerisi(p?.siraGecmisi, p?.ilkYil);
|
const seri = raporSiraSerisi(p?.siraGecmisi, p?.ilkYil);
|
||||||
const taban = p?.efektifSira ?? programEtkinSira(seri);
|
const taban = p?.efektifSira ?? programEtkinSira(seri);
|
||||||
const acikMi = acik.has(t.sira);
|
const acikMi = acik.has(t.sira);
|
||||||
const devlet = p?.unitur === "DEVLET";
|
|
||||||
const tabanMetni = taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—";
|
const tabanMetni = taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—";
|
||||||
const riskHucresi = analizKilitli ? (
|
const riskHucresi = analizKilitli ? (
|
||||||
<span className="inline-flex items-center gap-1.5 text-xs font-semibold text-slate-500">
|
<span className="inline-flex items-center gap-1.5 text-xs font-semibold text-slate-500">
|
||||||
@@ -263,9 +262,10 @@ export function RaporListesi({
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className={SATIR_META}>
|
<span className={SATIR_META}>
|
||||||
{p?.unitur ? (
|
<TurCipi
|
||||||
<span className={SATIR_CIP}>{devlet ? "Devlet" : "Vakıf"}</span>
|
unitur={p?.unitur}
|
||||||
) : null}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
{p?.il ? (
|
{p?.il ? (
|
||||||
<Link
|
<Link
|
||||||
href={ilSayfaHref(p.il)}
|
href={ilSayfaHref(p.il)}
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ import { olay, siraKovasi } from "@/lib/analitik";
|
|||||||
import { dilimdenRisk, riskHesapla } from "@/lib/risk";
|
import { dilimdenRisk, riskHesapla } from "@/lib/risk";
|
||||||
import { ilSayfaHref, trBaslikDuzeni, turkishSlugify } from "@/lib/slug";
|
import { ilSayfaHref, trBaslikDuzeni, turkishSlugify } from "@/lib/slug";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { TurCipi } from "@/components/tur-cipi";
|
||||||
import {
|
import {
|
||||||
SATIR_ALT_AD,
|
SATIR_ALT_AD,
|
||||||
SATIR_AD_DUZ,
|
SATIR_AD_DUZ,
|
||||||
SATIR_CIP,
|
|
||||||
SATIR_ALT_GRUP,
|
SATIR_ALT_GRUP,
|
||||||
SATIR_IKINCIL_LINK,
|
SATIR_IKINCIL_LINK,
|
||||||
SATIR_META,
|
SATIR_META,
|
||||||
@@ -123,7 +123,6 @@ export function TadimlikSatiri({
|
|||||||
const risk = riskHesapla(p.efektifSira, sira) ?? dilimdenRisk(satir.dilim);
|
const risk = riskHesapla(p.efektifSira, sira) ?? dilimdenRisk(satir.dilim);
|
||||||
const seri = raporSiraSerisi(p.siraGecmisi, p.ilkYil);
|
const seri = raporSiraSerisi(p.siraGecmisi, p.ilkYil);
|
||||||
const taban = p.efektifSira ?? programEtkinSira(seri);
|
const taban = p.efektifSira ?? programEtkinSira(seri);
|
||||||
const devlet = p.unitur === "DEVLET";
|
|
||||||
// Giriş dönüşü kullanıcıyı /sonuc'a geri bırakmaz: callback doğrudan
|
// Giriş dönüşü kullanıcıyı /sonuc'a geri bırakmaz: callback doğrudan
|
||||||
// üretimin koştuğu /listem'e iner, liste ek tık olmadan orada başlar.
|
// üretimin koştuğu /listem'e iner, liste ek tık olmadan orada başlar.
|
||||||
const girisUrl = `/giris?callback=${encodeURIComponent(listemUretHref("tadimlik"))}`;
|
const girisUrl = `/giris?callback=${encodeURIComponent(listemUretHref("tadimlik"))}`;
|
||||||
@@ -164,9 +163,7 @@ export function TadimlikSatiri({
|
|||||||
{trBaslikDuzeni(p.universite)}
|
{trBaslikDuzeni(p.universite)}
|
||||||
</span>
|
</span>
|
||||||
<span className={SATIR_META}>
|
<span className={SATIR_META}>
|
||||||
{p.unitur ? (
|
<TurCipi unitur={p.unitur} />
|
||||||
<span className={SATIR_CIP}>{devlet ? "Devlet" : "Vakıf"}</span>
|
|
||||||
) : null}
|
|
||||||
{p.il ? (
|
{p.il ? (
|
||||||
<Link
|
<Link
|
||||||
href={ilSayfaHref(p.il)}
|
href={ilSayfaHref(p.il)}
|
||||||
|
|||||||
@@ -408,17 +408,39 @@ export function CtaSiraForm({
|
|||||||
<div className="mt-3">{siraFormu}</div>
|
<div className="mt-3">{siraFormu}</div>
|
||||||
</div>
|
</div>
|
||||||
{profil ? (
|
{profil ? (
|
||||||
<div className="col-start-1 row-start-1 flex flex-col items-center justify-center gap-3 text-center sm:flex-row sm:justify-between sm:gap-6 sm:text-left">
|
// Şişko ve sade (DESIGN.md §1.1, §10.9): sayı kahramandır — iri
|
||||||
<div className="flex min-w-0 flex-col items-center gap-1 sm:items-start">
|
// kupa kutusu + text-3xl/4xl sıra, başlık text-xl/2xl, 48 px buton.
|
||||||
|
// Telefonda başlık → sayı → buton alt alta; lg'de tek satır
|
||||||
|
// (sayı · başlık · buton). Yükseklik formunkini aşmaz (CLS notu).
|
||||||
|
<div className="col-start-1 row-start-1 flex flex-col items-center justify-center gap-4 text-center lg:flex-row lg:gap-6 lg:text-left">
|
||||||
<p
|
<p
|
||||||
id={baslikId}
|
id={baslikId}
|
||||||
className="font-heading text-base font-bold leading-6 text-white"
|
className="order-1 min-w-0 flex-1 font-heading text-xl font-bold leading-tight text-balance text-white sm:text-2xl lg:order-2"
|
||||||
>
|
>
|
||||||
{kompaktBaslik}
|
{kompaktBaslik}
|
||||||
</p>
|
</p>
|
||||||
<KayitliSira sira={profil.sira} tur={profil.tur} beyaz />
|
<p className="order-2 flex items-center gap-3 lg:order-1 lg:shrink-0">
|
||||||
</div>
|
<span className="flex size-14 shrink-0 items-center justify-center rounded-2xl bg-white/15">
|
||||||
<Button asChild size="lg" className={cn(PROFILLI_CTA_SINIF, "w-full sm:w-auto")}>
|
<Trophy className="size-7 text-white" aria-hidden />
|
||||||
|
</span>
|
||||||
|
<span className="flex flex-col items-start text-left">
|
||||||
|
<span className="sr-only">Sıralaman kayıtlı:</span>
|
||||||
|
<span className="font-heading text-3xl font-bold leading-none tracking-tight text-white sm:text-4xl">
|
||||||
|
{profil.sira.toLocaleString("tr-TR")}.
|
||||||
|
</span>
|
||||||
|
<span className="mt-1 text-sm font-semibold text-white/80">
|
||||||
|
sıra · {turEtiketi(profil.tur)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
className={cn(
|
||||||
|
PROFILLI_CTA_SINIF,
|
||||||
|
"order-3 w-full active:scale-[0.97] sm:w-auto lg:shrink-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Link href={sonucHref()}>
|
<Link href={sonucHref()}>
|
||||||
Sıralamana uygun programları gör
|
Sıralamana uygun programları gör
|
||||||
<ArrowRight className="size-4" aria-hidden />
|
<ArrowRight className="size-4" aria-hidden />
|
||||||
|
|||||||
@@ -35,3 +35,39 @@ export const UNITUR_ETIKET: Record<string, { cip: string; cumle: string }> = {
|
|||||||
*/
|
*/
|
||||||
export const uniturDevletMi = (unitur: string | null | undefined) =>
|
export const uniturDevletMi = (unitur: string | null | undefined) =>
|
||||||
unitur === "DEVLET";
|
unitur === "DEVLET";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tür sayfaları (/universiteler/devlet, /universiteler/vakif; DESIGN.md
|
||||||
|
* §4.18). Bilal, 24 Eyl 2026: "vakıf ve devlet de tıklanabilir olmalı".
|
||||||
|
* Vakıf MYO'lar vakıf sayfasında sayılır; KKTC ve yurt dışının tür sayfası
|
||||||
|
* yoktur (şehir sayfalarında zaten ayrı öbek).
|
||||||
|
*/
|
||||||
|
export const TUR_SAYFALARI = {
|
||||||
|
devlet: {
|
||||||
|
slug: "devlet",
|
||||||
|
ad: "Devlet Üniversiteleri",
|
||||||
|
tekil: "devlet üniversitesi",
|
||||||
|
uniturler: ["DEVLET"],
|
||||||
|
},
|
||||||
|
vakif: {
|
||||||
|
slug: "vakif",
|
||||||
|
ad: "Vakıf Üniversiteleri",
|
||||||
|
tekil: "vakıf üniversitesi",
|
||||||
|
uniturler: ["VAKIF", "VAKIF MYO"],
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type TurSlug = keyof typeof TUR_SAYFALARI;
|
||||||
|
|
||||||
|
export const turSlugMu = (s: string): s is TurSlug => s in TUR_SAYFALARI;
|
||||||
|
|
||||||
|
/** Ham unitur'dan tür sayfası adresi; tür sayfası olmayanlarda null. */
|
||||||
|
export function turSayfaHref(unitur: string | null | undefined): string | null {
|
||||||
|
if (!unitur) return null;
|
||||||
|
for (const t of Object.values(TUR_SAYFALARI)) {
|
||||||
|
if ((t.uniturler as readonly string[]).includes(unitur)) {
|
||||||
|
return `/universiteler/${t.slug}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user