diff --git a/docs/tasarim/DESIGN.md b/docs/tasarim/DESIGN.md index adfed78..fb4a37c 100644 --- a/docs/tasarim/DESIGN.md +++ b/docs/tasarim/DESIGN.md @@ -336,7 +336,13 @@ verilir (logo listesi istemciye inmez). Hareket: yay `bounce: 0, duration: 0.35`, giriş ve çıkış aynı yoldan; "hareketi azalt"ta yalnız opaklık. -### 4.15 Liste satırı (program/üniversite listeleri) — `universite-program-tablosu.tsx` +### 4.15 Liste satırı (program/üniversite listeleri) — `src/components/liste-satiri.ts` +Sınıf dizileri tek dosyada (`SATIR_AD`, `SATIR_META`, `SATIR_CIP`, `SATIR_MOBIL_SAYI`, +`SATIR_BUTON_KABI`…); üniversite, bölüm ve `/sonuc` tabloları oradan okur. Yeni bir liste +tablosu bu sabitleri kullanır, sınıfı elle yazmaz. +- Risk sütunu (`/sonuc`) sm'den itibaren ayrı sütun; telefonda adın altındaki sayı satırına + iner. Etiket §4.5: `size-2` nokta + `text-xs font-semibold text-{renk}-600` + `RISK_ETIKET`. +- Veride BÜYÜK HARF gelen üniversite adı ekranda `trBaslikDuzeni` ile yazılır. - **Ad kesilmez, sarar:** `whitespace-normal` + `block text-base font-semibold leading-snug`. `truncate` ile "İngiliz Dili ve E…" yazılmaz. - Ad linkinin hover'ı **mavi**: `transition-colors hover:text-primary hover:underline` @@ -394,6 +400,11 @@ Haplar `h-11` (44 px), hover §2.1. Telefonda (sm altı) tek satır kalsın diye aktif sayfa · Sonraki görünür; diğer numaralar `hidden sm:inline-flex` ile HTML'de durur (tarayıcı botları tüm sayfaları görür). 375 px'te 7 sayfalık şerit iki satıra kırılıyordu. +### 5.2.2 Dilim sekmeleri (Hayal / Dengeli / Güvenli) — `program-tablosu.tsx` +Tek satırda üç hap 375 px'e sığmıyordu (418 px, sayfa yana kayıyordu). Telefonda +`grid w-full grid-cols-3`, hapın içinde ikon+ad üstte, sayı altta; sm'den tek satır. Hap en az +44 px (`min-h-11`), hover §2.1. + ### 5.3 Verimli ve havadar - İlişkili öğeler yan yana dizilir (iki sütun) — tek sütunda alt alta uzayan panel yapılmaz. - Yan yana dizmek "hepsini tek şeride sıkıştırmak" değildir: 23 Eyl'de denenen yatay "bilgi diff --git a/src/components/liste-satiri.ts b/src/components/liste-satiri.ts new file mode 100644 index 0000000..b797f6b --- /dev/null +++ b/src/components/liste-satiri.ts @@ -0,0 +1,45 @@ +/** + * Liste satırının (DESIGN.md §4.15) sınıf dizileri — tek kaynak. Üniversite, + * bölüm ve /sonuc program tabloları (ileride rapor listesi) aynı satırı + * çizer; sınıflar her dosyada ayrı yazılınca ölçüler kayıyordu. "use client" + * yok: sunucu bileşenleri de değer olarak okuyabilsin (§9.3). + */ + +/** Satırın adı (bölüm/üniversite sayfasına link): sarar, hover mavi (§2.1 istisnası). */ +export const SATIR_AD = + "block text-base font-semibold leading-snug text-slate-900 transition-colors hover:text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"; + +/** Link olmayan ad (slug yoksa). */ +export const SATIR_AD_DUZ = + "block text-base font-semibold leading-snug text-slate-900"; + +/** Adın altındaki ikinci satır (varyant ya da üniversite adı). */ +export const SATIR_ALT_AD = "mt-0.5 block text-sm leading-snug text-slate-600"; + +/** Satırdaki ikincil link (ör. üniversite adı) — §2.1 tek turuncu. */ +export const SATIR_IKINCIL_LINK = + "transition-colors hover:text-orange-500 hover:underline"; + +/** Tür/il/süre satırı. */ +export const SATIR_META = + "mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-slate-500"; + +/** Tür çipi (Devlet, Vakıf, SAY…). */ +export const SATIR_CIP = + "shrink-0 rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700"; + +/** Telefonda sayısal sütunun adın altına indiği satır. */ +export const SATIR_MOBIL_SAYI = + "mt-1.5 flex flex-wrap items-center gap-x-1.5 gap-y-1 text-sm text-slate-500 sm:hidden"; + +/** Tablo başlık hücresi. */ +export const SATIR_BASLIK = "h-12 text-sm font-semibold text-slate-500"; + +/** Adın bulunduğu hücre. */ +export const SATIR_AD_HUCRE = "w-full whitespace-normal py-4 pl-4 sm:pl-5"; + +/** Butonların hücresi ve kabı: telefonda alt alta, sm'den yan yana. */ +export const SATIR_BUTON_HUCRE = "py-4 pr-4 text-right sm:pr-5"; +export const SATIR_BUTON_KABI = + "inline-flex flex-col items-center gap-2 sm:flex-row"; +export const SATIR_BUTON_BASLIK = "h-12 w-14 text-right sm:w-28"; diff --git a/src/features/katalog/components/bolum-program-listesi.tsx b/src/features/katalog/components/bolum-program-listesi.tsx index b4e2310..3c87525 100644 --- a/src/features/katalog/components/bolum-program-listesi.tsx +++ b/src/features/katalog/components/bolum-program-listesi.tsx @@ -12,6 +12,19 @@ import { ProgramSiraGecmisi, ProgramTabanTrendi, } from "@/components/program-liste-verileri"; +import { + SATIR_AD, + SATIR_AD_DUZ, + SATIR_AD_HUCRE, + SATIR_ALT_AD, + SATIR_BASLIK, + SATIR_BUTON_BASLIK, + SATIR_BUTON_HUCRE, + SATIR_BUTON_KABI, + SATIR_CIP, + SATIR_META, + SATIR_MOBIL_SAYI, +} from "@/components/liste-satiri"; import { ProgramEkleButonu } from "@/features/liste/components/program-ekle-butonu"; import { ProgramNetleriButonu } from "./program-netleri"; import { UniLogo } from "@/components/uni-logo"; @@ -162,10 +175,10 @@ export function BolumProgramListesi({ - + Üniversite - + Son {VERI_YIL_SAYISI} yıl @@ -192,7 +205,7 @@ export function BolumProgramListesi({ onSirala={sirala} /> - + Netler ve listeye ekle @@ -206,7 +219,7 @@ export function BolumProgramListesi({ return ( - + {p.universiteSlug ? ( @@ -215,23 +228,23 @@ export function BolumProgramListesi({ {p.universiteSlug ? ( {uniAd} ) : ( - + {uniAd} )} {varyant ? ( - + {varyant} ) : null} - + {p.unitur ? ( - + {UNITUR_ETIKET[p.unitur]?.cip ?? trBaslikDuzeni(p.unitur)} @@ -245,7 +258,7 @@ export function BolumProgramListesi({ {p.sure} yıl ) : null} - + Taban sıra {tabanMetni} @@ -272,8 +285,8 @@ export function BolumProgramListesi({ {tabanMetni} - - + + - + Program - + Son {VERI_YIL_SAYISI} yıl - + {SON_YIL} puan - + Kontenjan - + Taban sıra - + Netler ve listeye ekle @@ -76,28 +88,28 @@ export function UniversiteProgramTablosu({ const tabanMetni = taban != null ? `~${sayi(taban)}.` : "—"; return ( - + {p.bolumSlug ? ( {p.isim.trim()} ) : ( - + {p.isim.trim()} )} - - + + {TUR_ETIKET[p.tur] ?? p.tur} {p.sure ? ( {p.sure} yıl ) : null} - + Taban sıra {tabanMetni} @@ -124,8 +136,8 @@ export function UniversiteProgramTablosu({ {tabanMetni} - - + + + {DILIMLER.map((d) => { const aktif = aktifDilim === d.key; return ( {aktif ? ( ) : null} - - - {d.etiket} - + + + + {d.etiket} + + {sonuclar.toplam[d.key].toLocaleString("tr-TR")} @@ -236,7 +257,7 @@ export function ProgramTablosu({ const toplam = sonuclar.toplam[d.key]; return ( -
+
{satirlar.length === 0 ? (

Bu dilimde filtrene uyan program bulunamadı. @@ -244,17 +265,31 @@ export function ProgramTablosu({ ) : (

- - Risk - Program - + + + Risk + + + Program + + Son {VERI_YIL_SAYISI} yıl - + Kontenjan - Taban sıra - + + Taban sıra + + Netler ve listeye ekle @@ -273,17 +308,19 @@ export function ProgramTablosu({ )} {satirlar.length < toplam ? ( -
- - + + + {satirlar.length.toLocaleString("tr-TR")} /{" "} {toplam.toLocaleString("tr-TR")} program @@ -297,6 +334,27 @@ export function ProgramTablosu({ ); } +// Risk etiketinin metin rengi (§4.5): nokta + renkli metin, renk tek başına değil. +const RISK_METIN: Record = { + guvenli: "text-emerald-600", + "az-riskli": "text-amber-600", + riskli: "text-red-600", +}; + +function RiskEtiketi({ risk }: { risk: RiskSeviyesi }) { + return ( + + + {RISK_ETIKET[risk]} + + ); +} + function ProgramSatiri({ program: p, adaySira, @@ -309,65 +367,74 @@ function ProgramSatiri({ const taban = programEtkinSira(p); const risk = riskHesapla(taban, adaySira); const devlet = p.unitur === "DEVLET"; + const tabanMetni = taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—"; + const bosKaldi = + p.kontenjanSon != null && + p.yerlesenSon != null && + p.yerlesenSon < p.kontenjanSon; + // Şişko ve sade (DESIGN.md §4.15): program adı kesilmez, üniversite adı + // altında ayrı satır; telefonda risk ve taban sıra adın altına iner, + // butonlar alt alta 44 px. return ( - - {risk ? ( - - - {RISK_ETIKET[risk]} - - ) : null} + + {risk ? : null} - - + + e.stopPropagation()} > {p.isim} - - + e.stopPropagation()} > - {devlet ? "Devlet" : "Vakıf"} - - - e.stopPropagation()} - > - {p.universite} - - {p.il ? ` · ${p.il.toLocaleLowerCase("tr-TR")}` : ""} - - {p.sure ? ( - · {p.sure} yıl + {trBaslikDuzeni(p.universite)} + + + + {devlet ? "Devlet" : "Vakıf"} + {p.il ? ( + {trBaslikDuzeni(p.il)} ) : null} + {p.sure ? ( + {p.sure} yıl + ) : null} + {bosKaldi ? ( + + Boş kaldı + + ) : null} + + + {risk ? : null} + + Taban sıra + + {tabanMetni} + + + - + {p.kontenjanSon != null ? ( - - {p.yerlesenSon != null && p.yerlesenSon < p.kontenjanSon ? ( - - boş kaldı + + {p.kontenjanSon.toLocaleString("tr-TR")} kişi + {bosKaldi ? ( + + Boş kaldı ) : null} - {p.kontenjanSon.toLocaleString("tr-TR")} kişi ) : ( "—" )} - - + + - {taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—"} + {tabanMetni} - - + + diff --git a/src/features/liste/components/tercih-haritasi.tsx b/src/features/liste/components/tercih-haritasi.tsx index 0af9813..187b3e5 100644 --- a/src/features/liste/components/tercih-haritasi.tsx +++ b/src/features/liste/components/tercih-haritasi.tsx @@ -513,10 +513,10 @@ export function TercihHaritasi({