"use client"; // Sonuç sayfasının Yapay Zeka'sız ham listesi: yalnızca başarı sıralamasıyla // yokatlas'tan gelen programlar, dilim sekmeleri halinde ve yakından uzağa // sayfalanır ("daha fazla göster" /api/programlar'dan akıtır). Her satırın "+" // butonu programı manuel 24'lük listeye ekler (bkz. manuel-liste/store). import { useState } from "react"; import Link from "next/link"; import { ChevronDown, Rocket, Scale, ShieldCheck, } from "lucide-react"; import type { DilimKey, Program, PuanTuruKey, RankResults } from "@/lib/db"; import { riskHesapla, type RiskSeviyesi } from "@/lib/risk"; import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug"; import { ProgramSiraGecmisi, ProgramTabanTrendi, programEtkinSira, } from "@/components/program-liste-verileri"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu"; import { ProgramNetleriButonu } from "@/components/program-netleri"; import { UniLogo } from "@/components/uni-logo"; const DILIMLER: { key: DilimKey; etiket: string; aciklama: string; icon: typeof Rocket; renk: string; }[] = [ { key: "hayal", etiket: "Hayal", aciklama: "Tabanı sıralamanın üstünde — şansını denediğin satırlar.", icon: Rocket, renk: "text-red-500", }, { key: "dengeli", etiket: "Dengeli", aciklama: "Tabanı sıralamana denk — listenin bel kemiği.", icon: Scale, renk: "text-amber-500", }, { key: "garanti", etiket: "Garanti", aciklama: "Tabanı belirgin şekilde altında — güvenli bölge.", icon: ShieldCheck, renk: "text-emerald-600", }, ]; const RISK_NOKTA: Record = { guvenli: "bg-emerald-500", "az-riskli": "bg-amber-500", riskli: "bg-red-500", }; const BOS_EKSTRA: Record = { hayal: [], dengeli: [], garanti: [], }; // Dengeli ve hayal dilimleri tamamen boşsa (sıralama çok gerideyse) boş bir // "dengeli" sekmesi yerine dolu olan "garanti" ile açılır. function varsayilanDilim(sonuclar: RankResults): DilimKey { return sonuclar.toplam.dengeli === 0 && sonuclar.toplam.hayal === 0 ? "garanti" : "dengeli"; } export function ProgramTablosu({ sonuclar, adaySira, tur, }: { sonuclar: RankResults; adaySira: number; tur: PuanTuruKey; }) { const [aktifDilim, setAktifDilim] = useState(() => varsayilanDilim(sonuclar), ); // "Daha fazla göster" ile sayfalanan devam satırları. Sıra/tür değişince // (puan türü seçici aynı bileşeni yeniden render eder, remount etmez) // render sırasında sıfırlanır ki eski türün satırları yenisine karışmasın. const [ekstra, setEkstra] = useState(BOS_EKSTRA); const [yuklenen, setYuklenen] = useState(null); const veriAnahtari = `${adaySira}-${tur}`; const [oncekiAnahtar, setOncekiAnahtar] = useState(veriAnahtari); if (veriAnahtari !== oncekiAnahtar) { setOncekiAnahtar(veriAnahtari); setEkstra(BOS_EKSTRA); // Yeni sıra/türde dengeli+hayal boş kaldıysa boş sekmede bırakma if (varsayilanDilim(sonuclar) === "garanti" && aktifDilim !== "garanti") { setAktifDilim("garanti"); } } async function dahaFazlaYukle(dilim: DilimKey, offset: number) { setYuklenen(dilim); try { const res = await fetch( `/api/programlar?sira=${adaySira}&tur=${tur}&dilim=${dilim}&offset=${offset}`, ); if (!res.ok) return; const veri = (await res.json()) as { programlar: Program[] }; // Aynı sayfanın iki kez istenmesine karşı id bazında tekilleştir setEkstra((mevcut) => { const eldekiler = new Set( [...sonuclar[dilim], ...mevcut[dilim]].map((p) => p.id), ); return { ...mevcut, [dilim]: [ ...mevcut[dilim], ...veri.programlar.filter((p) => !eldekiler.has(p.id)), ], }; }); } catch { } finally { setYuklenen(null); } } return (
setAktifDilim(v as DilimKey)} > {/* Dilim sekmeleri tablonun hemen üstünde; puan türü burada değil, navbar'daki sıralama rozetinden değiştirilir. */} {DILIMLER.map((d) => ( {d.etiket} {sonuclar.toplam[d.key].toLocaleString("tr-TR")} ))} {DILIMLER.map((d) => { const satirlar = [...sonuclar[d.key], ...ekstra[d.key]]; const toplam = sonuclar.toplam[d.key]; return (

{d.aciklama}

{satirlar.length === 0 ? (

Bu dilimde filtrene uyan program bulunamadı.

) : ( Program Son 5 yıl Kontenjan Taban sıra Netler ve listeye ekle {satirlar.map((p) => ( ))}
)}
{satirlar.length < toplam ? (
{satirlar.length.toLocaleString("tr-TR")} /{" "} {toplam.toLocaleString("tr-TR")} program
) : null}
); })}
); } function ProgramSatiri({ program: p, adaySira, tur, }: { program: Program; adaySira: number; tur: PuanTuruKey; }) { const taban = programEtkinSira(p); const risk = riskHesapla(taban, adaySira); const devlet = p.unitur === "DEVLET"; return ( {risk ? ( ) : null} e.stopPropagation()} > {p.isim} {devlet ? "Devlet" : "Vakıf"} e.stopPropagation()} > {p.universite} {p.il ? ` · ${p.il.toLocaleLowerCase("tr-TR")}` : ""} {p.sure ? ( · {p.sure} yıl ) : null} {p.kontenjan2025 != null ? ( {p.yerlesen2025 != null && p.yerlesen2025 < p.kontenjan2025 ? ( boş kaldı ) : null} {p.kontenjan2025.toLocaleString("tr-TR")} kişi ) : ( "—" )} {taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—"} ); }