Update routing in next.config.ts to redirect "/tercih-robotu" to the hero form, add simple-icons dependency in package.json, and enhance database indexing for improved query performance. Remove unused SVG files and update metadata across various pages for better SEO and clarity.
All checks were successful
Deploy / deploy (push) Successful in 14m14s
All checks were successful
Deploy / deploy (push) Successful in 14m14s
This commit is contained in:
640
src/app/meraklisina/demo.tsx
Normal file
640
src/app/meraklisina/demo.tsx
Normal file
@@ -0,0 +1,640 @@
|
||||
"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<DilimKey, number>;
|
||||
};
|
||||
|
||||
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<DemoDurum | null>(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<DilimKey, number> } | 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<DilimKey, number>;
|
||||
};
|
||||
} 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<string | null>(null);
|
||||
const [il, setIl] = useState<string | null>(null);
|
||||
const [tip, setTip] = useState<"devlet" | "vakif" | null>(null);
|
||||
const [kategoriFacet, setKategoriFacet] = useState<SihirbazFacetleri | null>(
|
||||
null,
|
||||
);
|
||||
const [ilFacet, setIlFacet] = useState<SihirbazFacetleri | null>(null);
|
||||
const zamanlayici = useRef<ReturnType<typeof setTimeout> | 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 <DemoContext.Provider value={durum}>{children}</DemoContext.Provider>;
|
||||
}
|
||||
|
||||
/** 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 (
|
||||
<div className="rounded-2xl border border-primary/20 bg-primary/5 p-5">
|
||||
<label
|
||||
htmlFor="demo-sira"
|
||||
className="font-heading text-sm font-bold text-slate-900"
|
||||
>
|
||||
Kendi sıralamanı gir, aşağıdaki görseller sana göre değişsin
|
||||
</label>
|
||||
<div className="mt-3 flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<input
|
||||
id="demo-sira"
|
||||
inputMode="numeric"
|
||||
autoComplete="off"
|
||||
value={siraMetni}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<div
|
||||
role="radiogroup"
|
||||
aria-label="Puan türü"
|
||||
className="flex flex-wrap gap-1.5"
|
||||
>
|
||||
{TUR_SECENEKLERI.map((t) => (
|
||||
<button
|
||||
key={t.deger}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={tur === t.deger}
|
||||
onClick={() => turDegistir(t.deger)}
|
||||
className={
|
||||
tur === t.deger
|
||||
? "cursor-pointer rounded-full border border-primary bg-primary px-3 py-1.5 text-xs font-medium text-white"
|
||||
: "cursor-pointer rounded-full border border-slate-200 bg-white px-3 py-1.5 text-xs text-slate-600 transition-colors duration-200 hover:border-slate-300"
|
||||
}
|
||||
>
|
||||
{t.etiket}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
className={`mt-2 text-xs text-slate-500 ${yukleniyor ? "animate-pulse" : ""}`}
|
||||
aria-live="polite"
|
||||
>
|
||||
{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.`}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** 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 (
|
||||
<figure className={`mt-6 ${yukleniyor ? "animate-pulse opacity-60" : ""}`}>
|
||||
<svg
|
||||
viewBox="0 0 640 168"
|
||||
role="img"
|
||||
aria-label={`Sıralama ekseni: ${fmt(sira)}. sıradaki aday için Hayal diliminde ${fmt(t.hayal)}, Dengeli diliminde ${fmt(t.dengeli)}, Garanti diliminde ${fmt(t.garanti)} program var.`}
|
||||
className="w-full"
|
||||
>
|
||||
{/* eksen */}
|
||||
<line
|
||||
x1="20"
|
||||
y1="86"
|
||||
x2="620"
|
||||
y2="86"
|
||||
stroke="currentColor"
|
||||
className="text-slate-300"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<text x="20" y="150" className="fill-slate-400 text-[11px]">
|
||||
1. sıra (en iyi)
|
||||
</text>
|
||||
<text
|
||||
x="620"
|
||||
y="150"
|
||||
textAnchor="end"
|
||||
className="fill-slate-400 text-[11px]"
|
||||
>
|
||||
taban sıralaması büyüdükçe →
|
||||
</text>
|
||||
|
||||
{/* hayal */}
|
||||
<rect x="20" y="70" width="150" height="32" rx="4" className="fill-red-100" />
|
||||
<text x="95" y="40" textAnchor="middle" className="fill-red-600 text-[12px] font-semibold">
|
||||
Hayal
|
||||
</text>
|
||||
<text x="95" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums">
|
||||
{fmt(t.hayal)} program
|
||||
</text>
|
||||
<text x="95" y="123" textAnchor="middle" className="fill-slate-500 text-[10px]">
|
||||
tabanı senden iyi
|
||||
</text>
|
||||
|
||||
{/* aday işareti */}
|
||||
<line x1="172" y1="24" x2="172" y2="112" stroke="currentColor" className="text-slate-900" strokeWidth="2" strokeDasharray="4 3" />
|
||||
<text x="172" y="16" textAnchor="middle" className="fill-slate-900 text-[12px] font-bold">
|
||||
SEN · {fmt(sira)}.
|
||||
</text>
|
||||
|
||||
{/* dengeli */}
|
||||
<rect x="174" y="70" width="120" height="32" rx="4" className="fill-amber-100" />
|
||||
<text x="234" y="40" textAnchor="middle" className="fill-amber-600 text-[12px] font-semibold">
|
||||
Dengeli
|
||||
</text>
|
||||
<text x="234" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums">
|
||||
{fmt(t.dengeli)} program
|
||||
</text>
|
||||
<text x="234" y="123" textAnchor="middle" className="fill-slate-500 text-[10px]">
|
||||
{fmt(sira)}. – {fmt(dengeliUst)}. arası taban
|
||||
</text>
|
||||
|
||||
{/* garanti */}
|
||||
<rect x="296" y="70" width="324" height="32" rx="4" className="fill-emerald-100" />
|
||||
<text x="458" y="40" textAnchor="middle" className="fill-emerald-700 text-[12px] font-semibold">
|
||||
Garanti
|
||||
</text>
|
||||
<text x="458" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums">
|
||||
{fmt(t.garanti)} program
|
||||
</text>
|
||||
<text x="458" y="123" textAnchor="middle" className="fill-slate-500 text-[10px]">
|
||||
{fmt(dengeliUst + 1)}. sonrası — sonu yok, sayfaladıkça uzar
|
||||
</text>
|
||||
</svg>
|
||||
<figcaption className="mt-2 text-xs text-slate-500">
|
||||
Liste her dilimde{" "}
|
||||
<span className="font-medium text-slate-700">
|
||||
sana en yakın tabandan uzağa doğru
|
||||
</span>{" "}
|
||||
sıralanır; “Daha fazla göster” dedikçe uzaktakiler gelir.
|
||||
</figcaption>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
|
||||
function HuniCubugu({
|
||||
etiket,
|
||||
adet,
|
||||
oran,
|
||||
}: {
|
||||
etiket: string;
|
||||
adet: number;
|
||||
oran: number;
|
||||
}) {
|
||||
return (
|
||||
<div className="min-w-0 flex-1">
|
||||
<div
|
||||
className="flex h-9 min-w-36 items-center justify-between gap-2 rounded-lg bg-primary/85 px-3 transition-[width] duration-300"
|
||||
style={{ width: `${Math.min(Math.max(oran * 100, 36), 100)}%` }}
|
||||
>
|
||||
<span className="truncate text-xs font-medium text-white">{etiket}</span>
|
||||
<span className="shrink-0 text-xs font-bold tabular-nums text-white">
|
||||
{adet.toLocaleString("tr-TR")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SecimCipleri<T extends { anahtar: string; etiket: string; adet: number }>({
|
||||
secenekler,
|
||||
secili,
|
||||
onSec,
|
||||
}: {
|
||||
secenekler: T[];
|
||||
secili: string | null;
|
||||
onSec: (anahtar: string | null) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{secenekler.map((s) => (
|
||||
<button
|
||||
key={s.anahtar}
|
||||
type="button"
|
||||
aria-pressed={secili === s.anahtar}
|
||||
onClick={() => onSec(secili === s.anahtar ? null : s.anahtar)}
|
||||
className={
|
||||
secili === s.anahtar
|
||||
? "cursor-pointer rounded-full border border-orange-500 bg-orange-500 px-3 py-1.5 text-xs font-medium text-white"
|
||||
: "cursor-pointer rounded-full border border-slate-200 bg-white px-3 py-1.5 text-xs text-slate-600 transition-colors duration-200 hover:border-slate-300"
|
||||
}
|
||||
>
|
||||
{s.etiket}
|
||||
<span className="ml-1 text-[10px] opacity-70 tabular-nums">
|
||||
{s.adet.toLocaleString("tr-TR")}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div className="border-t border-slate-100 pt-4">
|
||||
<p className="text-xs font-semibold text-slate-500">
|
||||
Sonra sıra yapay zekâda
|
||||
</p>
|
||||
<p className="mt-1.5 text-xs leading-5 text-slate-600">
|
||||
Seçimlerinden geriye kalan{" "}
|
||||
<span className="font-bold tabular-nums text-slate-900">
|
||||
{havuz.toLocaleString("tr-TR")} programlık havuz
|
||||
</span>{" "}
|
||||
yapay zekâya gider; o da bu havuzdan tam 24 tercih seçer ve şu iskelete
|
||||
oturtur:
|
||||
</p>
|
||||
|
||||
{/* 24 hücrelik liste şeridi — renk + metin birlikte (renk tek başına
|
||||
anlam taşımaz) */}
|
||||
<div
|
||||
role="img"
|
||||
aria-label="24 tercihlik listenin yapısı: en üstte yaklaşık 5 hayal, ortada 13 dengeli, sonda 6 garanti tercih."
|
||||
className="mt-3"
|
||||
>
|
||||
<div className="flex gap-1">
|
||||
{LISTE_ISKELETI.flatMap((b) =>
|
||||
Array.from({ length: b.adet }, (_, i) => (
|
||||
<span
|
||||
key={`${b.dilim}-${i}`}
|
||||
className={`h-6 min-w-0 flex-1 rounded ${b.renk}`}
|
||||
/>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1.5 flex justify-between text-[11px] text-slate-500">
|
||||
<span>
|
||||
üstte 4–6 <span className="font-semibold text-red-600">hayal</span>
|
||||
</span>
|
||||
<span>
|
||||
ortada 12–14{" "}
|
||||
<span className="font-semibold text-amber-600">dengeli</span>
|
||||
</span>
|
||||
<span>
|
||||
sonda 5–7{" "}
|
||||
<span className="font-semibold text-emerald-600">garanti</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="mt-3 space-y-1 text-xs leading-5 text-slate-600">
|
||||
<li>
|
||||
· 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.
|
||||
</li>
|
||||
<li>
|
||||
· Her satıra tek cümlelik gerekçe ve bir risk notu ekler; sıraya neyi
|
||||
neden koyduğu görünür.
|
||||
</li>
|
||||
<li>
|
||||
· Önceliklerin (iş garantisi, maaş, prestij…) hangi programın öne
|
||||
geçeceğini belirler.
|
||||
</li>
|
||||
<li>
|
||||
· 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.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{dar ? (
|
||||
<p className="mt-3 rounded-lg bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
|
||||
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.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** 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 (
|
||||
<div
|
||||
className={`mt-6 space-y-4 rounded-2xl border border-slate-200 bg-white p-5 ${
|
||||
yukleniyor ? "animate-pulse opacity-60" : ""
|
||||
}`}
|
||||
>
|
||||
{/* Huni çubukları — her seçim bir çubuk daha ekler */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-6 shrink-0" />
|
||||
<HuniCubugu
|
||||
etiket="Sıralamanla açılan tüm programlar"
|
||||
adet={toplam}
|
||||
oran={1}
|
||||
/>
|
||||
</div>
|
||||
{kategori != null && kategoriAdet != null ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-6 shrink-0 text-right text-xs font-bold text-slate-400">
|
||||
1.
|
||||
</span>
|
||||
<HuniCubugu
|
||||
etiket={`“${kategori}” seçince`}
|
||||
adet={kategoriAdet}
|
||||
oran={toplam > 0 ? kategoriAdet / toplam : 0}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{il != null && ilAdet != null ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-6 shrink-0 text-right text-xs font-bold text-slate-400">
|
||||
2.
|
||||
</span>
|
||||
<HuniCubugu
|
||||
etiket={`+ “${il.toLocaleLowerCase("tr-TR")}” seçince`}
|
||||
adet={ilAdet}
|
||||
oran={toplam > 0 ? ilAdet / toplam : 0}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{tip != null && tipAdet != null ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-6 shrink-0 text-right text-xs font-bold text-slate-400">
|
||||
3.
|
||||
</span>
|
||||
<HuniCubugu
|
||||
etiket={`+ “${tip === "devlet" ? "Devlet" : "Vakıf"}” seçince`}
|
||||
adet={tipAdet}
|
||||
oran={toplam > 0 ? tipAdet / toplam : 0}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Seçim adımları — sihirbazın küçük, oynanabilir hali */}
|
||||
<div className="space-y-3 border-t border-slate-100 pt-4">
|
||||
<div>
|
||||
<p className="mb-1.5 text-xs font-semibold text-slate-500">
|
||||
1. Bir alan seç
|
||||
</p>
|
||||
<SecimCipleri
|
||||
secenekler={kategoriSecenekleri}
|
||||
secili={kategori}
|
||||
onSec={kategoriSec}
|
||||
/>
|
||||
</div>
|
||||
{kategori != null ? (
|
||||
<div>
|
||||
<p className="mb-1.5 text-xs font-semibold text-slate-500">
|
||||
2. Bir il seç{" "}
|
||||
<span className="font-normal text-slate-400">
|
||||
(iller artık yalnızca “{kategori}” programlarını sayıyor)
|
||||
</span>
|
||||
</p>
|
||||
{ilSecenekleri.length > 0 ? (
|
||||
<SecimCipleri
|
||||
secenekler={ilSecenekleri}
|
||||
secili={il}
|
||||
onSec={ilSec}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-xs text-slate-400">Yükleniyor…</p>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{kategori != null && il != null ? (
|
||||
<div>
|
||||
<p className="mb-1.5 text-xs font-semibold text-slate-500">
|
||||
3. Üniversite tipi seç{" "}
|
||||
<span className="font-normal text-slate-400">
|
||||
(sayılar hem alanı hem ili hesaba katıyor)
|
||||
</span>
|
||||
</p>
|
||||
{tipSecenekleri.length > 0 ? (
|
||||
<SecimCipleri
|
||||
secenekler={tipSecenekleri}
|
||||
secili={tip}
|
||||
onSec={(t) => tipSec(t as "devlet" | "vakif" | null)}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-xs text-slate-400">Yükleniyor…</p>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<YapayZekaAsamasi havuz={havuz} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user