"use client"; // /meraklisina'nın canlı anlatım katmanı: kullanıcı kendi sıralamasını girer, // dilim şeridi ve kademeli filtre hunisi o sıralamanın GERÇEK sayılarıyla // güncellenir. Provider sayfadaki metin bölümlerini (RSC children) sarar; // görseller context'ten okur, böylece tek girişle iki diyagram birden yaşar. import { createContext, useContext, useEffect, useRef, useState, type ReactNode, } from "react"; import type { DilimKey, SihirbazFacetleri } from "@/lib/db"; const TUR_SECENEKLERI = [ { deger: "say", etiket: "Sayısal" }, { deger: "ea", etiket: "Eşit Ağırlık" }, { deger: "soz", etiket: "Sözel" }, { deger: "dil", etiket: "Dil" }, { deger: "tyt", etiket: "TYT" }, ] as const; export type DemoVeri = { facetler: SihirbazFacetleri; dilimToplam: Record; }; type DemoDurum = { sira: number; tur: string; veri: DemoVeri; yukleniyor: boolean; // Huni seçimleri ve kademeli facet cevapları kategori: string | null; il: string | null; tip: "devlet" | "vakif" | null; kategoriFacet: SihirbazFacetleri | null; // ?kategori= cevabı (iller buradan) ilFacet: SihirbazFacetleri | null; // ?kategori&il cevabı (tipler buradan) kategoriSec: (k: string | null) => void; ilSec: (i: string | null) => void; tipSec: (t: "devlet" | "vakif" | null) => void; siraDegistir: (ham: string) => void; turDegistir: (t: string) => void; siraMetni: string; }; const DemoContext = createContext(null); function useDemo(): DemoDurum { const ctx = useContext(DemoContext); if (!ctx) throw new Error("Demo bileşenleri SiralamaDemo içinde kullanılmalı"); return ctx; } async function facetGetir( sira: number, tur: string, secim: { kategori?: string; il?: string; dilimler?: boolean }, signal: AbortSignal, ): Promise<{ facetler: SihirbazFacetleri; dilimToplam?: Record } | null> { const params = new URLSearchParams({ sira: String(sira), tur }); if (secim.kategori) params.append("kategori", secim.kategori); if (secim.il) params.append("il", secim.il); if (secim.dilimler) params.set("dilimler", "1"); try { const res = await fetch(`/api/facetler?${params}`, { signal }); if (!res.ok) return null; return (await res.json()) as { facetler: SihirbazFacetleri; dilimToplam?: Record; }; } catch { return null; } } export function SiralamaDemo({ baslangicSira, baslangicVeri, children, }: { baslangicSira: number; baslangicVeri: DemoVeri; children: ReactNode; }) { const [siraMetni, setSiraMetni] = useState( baslangicSira.toLocaleString("tr-TR"), ); const [sira, setSira] = useState(baslangicSira); const [tur, setTur] = useState("say"); const [veri, setVeri] = useState(baslangicVeri); const [yukleniyor, setYukleniyor] = useState(false); const [kategori, setKategori] = useState(null); const [il, setIl] = useState(null); const [tip, setTip] = useState<"devlet" | "vakif" | null>(null); const [kategoriFacet, setKategoriFacet] = useState( null, ); const [ilFacet, setIlFacet] = useState(null); const zamanlayici = useRef | null>(null); // Sıralama/tür değişti → ana veriyi tazele, huni seçimlerini sıfırla useEffect(() => { if (sira === baslangicSira && tur === "say" && veri === baslangicVeri) { return; // ilk render: sunucudan gelen veri zaten güncel } const ctrl = new AbortController(); // eslint-disable-next-line react-hooks/set-state-in-effect -- fetch başlangıç işareti setYukleniyor(true); setKategori(null); setIl(null); setTip(null); setKategoriFacet(null); setIlFacet(null); facetGetir(sira, tur, { dilimler: true }, ctrl.signal) .then((cevap) => { if (!cevap?.dilimToplam) return; setVeri({ facetler: cevap.facetler, dilimToplam: cevap.dilimToplam }); }) .finally(() => setYukleniyor(false)); return () => ctrl.abort(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [sira, tur]); // Kategori seçildi → o kategorinin illerini getir useEffect(() => { if (!kategori) return; const ctrl = new AbortController(); facetGetir(sira, tur, { kategori }, ctrl.signal).then((cevap) => { if (cevap) setKategoriFacet(cevap.facetler); }); return () => ctrl.abort(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [kategori]); // İl seçildi → kategori+il ile tipleri getir useEffect(() => { if (!kategori || !il) return; const ctrl = new AbortController(); facetGetir(sira, tur, { kategori, il }, ctrl.signal).then((cevap) => { if (cevap) setIlFacet(cevap.facetler); }); return () => ctrl.abort(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [kategori, il]); function siraDegistir(ham: string) { const rakamlar = ham.replace(/\D/g, "").slice(0, 7); setSiraMetni(rakamlar ? Number(rakamlar).toLocaleString("tr-TR") : ""); if (zamanlayici.current) clearTimeout(zamanlayici.current); const deger = Number(rakamlar); if (!deger || deger < 1 || deger > 4_000_000) return; zamanlayici.current = setTimeout(() => setSira(deger), 450); } const durum: DemoDurum = { sira, tur, veri, yukleniyor, kategori, il, tip, kategoriFacet, ilFacet, kategoriSec: (k) => { setKategori(k); setIl(null); setTip(null); setKategoriFacet(null); setIlFacet(null); }, ilSec: (i) => { setIl(i); setTip(null); setIlFacet(null); }, tipSec: setTip, siraDegistir, turDegistir: setTur, siraMetni, }; return {children}; } /** Sıralama girişi + puan türü — iki diyagramı birden süren tek kumanda. */ export function DemoKumanda() { const { siraMetni, siraDegistir, tur, turDegistir, sira, yukleniyor } = useDemo(); return (
siraDegistir(e.target.value)} placeholder="ör. 85.000" className="h-11 w-full rounded-xl border border-slate-200 bg-white px-4 text-base font-medium tabular-nums outline-none transition-colors focus:border-primary sm:w-44" />
{TUR_SECENEKLERI.map((t) => ( ))}

