fix(tasarim): netler paneli alt sayfa + §4.15 tablo; ok düğmesinde gölge ve hover rengi yok
All checks were successful
Deploy / deploy (push) Successful in 5m3s

- "Son yerleşenin netleri" ortada açılan Dialog ve text-xs hücreli tabloydu;
  şimdi Drawer (kap/çarpı cta-sira-form çekmecesiyle birebir), ortalı başlık
  + bağlam cümlesi, SATIR_BASLIK başlıklar, text-sm hücreler, en yeni yıl koyu,
  "Yerleşme puanı" satırı ayrılmış (DESIGN.md §4.10, §4.15, yeni §4.19).
- Denetim: tablo-minik-yazi (hata) — TableCell/TableHead içinde text-xs yasak;
  §9.5 bitirmeden listesine ayrıntı paneli/tablo/buton kontrolü eklendi.
- Kayıtlı-sıra ok düğmesi: shadow-lg kaldırıldı (§6), hover'da renk değişmiyor
  (orange-600 iri yüzeyde kırmızımsı okunuyordu); hover scale + ok kayması.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-24 18:46:32 +03:00
parent 67371c929d
commit 988b9d5ce0
4 changed files with 138 additions and 90 deletions

View File

@@ -2,23 +2,25 @@
// Program satırındaki "netler" butonu: YÖK Atlas'ın "yerleşen son kişinin
// netleri" verisini (data/yokatlas.db → netler tablosu, /api/netler) yıl
// sütunlu kompakt bir modalde gösterir. Kadın-erkek dağılımı bilinçli olarak
// yok: YÖK Atlas 2026 SPA'sına geçerken cinsiyet panelini yayından kaldırdı
// (bkz. scripts/detay.ts).
// sütunlu tabloyla alttan açılan sayfada gösterir (DESIGN.md §4.10: ayrıntı
// her zaman Drawer, ortada modal değil; §4.19 netler paneli). Kadın-erkek
// dağılımı bilinçli olarak yok: YÖK Atlas 2026 SPA'sına geçerken cinsiyet
// panelini yayından kaldırdı (bkz. scripts/detay.ts).
import { SATIR_DUGME_BOYU } from "@/components/liste-satiri";
import { SATIR_BASLIK, SATIR_DUGME_BOYU } from "@/components/liste-satiri";
import { useState } from "react";
import { ListChecks } from "lucide-react";
import { ListChecks, X } from "lucide-react";
import type { ProgramNetSatiri } from "@/types/yokatlas";
import { olay } from "@/lib/analitik";
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import {
Table,
TableBody,
@@ -91,8 +93,8 @@ export function ProgramNetleriButonu({
}
return (
<Dialog onOpenChange={acildi}>
<DialogTrigger asChild>
<Drawer onOpenChange={acildi} handleOnly>
<DrawerTrigger asChild>
<button
type="button"
title="Son yerleşenin netleri"
@@ -102,22 +104,31 @@ export function ProgramNetleriButonu({
<ListChecks className="size-4" aria-hidden />
<span className="sr-only">{isim} — son yerleşenin netleri</span>
</button>
</DialogTrigger>
<DialogContent
className="sm:max-w-md"
</DrawerTrigger>
{/* Çekmece kabı ve çarpı cta-sira-form.tsx'teki çekmeceyle birebir (§4.10, §4.3) */}
<DrawerContent
className="max-h-[92dvh] bg-white sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<DialogHeader>
<DialogTitle className="pr-10">Son yerleşenin netleri</DialogTitle>
<DialogDescription>
<DrawerClose className="absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]">
<X className="size-4" aria-hidden />
<span className="sr-only">Kapat</span>
</DrawerClose>
<div className="min-h-0 flex-1 overflow-y-auto px-4 pt-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
<DrawerTitle className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
Son yerleşenin netleri
</DrawerTitle>
<DrawerDescription className="mx-auto mt-1 max-w-md px-4 text-center text-sm text-slate-500">
{isim}
{universite ? ` · ${universite}` : ""} — bölüme en düşük puanla
yerleşen adayın sınav netleri (YÖK Atlas).
</DialogDescription>
</DialogHeader>
<NetlerIcerik durum={durum} netler={netler} />
</DialogContent>
</Dialog>
</DrawerDescription>
<div className="mt-5">
<NetlerIcerik durum={durum} netler={netler} />
</div>
</div>
</DrawerContent>
</Drawer>
);
}
@@ -132,21 +143,21 @@ function NetlerIcerik({
return (
<div className="space-y-2" aria-hidden>
{[0, 1, 2, 3, 4].map((i) => (
<div key={i} className="h-6 animate-pulse rounded-md bg-slate-100" />
<div key={i} className="h-10 animate-pulse rounded-lg bg-slate-100" />
))}
</div>
);
}
if (durum === "hata") {
return (
<p className="py-4 text-center text-sm text-slate-500">
Netler yüklenemedi. Modalı kapatıp yeniden açmayı dene.
<p className="py-4 text-center text-base text-slate-500">
Netler yüklenemedi. Paneli kapatıp yeniden açmayı dene.
</p>
);
}
if (netler.length === 0) {
return (
<p className="py-4 text-center text-sm text-slate-500">
<p className="py-4 text-center text-base text-slate-500">
Bu program için YÖK Atlas&apos;ta net verisi yayınlanmamış.
</p>
);
@@ -155,68 +166,76 @@ function NetlerIcerik({
const dersler = DERSLER.filter((d) =>
netler.some((y) => y[d.alan] != null),
);
// En yeni yıl vurgulu (§1.1 "sık bakılan önde"); eski yıllar bağlam.
const sonYil = Math.max(...netler.map((y) => y.yil));
const hucre = (yil: number, vurgu = false) =>
cn(
"py-2.5 text-right text-sm tabular-nums",
yil === sonYil
? vurgu
? "font-semibold text-slate-900"
: "font-semibold text-slate-800"
: "text-slate-500",
);
return (
<div className="-mx-1 overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="h-9">Ders</TableHead>
{netler.map((y) => (
<TableHead key={y.yil} className="h-9 text-right tabular-nums">
{y.yil}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{dersler.map((d) => (
<TableRow key={d.alan}>
<TableCell className="py-2 text-xs text-slate-600">
{d.etiket}
<span className="text-slate-400"> / {d.max}</span>
</TableCell>
{netler.map((y) => {
const deger = y[d.alan];
return (
<TableCell
key={y.yil}
className="py-2 text-right text-xs tabular-nums"
>
{deger != null ? net(deger) : "—"}
</TableCell>
);
})}
</TableRow>
<Table>
<TableHeader>
<TableRow>
<TableHead className={cn(SATIR_BASLIK, "pl-0")}>Ders</TableHead>
{netler.map((y) => (
<TableHead
key={y.yil}
className={cn(
SATIR_BASLIK,
"text-right tabular-nums",
y.yil === sonYil && "text-slate-900",
)}
>
{y.yil}
</TableHead>
))}
<TableRow>
<TableCell className="py-2 text-xs font-medium text-slate-600">
OBP
</TableRow>
</TableHeader>
<TableBody>
{dersler.map((d) => (
<TableRow key={d.alan}>
<TableCell className="py-2.5 pl-0 text-sm text-slate-700">
{d.etiket}
<span className="text-slate-400"> / {d.max}</span>
</TableCell>
{netler.map((y) => (
<TableCell
key={y.yil}
className="py-2 text-right text-xs tabular-nums text-slate-500"
>
{y.obp != null ? net(y.obp) : "—"}
</TableCell>
))}
{netler.map((y) => {
const deger = y[d.alan];
return (
<TableCell key={y.yil} className={hucre(y.yil)}>
{deger != null ? net(deger) : "—"}
</TableCell>
);
})}
</TableRow>
<TableRow>
<TableCell className="py-2 text-xs font-semibold text-slate-700">
Yerleşme puanı
))}
<TableRow>
<TableCell className="py-2.5 pl-0 text-sm text-slate-500">OBP</TableCell>
{netler.map((y) => (
<TableCell
key={y.yil}
className="py-2.5 text-right text-sm tabular-nums text-slate-500"
>
{y.obp != null ? net(y.obp) : "—"}
</TableCell>
{netler.map((y) => (
<TableCell
key={y.yil}
className="py-2 text-right text-xs font-semibold tabular-nums text-slate-700"
>
{y.tabanPuan != null ? net(y.tabanPuan) : "—"}
</TableCell>
))}
</TableRow>
</TableBody>
</Table>
</div>
))}
</TableRow>
<TableRow className="border-t border-slate-200">
<TableCell className="py-3 pl-0 text-sm font-semibold text-slate-900">
Yerleşme puanı
</TableCell>
{netler.map((y) => (
<TableCell key={y.yil} className={hucre(y.yil, true)}>
{y.tabanPuan != null ? net(y.tabanPuan) : "—"}
</TableCell>
))}
</TableRow>
</TableBody>
</Table>
);
}

View File

@@ -442,7 +442,10 @@ export function CtaSiraForm({
href={sonucHref()}
aria-label="Sıralamana uygun programları gör"
title="Sıralamana uygun programları gör"
className="group flex size-12 shrink-0 items-center justify-center rounded-full bg-orange-500 text-white shadow-lg shadow-orange-500/30 transition-[background-color,transform,box-shadow] duration-200 after:absolute after:inset-0 hover:bg-orange-600 hover:shadow-orange-500/40 active:scale-[0.95] sm:size-14 lg:order-3"
// Gölge yok (§6: derinlik gölgeyle verilmez), hover'da renk
// değişmez (iri dolu yüzeyde orange-600 kırmızımsı okunuyor —
// Bilal, 24 Eyl 2026); tepki: ok kayar, basınca küçülür.
className="group flex size-12 shrink-0 items-center justify-center rounded-full bg-orange-500 text-white transition-transform duration-200 after:absolute after:inset-0 hover:scale-[1.04] active:scale-[0.95] sm:size-14 lg:order-3"
>
<ArrowRight
className="size-6 transition-transform duration-200 group-hover:translate-x-0.5 sm:size-7"