"use client"; // Sonuç sayfasının AI'sız ham listesi: yalnızca başarı sıralamasıyla // yokatlas'tan gelen programlar, dilim sekmeleri halinde. 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 { useLinkStatus } from "next/link"; import { Check, Plus, Rocket, Scale, ShieldCheck, TrendingDown, TrendingUp, } from "lucide-react"; import type { Program, PuanTuruKey, RankResults } from "@/lib/db"; import { riskHesapla, type RiskSeviyesi } from "@/lib/risk"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { MANUEL_LISTE_MAX, ekle, useManuelListe, type ManuelTercih, } from "@/components/manuel-liste/store"; import { ManuelHarita } from "@/components/manuel-liste/manuel-harita"; type DilimKey = keyof RankResults; 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", }; // Seçicideki kısa etiketler — sayfa başlığındaki uzun hallerinden bağımsız const TUR_ETIKETLERI: Record = { say: "Sayısal", ea: "Eşit Ağırlık", soz: "Sözel", dil: "Dil", tyt: "TYT", }; // useLinkStatus Link'in altındaki bir bileşende çağrılmalı; sunucu // gidiş-dönüşü sürerken tıklanan pill hafifçe nabız atar (layout shift yok). function SeciciEtiket({ children }: { children: React.ReactNode }) { const { pending } = useLinkStatus(); return ( {children} ); } function PuanTuruSecici({ sira, aktif, }: { sira: number; aktif: PuanTuruKey; }) { return ( ); } function efektifSira(p: Program): number | null { return p.sira2025 ?? p.sira2024; } function tercihYap(p: Program, adaySira: number): ManuelTercih { const taban = efektifSira(p); return { id: p.id, isim: p.isim, universite: p.universite, il: p.il, efektifSira: taban, risk: riskHesapla(taban, adaySira), }; } export function ProgramTablosu({ sonuclar, adaySira, tur, }: { sonuclar: RankResults; adaySira: number; tur: PuanTuruKey; }) { const liste = useManuelListe(); const [aktifDilim, setAktifDilim] = useState("dengeli"); const listedekiler = new Set(liste.map((t) => t.id)); const doldu = liste.length >= MANUEL_LISTE_MAX; function eklemeyiDene(p: Program) { ekle(tercihYap(p, adaySira)); } return (

Sıralamanla açılan programlar

Yapay zekâsız, ham YÖK Atlas verisi: geçen yılın taban sıralamalarına göre ulaşabildiğin bölümler.{" "} + ile beğendiklerini Seçtiklerim'e at.

{/* Listendeki üniversitelerin konumu — ekledikçe canlı dolar */}
setAktifDilim(v as DilimKey)} className="mt-5" > {/* Kontrol çubuğu: tabloyu süzen her şey tablonun hemen üstünde — solda dilim sekmeleri (birincil), sağda puan türü (ikincil filtre). Dar ekranda alt alta sarar. */}
{DILIMLER.map((d) => ( {d.etiket} {sonuclar[d.key].length} ))}
Puan türü {/* scroll={false}: tür değişince sayfa konumu korunur */}
{DILIMLER.map((d) => (

{d.aciklama}

{sonuclar[d.key].length === 0 ? (

Bu dilimde filtrene uyan program bulunamadı.

) : ( Program Son 5 yıl Kontenjan Taban sıra Listeye ekle {sonuclar[d.key].map((p) => ( ))}
)}
))}
); } // Tabanın 2021–2025 seyri: küçük sıra = daha rekabetçi olduğundan y ekseni // ters çevrilir; çizgi yukarı gidiyorsa bölüm yıllar içinde zorlaşıyor demektir. function SiraGecmisi({ p }: { p: Program }) { const yillar = [ [2021, p.sira2021], [2022, p.sira2022], [2023, p.sira2023], [2024, p.sira2024], [2025, p.sira2025], ].filter((y): y is [number, number] => y[1] != null); if (yillar.length < 2) return ; const siralar = yillar.map(([, s]) => s); const min = Math.min(...siralar); const max = Math.max(...siralar); const W = 56; const H = 18; const PAD = 2; const noktalar = yillar.map(([yil, s], i) => { const x = PAD + (i / (yillar.length - 1)) * (W - PAD * 2); // min sıra (en rekabetçi) üstte dursun const y = max === min ? H / 2 : PAD + ((s - min) / (max - min)) * (H - PAD * 2); return { x, y, yil, s }; }); const son = noktalar[noktalar.length - 1]; const ozet = yillar .map(([yil, s]) => `${yil}: ~${s.toLocaleString("tr-TR")}.`) .join("\n"); return ( {ozet} `${n.x},${n.y}`).join(" ")} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> ); } // Taban sıra iki yıl üst üste %10'dan fazla oynadıysa yönünü gösterir; // küçük sıra = daha rekabetçi taban demektir. function TabanTrendi({ p }: { p: Program }) { if (p.sira2025 == null || p.sira2024 == null) return null; const oran = (p.sira2024 - p.sira2025) / p.sira2024; if (Math.abs(oran) < 0.1) return null; const yukseliyor = oran > 0; const gecis = `${p.sira2024.toLocaleString("tr-TR")} → ${p.sira2025.toLocaleString("tr-TR")}`; return yukseliyor ? ( {`Taban yükseliyor: ${gecis}`} ) : ( {`Taban geriliyor: ${gecis}`} ); } function ProgramSatiri({ program: p, adaySira, listede, doldu, onEkle, }: { program: Program; adaySira: number; listede: boolean; doldu: boolean; onEkle: (p: Program) => void; }) { const taban = efektifSira(p); const risk = riskHesapla(taban, adaySira); const devlet = p.unitur === "DEVLET"; return ( {risk ? ( ) : null} {p.isim} {devlet ? "Devlet" : "Vakıf"} {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")}.` : "—"} ); }