{yukleniyor ? "Hesaplanıyor…" : `Aşağıdaki tüm sayılar ${sira.toLocaleString("tr-TR")}. sıradaki bir aday için gerçek YÖK Atlas verisidir.`}

); } /** Sıralama ekseni: girilen sıraya göre dilim sınırları ve gerçek adetler. */ export function DilimSeridiCanli() { const { sira, veri, yukleniyor } = useDemo(); const t = veri.dilimToplam; const dengeliUst = Math.round(sira * 1.4); const fmt = (n: number) => n.toLocaleString("tr-TR"); return (
{/* eksen */} 1. sıra (en iyi) taban sıralaması büyüdükçe → {/* hayal */} Hayal {fmt(t.hayal)} program tabanı senden iyi {/* aday işareti */} SEN · {fmt(sira)}. {/* dengeli */} Dengeli {fmt(t.dengeli)} program {fmt(sira)}. – {fmt(dengeliUst)}. arası taban {/* garanti */} Garanti {fmt(t.garanti)} program {fmt(dengeliUst + 1)}. sonrası — sonu yok, sayfaladıkça uzar
Liste her dilimde{" "} sana en yakın tabandan uzağa doğru {" "} sıralanır; “Daha fazla göster” dedikçe uzaktakiler gelir.
); } function HuniCubugu({ etiket, adet, oran, }: { etiket: string; adet: number; oran: number; }) { return (
{etiket} {adet.toLocaleString("tr-TR")}
); } function SecimCipleri({ secenekler, secili, onSec, }: { secenekler: T[]; secili: string | null; onSec: (anahtar: string | null) => void; }) { return (
{secenekler.map((s) => ( ))}
); } // AI listesinin dilim iskeleti: üstte hayal, ortada dengeli, sonda garanti. // Ürünün risk renkleriyle boyanır; sayılar prompt'taki kuralın orta değeri. const LISTE_ISKELETI: { dilim: string; adet: number; renk: string }[] = [ { dilim: "hayal", adet: 5, renk: "bg-red-400" }, { dilim: "dengeli", adet: 13, renk: "bg-amber-400" }, { dilim: "garanti", adet: 6, renk: "bg-emerald-500" }, ]; /** Huninin finali: kalan havuzun yapay zekâya gidişi ve 24'lük listenin iskeleti. Havuz 24'ün altına düşerse gevşetme kuralını canlı gösterir. */ function YapayZekaAsamasi({ havuz }: { havuz: number }) { const dar = havuz < 24; return (

