feat(tasarim): /sonuc program tablosu "şişko ve sade" dile göre

- Liste satırı sınıfları src/components/liste-satiri.ts'e çıkarıldı;
  üniversite, bölüm ve /sonuc tabloları oradan okuyor
- /sonuc satırı: ad sarar, üniversite adı ayrı satırda ve düzgün
  harfle, telefonda risk + taban sıra adın altında, 44 px butonlar,
  "boş kaldı" 12 px altından çıktı
- Dilim sekmeleri 375 px'te taşıyordu (418 px); telefonda üç eşit hap
- Tercih kapısında büyük harfli etiketler SectionEyebrow'a, harita
  şeridinde 10 px yazılar 12 px'e

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-24 00:44:59 +03:00
parent 5b51fd16e0
commit f02494ef78
7 changed files with 255 additions and 112 deletions

View File

@@ -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 Hareket: yay `bounce: 0, duration: 0.35`, giriş ve çıkış aynı yoldan; "hareketi azalt"ta yalnız
opaklık. 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`. - **Ad kesilmez, sarar:** `whitespace-normal` + `block text-base font-semibold leading-snug`.
`truncate` ile "İngiliz Dili ve E…" yazılmaz. `truncate` ile "İngiliz Dili ve E…" yazılmaz.
- Ad linkinin hover'ı **mavi**: `transition-colors hover:text-primary hover:underline` - 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 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. (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 ### 5.3 Verimli ve havadar
- İlişkili öğeler yan yana dizilir (iki sütun) — tek sütunda alt alta uzayan panel yapılmaz. - İ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 - Yan yana dizmek "hepsini tek şeride sıkıştırmak" değildir: 23 Eyl'de denenen yatay "bilgi

View File

@@ -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";

View File

@@ -12,6 +12,19 @@ import {
ProgramSiraGecmisi, ProgramSiraGecmisi,
ProgramTabanTrendi, ProgramTabanTrendi,
} from "@/components/program-liste-verileri"; } 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 { ProgramEkleButonu } from "@/features/liste/components/program-ekle-butonu";
import { ProgramNetleriButonu } from "./program-netleri"; import { ProgramNetleriButonu } from "./program-netleri";
import { UniLogo } from "@/components/uni-logo"; import { UniLogo } from "@/components/uni-logo";
@@ -162,10 +175,10 @@ export function BolumProgramListesi({
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="hover:bg-transparent"> <TableRow className="hover:bg-transparent">
<TableHead className="h-12 pl-4 text-sm font-semibold text-slate-500 sm:pl-5"> <TableHead className={`${SATIR_BASLIK} pl-4 sm:pl-5`}>
Üniversite Üniversite
</TableHead> </TableHead>
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 lg:table-cell"> <TableHead className={`hidden text-right lg:table-cell ${SATIR_BASLIK}`}>
Son {VERI_YIL_SAYISI} yıl Son {VERI_YIL_SAYISI} yıl
</TableHead> </TableHead>
<TableHead className="hidden h-12 text-right lg:table-cell"> <TableHead className="hidden h-12 text-right lg:table-cell">
@@ -192,7 +205,7 @@ export function BolumProgramListesi({
onSirala={sirala} onSirala={sirala}
/> />
</TableHead> </TableHead>
<TableHead className="h-12 w-14 text-right sm:w-28"> <TableHead className={SATIR_BUTON_BASLIK}>
<span className="sr-only">Netler ve listeye ekle</span> <span className="sr-only">Netler ve listeye ekle</span>
</TableHead> </TableHead>
</TableRow> </TableRow>
@@ -206,7 +219,7 @@ export function BolumProgramListesi({
return ( return (
<TableRow key={p.id} className="kt-cv-satir"> <TableRow key={p.id} className="kt-cv-satir">
<TableCell className="w-full whitespace-normal py-4 pl-4 sm:pl-5"> <TableCell className={SATIR_AD_HUCRE}>
<span className="flex items-start gap-3"> <span className="flex items-start gap-3">
{p.universiteSlug ? ( {p.universiteSlug ? (
<UniLogo slug={p.universiteSlug} ad={uniAd} boy="sm" /> <UniLogo slug={p.universiteSlug} ad={uniAd} boy="sm" />
@@ -215,23 +228,23 @@ export function BolumProgramListesi({
{p.universiteSlug ? ( {p.universiteSlug ? (
<Link <Link
href={`/universite/${p.universiteSlug}`} href={`/universite/${p.universiteSlug}`}
className="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" className={SATIR_AD}
> >
{uniAd} {uniAd}
</Link> </Link>
) : ( ) : (
<span className="block text-base font-semibold leading-snug text-slate-900"> <span className={SATIR_AD_DUZ}>
{uniAd} {uniAd}
</span> </span>
)} )}
{varyant ? ( {varyant ? (
<span className="mt-0.5 block text-sm leading-snug text-slate-600"> <span className={SATIR_ALT_AD}>
{varyant} {varyant}
</span> </span>
) : null} ) : null}
<span className="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-slate-500"> <span className={SATIR_META}>
{p.unitur ? ( {p.unitur ? (
<span className="shrink-0 rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700"> <span className={SATIR_CIP}>
{UNITUR_ETIKET[p.unitur]?.cip ?? {UNITUR_ETIKET[p.unitur]?.cip ??
trBaslikDuzeni(p.unitur)} trBaslikDuzeni(p.unitur)}
</span> </span>
@@ -245,7 +258,7 @@ export function BolumProgramListesi({
<span className="shrink-0">{p.sure} yıl</span> <span className="shrink-0">{p.sure} yıl</span>
) : null} ) : null}
</span> </span>
<span className="mt-1.5 flex items-center gap-1.5 text-sm text-slate-500 sm:hidden"> <span className={SATIR_MOBIL_SAYI}>
Taban sıra Taban sıra
<span className="font-semibold tabular-nums text-slate-900"> <span className="font-semibold tabular-nums text-slate-900">
{tabanMetni} {tabanMetni}
@@ -272,8 +285,8 @@ export function BolumProgramListesi({
{tabanMetni} {tabanMetni}
</span> </span>
</TableCell> </TableCell>
<TableCell className="py-4 pr-4 text-right sm:pr-5"> <TableCell className={SATIR_BUTON_HUCRE}>
<span className="inline-flex flex-col items-center gap-2 sm:flex-row"> <span className={SATIR_BUTON_KABI}>
<ProgramNetleriButonu <ProgramNetleriButonu
programId={p.id} programId={p.id}
isim={p.isim.trim()} isim={p.isim.trim()}

View File

@@ -8,6 +8,18 @@ import {
ProgramTabanTrendi, ProgramTabanTrendi,
programEtkinSira, programEtkinSira,
} from "@/components/program-liste-verileri"; } from "@/components/program-liste-verileri";
import {
SATIR_AD,
SATIR_AD_DUZ,
SATIR_AD_HUCRE,
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 { ProgramEkleButonu } from "@/features/liste/components/program-ekle-butonu";
import { ProgramNetleriButonu } from "./program-netleri"; import { ProgramNetleriButonu } from "./program-netleri";
import { import {
@@ -50,22 +62,22 @@ export function UniversiteProgramTablosu({
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="hover:bg-transparent"> <TableRow className="hover:bg-transparent">
<TableHead className="h-12 pl-4 text-sm font-semibold text-slate-500 sm:pl-5"> <TableHead className={`${SATIR_BASLIK} pl-4 sm:pl-5`}>
Program Program
</TableHead> </TableHead>
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 lg:table-cell"> <TableHead className={`hidden text-right lg:table-cell ${SATIR_BASLIK}`}>
Son {VERI_YIL_SAYISI} yıl Son {VERI_YIL_SAYISI} yıl
</TableHead> </TableHead>
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 lg:table-cell"> <TableHead className={`hidden text-right lg:table-cell ${SATIR_BASLIK}`}>
{SON_YIL} puan {SON_YIL} puan
</TableHead> </TableHead>
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 md:table-cell"> <TableHead className={`hidden text-right md:table-cell ${SATIR_BASLIK}`}>
Kontenjan Kontenjan
</TableHead> </TableHead>
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 sm:table-cell"> <TableHead className={`hidden text-right sm:table-cell ${SATIR_BASLIK}`}>
Taban sıra Taban sıra
</TableHead> </TableHead>
<TableHead className="h-12 w-14 text-right sm:w-28"> <TableHead className={SATIR_BUTON_BASLIK}>
<span className="sr-only">Netler ve listeye ekle</span> <span className="sr-only">Netler ve listeye ekle</span>
</TableHead> </TableHead>
</TableRow> </TableRow>
@@ -76,28 +88,28 @@ export function UniversiteProgramTablosu({
const tabanMetni = taban != null ? `~${sayi(taban)}.` : "—"; const tabanMetni = taban != null ? `~${sayi(taban)}.` : "—";
return ( return (
<TableRow key={p.id} className="kt-cv-satir"> <TableRow key={p.id} className="kt-cv-satir">
<TableCell className="w-full whitespace-normal py-4 pl-4 sm:pl-5"> <TableCell className={SATIR_AD_HUCRE}>
{p.bolumSlug ? ( {p.bolumSlug ? (
<Link <Link
href={`/bolum/${p.bolumSlug}`} href={`/bolum/${p.bolumSlug}`}
className="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" className={SATIR_AD}
> >
{p.isim.trim()} {p.isim.trim()}
</Link> </Link>
) : ( ) : (
<span className="block text-base font-semibold leading-snug text-slate-900"> <span className={SATIR_AD_DUZ}>
{p.isim.trim()} {p.isim.trim()}
</span> </span>
)} )}
<span className="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-slate-500"> <span className={SATIR_META}>
<span className="shrink-0 rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700"> <span className={SATIR_CIP}>
{TUR_ETIKET[p.tur] ?? p.tur} {TUR_ETIKET[p.tur] ?? p.tur}
</span> </span>
{p.sure ? ( {p.sure ? (
<span className="shrink-0">{p.sure} yıl</span> <span className="shrink-0">{p.sure} yıl</span>
) : null} ) : null}
</span> </span>
<span className="mt-1.5 flex items-center gap-1.5 text-sm text-slate-500 sm:hidden"> <span className={SATIR_MOBIL_SAYI}>
Taban sıra Taban sıra
<span className="font-semibold tabular-nums text-slate-900"> <span className="font-semibold tabular-nums text-slate-900">
{tabanMetni} {tabanMetni}
@@ -124,8 +136,8 @@ export function UniversiteProgramTablosu({
{tabanMetni} {tabanMetni}
</span> </span>
</TableCell> </TableCell>
<TableCell className="py-4 pr-4 text-right sm:pr-5"> <TableCell className={SATIR_BUTON_HUCRE}>
<span className="inline-flex flex-col items-center gap-2 sm:flex-row"> <span className={SATIR_BUTON_KABI}>
<ProgramNetleriButonu <ProgramNetleriButonu
programId={p.id} programId={p.id}
isim={p.isim.trim()} isim={p.isim.trim()}

View File

@@ -26,7 +26,7 @@ import {
riskHesapla, riskHesapla,
type RiskSeviyesi, type RiskSeviyesi,
} from "@/lib/risk"; } from "@/lib/risk";
import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug"; import { bolumSayfaSlug, trBaslikDuzeni, uniSayfaSlug } from "@/lib/slug";
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari"; import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
import { import {
ProgramSiraGecmisi, ProgramSiraGecmisi,
@@ -46,6 +46,20 @@ import { ProgramEkleButonu } from "@/features/liste/components/program-ekle-buto
import { preloadTercihHaritasi } from "@/features/liste/components/tercih-haritasi-lazy"; import { preloadTercihHaritasi } from "@/features/liste/components/tercih-haritasi-lazy";
import { ProgramNetleriButonu } from "@/features/katalog/components/program-netleri"; import { ProgramNetleriButonu } from "@/features/katalog/components/program-netleri";
import { UniLogo } from "@/components/uni-logo"; import { UniLogo } from "@/components/uni-logo";
import { Button } from "@/components/ui/button";
import {
SATIR_AD,
SATIR_AD_HUCRE,
SATIR_ALT_AD,
SATIR_BASLIK,
SATIR_BUTON_BASLIK,
SATIR_BUTON_HUCRE,
SATIR_BUTON_KABI,
SATIR_CIP,
SATIR_IKINCIL_LINK,
SATIR_META,
SATIR_MOBIL_SAYI,
} from "@/components/liste-satiri";
const DILIMLER: { const DILIMLER: {
key: DilimKey; key: DilimKey;
@@ -195,14 +209,17 @@ export function ProgramTablosu({
navbar'daki sıralama rozetinden değiştirilir. Aktif hap, nav'daki navbar'daki sıralama rozetinden değiştirilir. Aktif hap, nav'daki
"Giriş yap" butonuyla aynı dilde (koyu, rounded-full) ve layoutId "Giriş yap" butonuyla aynı dilde (koyu, rounded-full) ve layoutId
ile sekmeler arasında kayarak taşınır. */} ile sekmeler arasında kayarak taşınır. */}
<TabsList variant="pill" className="self-center"> <TabsList
variant="pill"
className="grid w-full grid-cols-3 self-center sm:inline-flex sm:w-auto"
>
{DILIMLER.map((d) => { {DILIMLER.map((d) => {
const aktif = aktifDilim === d.key; const aktif = aktifDilim === d.key;
return ( return (
<TabsTrigger <TabsTrigger
key={d.key} key={d.key}
value={d.key} value={d.key}
className="relative cursor-pointer px-3 py-1.5 transition-colors duration-200 hover:bg-slate-100 sm:px-4 sm:py-2" className="relative min-h-11 cursor-pointer px-2 py-1.5 text-sm font-semibold transition-colors duration-200 hover:text-orange-500 data-active:hover:text-white group-data-[variant=pill]/tabs-list:h-auto sm:px-4"
> >
{aktif ? ( {aktif ? (
<motion.span <motion.span
@@ -216,13 +233,17 @@ export function ProgramTablosu({
aria-hidden aria-hidden
/> />
) : null} ) : null}
<span className="relative z-10 inline-flex items-center gap-1.5"> <span className="relative z-10 inline-flex flex-col items-center gap-0.5 sm:flex-row sm:gap-1.5">
<d.icon <span className="inline-flex items-center gap-1.5">
className={`size-3.5 ${aktif ? d.renkAktif : d.renk}`} <d.icon
aria-hidden className={`size-4 ${aktif ? d.renkAktif : d.renk}`}
/> aria-hidden
{d.etiket} />
<span className="text-xs tabular-nums text-slate-400"> {d.etiket}
</span>
<span
className={`text-sm font-medium tabular-nums ${aktif ? "text-white/70" : "text-slate-400"}`}
>
{sonuclar.toplam[d.key].toLocaleString("tr-TR")} {sonuclar.toplam[d.key].toLocaleString("tr-TR")}
</span> </span>
</span> </span>
@@ -236,7 +257,7 @@ export function ProgramTablosu({
const toplam = sonuclar.toplam[d.key]; const toplam = sonuclar.toplam[d.key];
return ( return (
<TabsContent key={d.key} value={d.key}> <TabsContent key={d.key} value={d.key}>
<div className="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white"> <div className="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white">
{satirlar.length === 0 ? ( {satirlar.length === 0 ? (
<p className="px-5 py-8 text-center text-sm text-slate-500"> <p className="px-5 py-8 text-center text-sm text-slate-500">
Bu dilimde filtrene uyan program bulunamadı. Bu dilimde filtrene uyan program bulunamadı.
@@ -244,17 +265,31 @@ export function ProgramTablosu({
) : ( ) : (
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow className="hover:bg-transparent">
<TableHead className="w-8 pl-4 pr-0">Risk</TableHead> <TableHead
<TableHead>Program</TableHead> className={`hidden w-28 pl-4 pr-0 sm:table-cell sm:pl-5 ${SATIR_BASLIK}`}
<TableHead className="hidden text-right md:table-cell"> >
Risk
</TableHead>
<TableHead className={`pl-4 ${SATIR_BASLIK}`}>
Program
</TableHead>
<TableHead
className={`hidden text-right lg:table-cell ${SATIR_BASLIK}`}
>
Son {VERI_YIL_SAYISI} yıl Son {VERI_YIL_SAYISI} yıl
</TableHead> </TableHead>
<TableHead className="hidden text-right sm:table-cell"> <TableHead
className={`hidden text-right md:table-cell ${SATIR_BASLIK}`}
>
Kontenjan Kontenjan
</TableHead> </TableHead>
<TableHead className="text-right">Taban sıra</TableHead> <TableHead
<TableHead className="w-24 text-right"> className={`hidden text-right sm:table-cell ${SATIR_BASLIK}`}
>
Taban sıra
</TableHead>
<TableHead className={SATIR_BUTON_BASLIK}>
<span className="sr-only">Netler ve listeye ekle</span> <span className="sr-only">Netler ve listeye ekle</span>
</TableHead> </TableHead>
</TableRow> </TableRow>
@@ -273,17 +308,19 @@ export function ProgramTablosu({
)} )}
</div> </div>
{satirlar.length < toplam ? ( {satirlar.length < toplam ? (
<div className="mt-3 flex flex-col items-center gap-1"> <div className="mt-4 flex flex-col items-center gap-2">
<button <Button
type="button" type="button"
size="lg"
variant="outline"
disabled={yuklenen === d.key} disabled={yuklenen === d.key}
onClick={() => dahaFazlaYukle(d.key, satirlar.length)} onClick={() => dahaFazlaYukle(d.key, satirlar.length)}
className="inline-flex cursor-pointer items-center gap-1.5 rounded-full border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition-colors duration-150 hover:border-slate-300 hover:bg-slate-50 disabled:cursor-default disabled:opacity-60" className="cursor-pointer px-6 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]"
> >
<ChevronDown className="size-4" aria-hidden />
{yuklenen === d.key ? "Yükleniyor…" : "Daha fazla göster"} {yuklenen === d.key ? "Yükleniyor…" : "Daha fazla göster"}
</button> <ChevronDown className="size-4" aria-hidden />
<span className="text-xs tabular-nums text-slate-400"> </Button>
<span className="text-sm tabular-nums text-slate-500">
{satirlar.length.toLocaleString("tr-TR")} /{" "} {satirlar.length.toLocaleString("tr-TR")} /{" "}
{toplam.toLocaleString("tr-TR")} program {toplam.toLocaleString("tr-TR")} program
</span> </span>
@@ -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<RiskSeviyesi, string> = {
guvenli: "text-emerald-600",
"az-riskli": "text-amber-600",
riskli: "text-red-600",
};
function RiskEtiketi({ risk }: { risk: RiskSeviyesi }) {
return (
<span
className={`inline-flex items-center gap-1.5 text-xs font-semibold ${RISK_METIN[risk]}`}
>
<span
className={`size-2 shrink-0 rounded-full ${RISK_NOKTA[risk]}`}
aria-hidden
/>
{RISK_ETIKET[risk]}
</span>
);
}
function ProgramSatiri({ function ProgramSatiri({
program: p, program: p,
adaySira, adaySira,
@@ -309,65 +367,74 @@ 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 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 ( return (
<TableRow className="kt-cv-satir"> <TableRow className="kt-cv-satir">
<TableCell className="pl-4 pr-0 text-xs font-medium"> <TableCell className="hidden pl-4 pr-0 sm:table-cell sm:pl-5">
{risk ? ( {risk ? <RiskEtiketi risk={risk} /> : null}
<span className="inline-flex items-center gap-1.5">
<span
className={`size-2.5 shrink-0 rounded-full ${RISK_NOKTA[risk]}`}
aria-hidden
/>
{RISK_ETIKET[risk]}
</span>
) : null}
</TableCell> </TableCell>
<TableCell className="max-w-0 w-full"> <TableCell className={`${SATIR_AD_HUCRE} sm:pl-4`}>
<span className="flex min-w-0 items-center gap-2.5"> <span className="flex items-start gap-3">
<UniLogo ad={p.universite} boy="sm" /> <UniLogo ad={p.universite} boy="sm" />
<span className="min-w-0 flex-1"> <span className="min-w-0 flex-1">
<Link <Link
href={`/bolum/${bolumSayfaSlug(p.isim)}`} href={`/bolum/${bolumSayfaSlug(p.isim)}`}
className="block truncate text-sm font-semibold hover:text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className={SATIR_AD}
title={`${p.isim} bölüm sayfası`} title={`${p.isim} bölüm sayfası`}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{p.isim} {p.isim}
</Link> </Link>
<span className="mt-0.5 flex items-center gap-1.5 text-xs text-slate-500"> <span className={SATIR_ALT_AD}>
<span <Link
className={`shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4 ${ href={`/universite/${uniSayfaSlug(p.universite)}`}
devlet className={SATIR_IKINCIL_LINK}
? "bg-slate-100 text-slate-600" title={`${p.universite} taban puanları sayfası`}
: "bg-violet-50 text-violet-600" onClick={(e) => e.stopPropagation()}
}`}
> >
{devlet ? "Devlet" : "Vakıf"} {trBaslikDuzeni(p.universite)}
</span> </Link>
<span className="truncate"> </span>
<Link <span className={SATIR_META}>
href={`/universite/${uniSayfaSlug(p.universite)}`} <span className={SATIR_CIP}>{devlet ? "Devlet" : "Vakıf"}</span>
className="hover:text-primary hover:underline" {p.il ? (
title={`${p.universite} taban puanları sayfası`} <span className="shrink-0">{trBaslikDuzeni(p.il)}</span>
onClick={(e) => e.stopPropagation()}
>
{p.universite}
</Link>
{p.il ? ` · ${p.il.toLocaleLowerCase("tr-TR")}` : ""}
</span>
{p.sure ? (
<span className="shrink-0 text-slate-400">· {p.sure} yıl</span>
) : null} ) : null}
{p.sure ? (
<span className="shrink-0">{p.sure} yıl</span>
) : null}
{bosKaldi ? (
<span className="shrink-0 rounded-full bg-amber-50 px-2.5 py-0.5 text-xs font-semibold text-amber-700 sm:hidden">
Boş kaldı
</span>
) : null}
</span>
<span className={SATIR_MOBIL_SAYI}>
{risk ? <RiskEtiketi risk={risk} /> : null}
<span className="inline-flex items-center gap-1.5">
Taban sıra
<span className="font-semibold tabular-nums text-slate-900">
{tabanMetni}
</span>
<ProgramTabanTrendi program={p} />
</span>
</span> </span>
</span> </span>
</span> </span>
</TableCell> </TableCell>
<TableCell className="hidden text-right text-xs md:table-cell"> <TableCell className="hidden text-right text-sm lg:table-cell">
<ProgramSiraGecmisi program={p} /> <ProgramSiraGecmisi program={p} />
</TableCell> </TableCell>
<TableCell <TableCell
className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell" className="hidden text-right text-sm tabular-nums text-slate-600 md:table-cell"
title={ title={
p.kontenjanSon != null && p.yerlesenSon != null p.kontenjanSon != null && p.yerlesenSon != null
? `${p.kontenjanSon} kontenjan, ${p.yerlesenSon} yerleşen` ? `${p.kontenjanSon} kontenjan, ${p.yerlesenSon} yerleşen`
@@ -375,38 +442,36 @@ function ProgramSatiri({
} }
> >
{p.kontenjanSon != null ? ( {p.kontenjanSon != null ? (
<span className="inline-flex items-center gap-1.5"> <span className="inline-flex flex-col items-end gap-1">
{p.yerlesenSon != null && p.yerlesenSon < p.kontenjanSon ? ( {p.kontenjanSon.toLocaleString("tr-TR")} kişi
<span className="rounded bg-amber-50 px-1 py-px text-[10px] font-medium leading-4 text-amber-600"> {bosKaldi ? (
boş kaldı <span className="rounded-full bg-amber-50 px-2.5 py-0.5 text-xs font-semibold text-amber-700">
Boş kaldı
</span> </span>
) : null} ) : null}
{p.kontenjanSon.toLocaleString("tr-TR")} kişi
</span> </span>
) : ( ) : (
"—" "—"
)} )}
</TableCell> </TableCell>
<TableCell className="text-right text-xs tabular-nums text-slate-500"> <TableCell className="hidden text-right text-base font-semibold tabular-nums text-slate-900 sm:table-cell">
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1.5">
<ProgramTabanTrendi program={p} /> <ProgramTabanTrendi program={p} />
{taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—"} {tabanMetni}
</span> </span>
</TableCell> </TableCell>
<TableCell className="pr-4 text-right"> <TableCell className={SATIR_BUTON_HUCRE}>
<span className="inline-flex items-center gap-1.5"> <span className={SATIR_BUTON_KABI}>
<ProgramNetleriButonu <ProgramNetleriButonu
programId={p.id} programId={p.id}
isim={p.isim} isim={p.isim}
universite={p.universite} universite={p.universite}
className="size-8 min-h-8 min-w-8"
/> />
<ProgramEkleButonu <ProgramEkleButonu
program={p} program={p}
kaynak="sonuc" kaynak="sonuc"
adaySira={adaySira} adaySira={adaySira}
adayTur={tur} adayTur={tur}
className="size-8 min-h-8 min-w-8"
/> />
</span> </span>
</TableCell> </TableCell>

View File

@@ -513,10 +513,10 @@ export function TercihHaritasi({
<button <button
type="button" type="button"
onClick={onTercihSec ? () => onTercihSec(sira) : undefined} onClick={onTercihSec ? () => onTercihSec(sira) : undefined}
className="flex cursor-pointer items-center gap-2 rounded-full border border-slate-200 bg-white py-1.5 pl-1.5 pr-3 text-xs transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50" className="flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-slate-200 bg-white py-1.5 pl-1.5 pr-3.5 text-sm transition-[background-color,border-color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 active:scale-[0.97]"
> >
<span <span
className="flex size-5 items-center justify-center rounded-full font-heading text-[10px] font-bold text-white" className="flex size-7 shrink-0 items-center justify-center rounded-full font-heading text-xs font-bold text-white"
style={{ backgroundColor: RISK_RENK[pin.risk] }} style={{ backgroundColor: RISK_RENK[pin.risk] }}
> >
{sira} {sira}
@@ -543,7 +543,7 @@ export function TercihHaritasi({
{bolum || kisaUniAdi(pin.universite)} {bolum || kisaUniAdi(pin.universite)}
</span> </span>
{bolum ? ( {bolum ? (
<span className="max-w-48 truncate text-[10px] text-slate-500"> <span className="max-w-48 truncate text-xs text-slate-500">
{kisaUniAdi(pin.universite)} {kisaUniAdi(pin.universite)}
</span> </span>
) : null} ) : null}

View File

@@ -7,6 +7,7 @@ import { useReducedMotion } from "motion/react";
import useMeasure from "react-use-measure"; import useMeasure from "react-use-measure";
import { toast } from "sonner"; import { toast } from "sonner";
import { TransitionPanel } from "@/components/core/transition-panel"; import { TransitionPanel } from "@/components/core/transition-panel";
import { SectionEyebrow } from "@/components/pixel-decor";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
@@ -295,11 +296,9 @@ function KapıIcerik({
<div ref={panelRef} className="flex flex-col gap-5 px-px"> <div ref={panelRef} className="flex flex-col gap-5 px-px">
<div> <div>
{duzenle ? ( {duzenle ? (
<p className="text-xs font-semibold uppercase tracking-wide text-primary"> <SectionEyebrow>Tercihlerini güncelle</SectionEyebrow>
Tercihlerini güncelle
</p>
) : null} ) : null}
<h2 className="mt-2 font-heading text-2xl font-bold text-slate-900 first:mt-0"> <h2 className="mt-3 font-heading text-2xl font-bold text-slate-900 first:mt-0">
{duzenle {duzenle
? "Seçimlerini değiştir" ? "Seçimlerini değiştir"
: "Tercihine ışık tutabilmemiz için önce ne istediğini öğrenelim"} : "Tercihine ışık tutabilmemiz için önce ne istediğini öğrenelim"}
@@ -348,11 +347,9 @@ function KapıIcerik({
> >
<div> <div>
{duzenle ? ( {duzenle ? (
<p className="text-xs font-semibold uppercase tracking-wide text-primary"> <SectionEyebrow>Sıralama ve tercihler</SectionEyebrow>
Sıralama ve tercihler
</p>
) : null} ) : null}
<h2 className="mt-2 font-heading text-xl font-bold text-slate-900 first:mt-0"> <h2 className="mt-3 font-heading text-xl font-bold text-slate-900 first:mt-0">
{duzenle {duzenle
? "Yeni sıralaman nedir?" ? "Yeni sıralaman nedir?"
: "Başarı sıralaman nedir?"} : "Başarı sıralaman nedir?"}
@@ -366,7 +363,7 @@ function KapıIcerik({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label <label
htmlFor="kapisi-siralama" htmlFor="kapisi-siralama"
className="text-xs font-semibold uppercase tracking-wide text-slate-400" className="text-sm font-semibold text-slate-700"
> >
Başarı sıralaması ve puan türü Başarı sıralaması ve puan türü
</label> </label>