Files
kolaytercih/src/features/katalog/components/program-netleri.tsx
bilalgursen 1a176dd085 feat(tasarim): geniş ekranda liste satırları ince
- Tür/il/süre satırı sm'den itibaren adla aynı satırda (SATIR_GRUP)
- lg'de hücre dolgusu bir kademe az, satır düğmeleri 36 px (§8)
- 1280 px ölçüm: bölüm listesi 83 → 61 px; /sonuc 68 px (uzun adda 92)
- Telefonda değişiklik yok (44 px düğme, alt alta düzen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 01:45:03 +03:00

223 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
// 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).
import { SATIR_DUGME_BOYU } from "@/components/liste-satiri";
import { useState } from "react";
import { ListChecks } from "lucide-react";
import type { ProgramNetSatiri } from "@/types/yokatlas";
import { olay } from "@/lib/analitik";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
// Ders sırası ÖSYM oturum sırasıyla; max = testteki soru sayısı.
const DERSLER: {
alan: keyof Omit<ProgramNetSatiri, "yil" | "puanTuru" | "tabanPuan" | "obp">;
etiket: string;
max: number;
}[] = [
{ alan: "tytTrkNet", etiket: "TYT Türkçe", max: 40 },
{ alan: "tytSosNet", etiket: "TYT Sosyal Bilimler", max: 20 },
{ alan: "tytMatNet", etiket: "TYT Matematik", max: 40 },
{ alan: "tytFenNet", etiket: "TYT Fen Bilimleri", max: 20 },
{ alan: "aytMatNet", etiket: "AYT Matematik", max: 40 },
{ alan: "aytFizNet", etiket: "AYT Fizik", max: 14 },
{ alan: "aytKimNet", etiket: "AYT Kimya", max: 13 },
{ alan: "aytBioNet", etiket: "AYT Biyoloji", max: 13 },
{ alan: "aytTdeNet", etiket: "AYT Türk Dili ve Edebiyatı", max: 24 },
{ alan: "aytTrh1Net", etiket: "AYT Tarih-1", max: 10 },
{ alan: "aytCog1Net", etiket: "AYT Coğrafya-1", max: 6 },
{ alan: "aytTrh2Net", etiket: "AYT Tarih-2", max: 11 },
{ alan: "aytCog2Net", etiket: "AYT Coğrafya-2", max: 11 },
{ alan: "aytDinNet", etiket: "AYT Din Kültürü", max: 6 },
{ alan: "aytFelNet", etiket: "AYT Felsefe Grubu", max: 12 },
{ alan: "ydtYdilNet", etiket: "YDT Yabancı Dil", max: 80 },
];
const net = (n: number) =>
n.toLocaleString("tr-TR", { maximumFractionDigits: 2 });
type Durum = "bekliyor" | "yukleniyor" | "hazir" | "hata";
export function ProgramNetleriButonu({
programId,
isim,
universite,
className = "",
}: {
programId: string;
isim: string;
universite?: string;
className?: string;
}) {
const [durum, setDurum] = useState<Durum>("bekliyor");
const [netler, setNetler] = useState<ProgramNetSatiri[]>([]);
async function yukle() {
setDurum("yukleniyor");
try {
const res = await fetch(`/api/netler?id=${programId}`);
if (!res.ok) throw new Error(String(res.status));
const veri = (await res.json()) as { netler: ProgramNetSatiri[] };
setNetler(veri.netler);
setDurum("hazir");
} catch {
setDurum("hata");
}
}
function acildi(acik: boolean) {
if (!acik) return;
olay("netler_goruntulendi");
if (durum === "bekliyor" || durum === "hata") yukle();
}
return (
<Dialog onOpenChange={acildi}>
<DialogTrigger asChild>
<button
type="button"
title="Son yerleşenin netleri"
onClick={(e) => e.stopPropagation()}
className={`inline-flex ${SATIR_DUGME_BOYU} cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 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.92] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${className}`}
>
<ListChecks className="size-4" aria-hidden />
<span className="sr-only">{isim} — son yerleşenin netleri</span>
</button>
</DialogTrigger>
<DialogContent
className="sm:max-w-md"
onClick={(e) => e.stopPropagation()}
>
<DialogHeader>
<DialogTitle className="pr-10">Son yerleşenin netleri</DialogTitle>
<DialogDescription>
{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>
);
}
function NetlerIcerik({
durum,
netler,
}: {
durum: Durum;
netler: ProgramNetSatiri[];
}) {
if (durum === "yukleniyor" || durum === "bekliyor") {
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>
);
}
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>
);
}
if (netler.length === 0) {
return (
<p className="py-4 text-center text-sm text-slate-500">
Bu program için YÖK Atlas&apos;ta net verisi yayınlanmamış.
</p>
);
}
const dersler = DERSLER.filter((d) =>
netler.some((y) => y[d.alan] != null),
);
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>
))}
<TableRow>
<TableCell className="py-2 text-xs font-medium text-slate-600">
OBP
</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>
))}
</TableRow>
<TableRow>
<TableCell className="py-2 text-xs font-semibold text-slate-700">
Yerleşme puanı
</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>
);
}