Sonra sıra yapay zekâda

Seçimlerinden geriye kalan{" "} {havuz.toLocaleString("tr-TR")} programlık havuz {" "} yapay zekâya gider; o da bu havuzdan tam 24 tercih seçer ve şu iskelete oturtur:

{/* 24 hücrelik liste şeridi — renk + metin birlikte (renk tek başına anlam taşımaz) */}
{LISTE_ISKELETI.flatMap((b) => Array.from({ length: b.adet }, (_, i) => ( )), )}
üstte 4–6 hayal ortada 12–14{" "} dengeli sonda 5–7{" "} garanti
  • · Havuzda olmayan bir bölümü yazamaz — listedeki her satırın kimliği havuzla karşılaştırılır, uymayan liste yeniden ürettirilir.
  • · Her satıra tek cümlelik gerekçe ve bir risk notu ekler; sıraya neyi neden koyduğu görünür.
  • · Önceliklerin (iş garantisi, maaş, prestij…) hangi programın öne geçeceğini belirler.
  • · Havuz 24'ün altına düşerse filtreler adım adım gevşetilir — önce il, sonra alan — ve bu, listende açıkça belirtilir.
{dar ? (

Bu seçimle havuz 24'ün altına düştü. Gerçek sihirbaz bu durumda filtreleri adım adım gevşetir — önce il, sonra alan tercihi esnetilir — ve bunu listende açıkça belirtir; liste her zaman 24'e tamamlanır.

) : null}
); } /** Kademeli filtre hunisi: gerçek sihirbaz mantığının oynanabilir kopyası. */ export function HuniCanli() { const { veri, yukleniyor, kategori, il, tip, kategoriFacet, ilFacet, kategoriSec, ilSec, tipSec, } = useDemo(); const toplam = veri.facetler.toplam; const kategoriAdet = kategori != null ? (veri.facetler.kategoriler.find((k) => k.ad === kategori)?.adet ?? 0) : null; const ilAdet = il != null && kategoriFacet ? (kategoriFacet.iller.find((i) => i.il === il)?.adet ?? 0) : null; const tipAdet = tip != null && ilFacet ? (ilFacet.uniturler.find((u) => u.grup === tip)?.adet ?? 0) : null; const kategoriSecenekleri = veri.facetler.kategoriler.slice(0, 6).map((k) => ({ anahtar: k.ad, etiket: k.ad, adet: k.adet, })); const ilSecenekleri = (kategoriFacet?.iller ?? []).slice(0, 6).map((i) => ({ anahtar: i.il, etiket: i.il.toLocaleLowerCase("tr-TR"), adet: i.adet, })); const tipSecenekleri = (ilFacet?.uniturler ?? []).map((u) => ({ anahtar: u.grup, etiket: u.grup === "devlet" ? "Devlet" : "Vakıf", adet: u.adet, })); // Yapay zekâya giden havuz: huninin en derin (en son seçilmiş) katmanı const havuz = tipAdet ?? ilAdet ?? kategoriAdet ?? toplam; return (
{/* Huni çubukları — her seçim bir çubuk daha ekler */}
{kategori != null && kategoriAdet != null ? (
1. 0 ? kategoriAdet / toplam : 0} />
) : null} {il != null && ilAdet != null ? (
2. 0 ? ilAdet / toplam : 0} />
) : null} {tip != null && tipAdet != null ? (
3. 0 ? tipAdet / toplam : 0} />
) : null}
{/* Seçim adımları — sihirbazın küçük, oynanabilir hali */}

1. Bir alan seç

{kategori != null ? (

2. Bir il seç{" "} (iller artık yalnızca “{kategori}” programlarını sayıyor)

{ilSecenekleri.length > 0 ? ( ) : (

Yükleniyor…

)}
) : null} {kategori != null && il != null ? (

3. Üniversite tipi seç{" "} (sayılar hem alanı hem ili hesaba katıyor)

{tipSecenekleri.length > 0 ? ( tipSec(t as "devlet" | "vakif" | null)} /> ) : (

Yükleniyor…

)}
) : null}
); }