- Adım göstergesi: Alan · Şehir · Öncelik; dolum transform ile soldan, güçlü ease-out 300 ms; adım değişimi ekran okuyucuya duyurulur - Çekmecede "Listen tamam" satırı @starting-style ile küçük giriş (scale 0.95 + opaklık); hareketi azalt'ta yalnız opaklık - Dilim sekmesindeki kayan hapta sekme kaldırıldı (bounce 0, 0.35 sn) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
587 lines
23 KiB
TypeScript
587 lines
23 KiB
TypeScript
"use client";
|
||
|
||
// Sihirbazın 3 adımlık seçim gövdesi. Hem ana sayfadaki hero modalında hem de
|
||
// /sonuc'taki sihirbaz modalında kullanılır; seçim state'ini kendi tutar,
|
||
// tamamlanınca onTamamla(secimler) çağırır.
|
||
//
|
||
// Adımlar kademeli süzülür: 2. adımın illeri seçili kategorilere, 3. adımın
|
||
// üniversite tipi sayıları kategori+il'e göre /api/facetler'den tazelenir.
|
||
|
||
import { useEffect, useMemo, useState } from "react";
|
||
import { ArrowLeft, ArrowRight, Search, X } from "lucide-react";
|
||
import { useReducedMotion } from "motion/react";
|
||
import useMeasure from "react-use-measure";
|
||
import { toast } from "sonner";
|
||
import { cities } from "turkey-map-react/lib/data";
|
||
import { TransitionPanel } from "@/components/core/transition-panel";
|
||
import { Button } from "@/components/ui/button";
|
||
import { IlSecimHaritasi } from "./il-secim-haritasi";
|
||
import { normalizeIlAdi } from "@/lib/harita";
|
||
import { onbellekliJsonGetir } from "@/lib/istek-cache";
|
||
import { olay } from "@/lib/analitik";
|
||
import { trBaslikDuzeni } from "@/lib/slug";
|
||
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
||
import { ONCELIKLER, UNIVERSITE_TIPI_ETIKET, type SihirbazSecimleri, type UniversiteTipi } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||
|
||
export function Cip({
|
||
secili,
|
||
onClick,
|
||
children,
|
||
kucuk = false,
|
||
className = "",
|
||
}: {
|
||
secili: boolean;
|
||
onClick: () => void;
|
||
children: React.ReactNode;
|
||
kucuk?: boolean;
|
||
className?: string;
|
||
}) {
|
||
// Şişko ve sade (DESIGN.md §1.1): çip 44 px dokunma hedefi; il
|
||
// çipleri (kucuk) çok sayıda olduğu için 40 px. Hover §2.1 tek turuncu.
|
||
const boyut = kucuk ? "h-10 px-3.5 text-sm" : "h-11 px-4 text-sm";
|
||
const temel = `inline-flex cursor-pointer items-center rounded-full border font-semibold transition-[background-color,border-color,color,transform] duration-150 active:scale-[0.97] ${boyut} ${className}`;
|
||
return (
|
||
<button
|
||
type="button"
|
||
onClick={onClick}
|
||
aria-pressed={secili}
|
||
className={
|
||
secili
|
||
? `${temel} border-primary bg-primary text-white`
|
||
: `${temel} border-slate-200 bg-white text-slate-700 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500`
|
||
}
|
||
>
|
||
{children}
|
||
</button>
|
||
);
|
||
}
|
||
|
||
const ADIM_ADLARI = ["Alan", "Şehir", "Öncelik"] as const;
|
||
|
||
// Haritada karşılığı olan iller (KKTC/yurtdışı kampüsler çip olarak kalır)
|
||
const HARITA_ILLERI = new Set(cities.map((c) => normalizeIlAdi(c.name)));
|
||
|
||
export function SihirbazAdimlar({
|
||
sira,
|
||
tur,
|
||
facetler,
|
||
baslangicSecimler,
|
||
sonButonEtiketi,
|
||
onTamamla,
|
||
}: {
|
||
sira: number;
|
||
tur: string;
|
||
facetler: SihirbazFacetleri;
|
||
baslangicSecimler?: SihirbazSecimleri | null;
|
||
sonButonEtiketi: string;
|
||
onTamamla: (secimler: SihirbazSecimleri) => void;
|
||
}) {
|
||
const [adim, setAdim] = useState(0);
|
||
const [yon, setYon] = useState(1);
|
||
const [gecisBasladi, setGecisBasladi] = useState(false);
|
||
const [panelRef, panelOlculeri] = useMeasure();
|
||
const hareketiAzalt = useReducedMotion();
|
||
const [kategoriler, setKategoriler] = useState<string[]>(
|
||
baslangicSecimler?.kategoriler ?? [],
|
||
);
|
||
const [iller, setIller] = useState<string[]>(baslangicSecimler?.iller ?? []);
|
||
const [universiteTipi, setUniversiteTipi] = useState<UniversiteTipi>(
|
||
baslangicSecimler?.universiteTipi ?? "farketmez",
|
||
);
|
||
const [oncelikler, setOncelikler] = useState<string[]>(
|
||
baslangicSecimler?.oncelikler ?? [],
|
||
);
|
||
|
||
// Kademeli facet'ler: kategoriler daima ilk yükten gelir; il ve üniversite
|
||
// tipi sayıları adıma girerken o anki seçimlerle tazelenir. Fetch başarısız
|
||
// olursa eldeki (daha geniş) liste kalır — sihirbaz asla kilitlenmez.
|
||
const [canliFacetler, setCanliFacetler] = useState(facetler);
|
||
// 81 elemanlı listeyi her çip tıklamasında yeniden sıralamamak/filtrelememek
|
||
// için memo; haritaya giden dizinin referansı da sabit kalır (gereksiz
|
||
// IlSecimHaritasi render'ı biter).
|
||
const siraliIller = useMemo(
|
||
() => [...canliFacetler.iller].sort((a, b) => b.adet - a.adet),
|
||
[canliFacetler.iller],
|
||
);
|
||
// Türkiye illeri ana çip grubu; KKTC/yurtdışı kampüsler haritada olmadığı
|
||
// için ayrı alt grupta listelenir.
|
||
const turkiyeIlleri = useMemo(
|
||
() => siraliIller.filter((i) => HARITA_ILLERI.has(i.il)),
|
||
[siraliIller],
|
||
);
|
||
const disIller = useMemo(
|
||
() => siraliIller.filter((i) => !HARITA_ILLERI.has(i.il)),
|
||
[siraliIller],
|
||
);
|
||
// İl araması: Türkçe büyük/küçük harf ve şapkalı harf farklarına duyarsız
|
||
// eşleşme için iki taraf da normalizeIlAdi'dan geçirilir.
|
||
const [ilArama, setIlArama] = useState("");
|
||
const ilAramaNorm = normalizeIlAdi(ilArama.trim());
|
||
// 81 çip ilk bakışta boğucu; varsayılan görünüm ilk 10 il + "+N il daha"
|
||
// rozeti. Arama açıkken kırpma yok (arama zaten görünümü daraltıyor).
|
||
const [tumIllerAcik, setTumIllerAcik] = useState(false);
|
||
const gorunenTurkiyeIlleri = useMemo(
|
||
() =>
|
||
ilAramaNorm
|
||
? turkiyeIlleri.filter((i) =>
|
||
normalizeIlAdi(i.il).includes(ilAramaNorm),
|
||
)
|
||
: turkiyeIlleri,
|
||
[turkiyeIlleri, ilAramaNorm],
|
||
);
|
||
const IL_ON_IZLEME = 10;
|
||
const ilListesiKirpik =
|
||
!ilAramaNorm &&
|
||
!tumIllerAcik &&
|
||
gorunenTurkiyeIlleri.length > IL_ON_IZLEME;
|
||
const listelenenTurkiyeIlleri = ilListesiKirpik
|
||
? gorunenTurkiyeIlleri.slice(0, IL_ON_IZLEME)
|
||
: gorunenTurkiyeIlleri;
|
||
const gorunenDisIller = useMemo(
|
||
() =>
|
||
ilAramaNorm
|
||
? disIller.filter((i) => normalizeIlAdi(i.il).includes(ilAramaNorm))
|
||
: disIller,
|
||
[disIller, ilAramaNorm],
|
||
);
|
||
const haritaIlleri = useMemo(
|
||
() => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)),
|
||
[canliFacetler.iller],
|
||
);
|
||
// Huninin paydası: 1. adım hiçbir zaman "geçilmediği" için `adimaGit`ten
|
||
// atılamıyordu (orası yalnız ileri geçişleri sayar) ve `sihirbaz_adim{1}`
|
||
// hiç düşmüyordu — payda `sira_girildi`den türetilmek zorunda kalıyordu.
|
||
// Modal kapanınca bileşen unmount olduğu için mount = "1. adım görüldü";
|
||
// 1→2→1→2 gidip gelmek unmount etmediğinden tekrar tetiklenmez.
|
||
//
|
||
// Bilerek `olayBirKez` KULLANILMAZ (bkz. lib/analitik.ts:22-24, aynı kural
|
||
// `liste_uretim_basladi` için yazılı): `:220`'deki ileri geçiş olayı
|
||
// (`adim:2`, `adim:3`) her geçişte atılıyor. Paydayı oturum başına tekil
|
||
// yapsaydık sihirbazı ikinci kez açan öğrencide pay 2, payda 1 kalır ve
|
||
// 1→2 geçiş oranı %100'ü aşardı. Payda ile pay aynı birimde olmalı:
|
||
// sihirbaz her açıldığında `adim:1` de düşer.
|
||
//
|
||
// SERİ KIRILMASI: bu satırın canlıya çıktığı tarihten önce `sihirbaz_adim`
|
||
// serisinde `adim:1` hiç yok; öncesi/sonrası karşılaştırılamaz.
|
||
useEffect(() => {
|
||
olay("sihirbaz_adim", { adim: 1 });
|
||
}, []);
|
||
|
||
const [facetYukleniyor, setFacetYukleniyor] = useState(false);
|
||
useEffect(() => {
|
||
if (adim === 0) return;
|
||
const ctrl = new AbortController();
|
||
const params = new URLSearchParams({ sira: String(sira), tur });
|
||
for (const k of kategoriler) params.append("kategori", k);
|
||
if (adim === 2) for (const i of iller) params.append("il", i);
|
||
// eslint-disable-next-line react-hooks/set-state-in-effect -- fetch başlangıç işareti
|
||
setFacetYukleniyor(true);
|
||
// Cache'li: 2↔3 adımları arasında gidip gelmek aynı URL'i yeniden çekmez
|
||
onbellekliJsonGetir<{ facetler: SihirbazFacetleri }>(
|
||
`/api/facetler?${params}`,
|
||
{ signal: ctrl.signal },
|
||
)
|
||
.then((veri) => {
|
||
setCanliFacetler((mevcut) => ({
|
||
...mevcut,
|
||
iller: veri.facetler.iller,
|
||
uniturler: veri.facetler.uniturler,
|
||
}));
|
||
// Seçili tip daralan filtrede yoksa (ör. seçilen ilde vakıf kalmadı)
|
||
// görünmez bir seçim bırakma
|
||
setUniversiteTipi((tip) =>
|
||
tip !== "farketmez" &&
|
||
!veri.facetler.uniturler.some((u) => u.grup === tip)
|
||
? "farketmez"
|
||
: tip,
|
||
);
|
||
})
|
||
.catch(() => {})
|
||
.finally(() => setFacetYukleniyor(false));
|
||
return () => ctrl.abort();
|
||
// Seçimler yalnızca önceki adımlarda değişebildiği için adım geçişinde
|
||
// okumak yeterli; her çip tıklamasında istek atmayalım.
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [adim]);
|
||
|
||
function listeDegistir(
|
||
liste: string[],
|
||
setListe: (v: string[]) => void,
|
||
deger: string,
|
||
maks?: number,
|
||
) {
|
||
if (liste.includes(deger)) {
|
||
setListe(liste.filter((x) => x !== deger));
|
||
} else {
|
||
if (maks && liste.length >= maks) {
|
||
toast.info(`En fazla ${maks} seçebilirsin.`);
|
||
return;
|
||
}
|
||
setListe([...liste, deger]);
|
||
}
|
||
}
|
||
|
||
const adimGecerli = adim === 0 ? kategoriler.length > 0 : true;
|
||
|
||
function adimaGit(yeniAdim: number) {
|
||
setYon(yeniAdim > adim ? 1 : -1);
|
||
setGecisBasladi(true);
|
||
setAdim(yeniAdim);
|
||
// Yalnızca ileri geçişler huni adımı sayılır; geri dönüşler tekrar saymasın.
|
||
// `adim:1` buradan hiç geçmez — onu yukarıdaki mount effect'i atar.
|
||
if (yeniAdim > adim) olay("sihirbaz_adim", { adim: yeniAdim + 1 });
|
||
}
|
||
|
||
const panelYuksekligi =
|
||
gecisBasladi && panelOlculeri.height > 0
|
||
? panelOlculeri.height
|
||
: "auto";
|
||
|
||
return (
|
||
<div className="flex flex-col gap-5">
|
||
{/* Adım göstergesi (DESIGN.md §10): üç parçalı çubuk + adların
|
||
kendisi; öğrenci nerede olduğunu ve ne kaldığını görür. Değişim
|
||
ekran okuyucuya duyurulur (WCAG 4.1.3). */}
|
||
<div>
|
||
<ol className="grid grid-cols-3 gap-2" aria-label="Sihirbaz adımları">
|
||
{ADIM_ADLARI.map((ad, i) => (
|
||
<li key={ad} className="flex flex-col gap-1.5">
|
||
{/* Dolum transform ile (yalnız transform/opaklık canlandırılır);
|
||
soldan dolar, güçlü ease-out 300 ms. */}
|
||
<span className="h-1.5 overflow-hidden rounded-full bg-slate-200" aria-hidden>
|
||
<span
|
||
className={`block h-full origin-left rounded-full bg-primary transition-transform duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none ${
|
||
i <= adim ? "scale-x-100" : "scale-x-0"
|
||
}`}
|
||
/>
|
||
</span>
|
||
<span
|
||
className={`text-sm ${
|
||
i === adim
|
||
? "font-semibold text-slate-900"
|
||
: i < adim
|
||
? "font-medium text-slate-600"
|
||
: "text-slate-400"
|
||
}`}
|
||
aria-current={i === adim ? "step" : undefined}
|
||
>
|
||
{ad}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ol>
|
||
<p role="status" className="sr-only">
|
||
Adım {adim + 1} / 3: {ADIM_ADLARI[adim]}
|
||
</p>
|
||
</div>
|
||
|
||
<TransitionPanel
|
||
activeIndex={adim}
|
||
custom={yon}
|
||
variants={{
|
||
enter: (gecisYonu: number) => ({
|
||
x: hareketiAzalt ? 0 : gecisYonu > 0 ? "100%" : "-100%",
|
||
opacity: 0,
|
||
height: panelYuksekligi,
|
||
position: "relative",
|
||
}),
|
||
center: {
|
||
zIndex: 1,
|
||
x: 0,
|
||
opacity: 1,
|
||
height: panelYuksekligi,
|
||
},
|
||
exit: (gecisYonu: number) => ({
|
||
zIndex: 0,
|
||
x: hareketiAzalt ? 0 : gecisYonu < 0 ? "100%" : "-100%",
|
||
opacity: 0,
|
||
position: "absolute",
|
||
top: 0,
|
||
left: 0,
|
||
width: "100%",
|
||
}),
|
||
}}
|
||
transition={
|
||
hareketiAzalt
|
||
? // Azaltılmış harekette yalnızca kısa çapraz solma; height/x
|
||
// varsayılan spring'e düşüp oynamasın diye açıkça sıfırlanır.
|
||
{
|
||
opacity: { duration: 0.12 },
|
||
x: { duration: 0 },
|
||
height: { duration: 0 },
|
||
}
|
||
: {
|
||
x: { type: "spring", stiffness: 300, damping: 30 },
|
||
height: { type: "spring", stiffness: 300, damping: 30 },
|
||
opacity: { duration: 0.2 },
|
||
}
|
||
}
|
||
>
|
||
<div ref={panelRef} className="px-px pb-2">
|
||
<h3 className="font-heading text-xl font-bold text-slate-900">
|
||
Hangi alanlara ilgi duyuyorsun?
|
||
</h3>
|
||
<p className="mt-1 text-sm text-slate-500">
|
||
Sıralamanla ulaşabileceğin alanlar. En az bir tane seç.
|
||
</p>
|
||
<div className="mt-4 flex flex-wrap gap-2">
|
||
{facetler.kategoriler.map((k) => (
|
||
<Cip
|
||
key={k.ad}
|
||
secili={kategoriler.includes(k.ad)}
|
||
onClick={() => listeDegistir(kategoriler, setKategoriler, k.ad)}
|
||
>
|
||
{k.ad}
|
||
<span className="ml-1.5 text-xs font-medium opacity-70">{k.adet}</span>
|
||
</Cip>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div ref={panelRef}>
|
||
<h3 className="font-heading text-xl font-bold text-slate-900">
|
||
Nerede okumak istersin?
|
||
</h3>
|
||
<p className="mt-1 text-sm text-slate-500">
|
||
En fazla 5 il seçebilirsin. İstersen boş bırak, fark etmez.
|
||
</p>
|
||
|
||
<div className="mt-4 flex h-11 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 transition-colors focus-within:border-slate-300">
|
||
<Search className="size-4 shrink-0 text-slate-400" aria-hidden />
|
||
<input
|
||
type="text"
|
||
value={ilArama}
|
||
onChange={(e) => setIlArama(e.target.value)}
|
||
placeholder="İl ara..."
|
||
aria-label="İl ara"
|
||
autoComplete="off"
|
||
className="min-w-0 flex-1 bg-transparent text-base text-slate-950 outline-none placeholder:text-slate-400"
|
||
/>
|
||
{ilArama ? (
|
||
<button
|
||
type="button"
|
||
onClick={() => setIlArama("")}
|
||
aria-label="Aramayı temizle"
|
||
className="-mr-2 flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700"
|
||
>
|
||
<X className="size-3.5" aria-hidden />
|
||
</button>
|
||
) : null}
|
||
</div>
|
||
|
||
{/* Çipler birincil seçim yolu (haritadaki küçük iller mobilde
|
||
güvenilir dokunma hedefi değil); iller seçili kategorilere göre
|
||
süzülmüş, program sayısına göre sıralı listelenir. Varsayılan
|
||
görünüm ilk 10 il; kalanı "+N il daha" rozetiyle açılır. Arama
|
||
yalnızca görünümü daraltır, seçimleri etkilemez ve kırpmayı
|
||
devre dışı bırakır. */}
|
||
<div
|
||
className={`mt-3 flex max-h-64 flex-wrap gap-2 overflow-y-auto sm:max-h-none sm:overflow-visible ${
|
||
facetYukleniyor ? "animate-pulse opacity-60" : ""
|
||
}`}
|
||
>
|
||
{listelenenTurkiyeIlleri.map((i) => (
|
||
<Cip
|
||
key={i.il}
|
||
kucuk
|
||
secili={iller.includes(i.il)}
|
||
onClick={() => listeDegistir(iller, setIller, i.il, 5)}
|
||
>
|
||
{trBaslikDuzeni(i.il)}
|
||
<span className="ml-1.5 text-xs font-medium opacity-70">{i.adet}</span>
|
||
</Cip>
|
||
))}
|
||
{ilListesiKirpik ? (
|
||
<button
|
||
type="button"
|
||
onClick={() => setTumIllerAcik(true)}
|
||
className="inline-flex h-10 cursor-pointer items-center rounded-full border border-dashed border-slate-300 bg-white px-3.5 text-sm font-semibold 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.97]"
|
||
>
|
||
+{gorunenTurkiyeIlleri.length - IL_ON_IZLEME} il daha
|
||
</button>
|
||
) : null}
|
||
{!ilListesiKirpik &&
|
||
tumIllerAcik &&
|
||
!ilAramaNorm &&
|
||
gorunenTurkiyeIlleri.length > IL_ON_IZLEME ? (
|
||
<button
|
||
type="button"
|
||
onClick={() => setTumIllerAcik(false)}
|
||
className="inline-flex h-10 cursor-pointer items-center rounded-full border border-dashed border-slate-300 bg-white px-3.5 text-sm font-semibold 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.97]"
|
||
>
|
||
Daha az göster
|
||
</button>
|
||
) : null}
|
||
</div>
|
||
|
||
{ilAramaNorm &&
|
||
gorunenTurkiyeIlleri.length === 0 &&
|
||
gorunenDisIller.length === 0 ? (
|
||
<p className="mt-3 text-sm text-slate-500">
|
||
“{ilArama.trim()}” ile eşleşen il yok.
|
||
</p>
|
||
) : null}
|
||
|
||
{gorunenDisIller.length > 0 ? (
|
||
<div className="mt-4">
|
||
<p className="mb-2 text-sm font-semibold text-slate-700">
|
||
KKTC & Yurt Dışı
|
||
</p>
|
||
<div
|
||
className={`flex flex-wrap gap-2 ${
|
||
facetYukleniyor ? "animate-pulse opacity-60" : ""
|
||
}`}
|
||
>
|
||
{gorunenDisIller.map((i) => (
|
||
<Cip
|
||
key={i.il}
|
||
kucuk
|
||
secili={iller.includes(i.il)}
|
||
onClick={() => listeDegistir(iller, setIller, i.il, 5)}
|
||
>
|
||
{trBaslikDuzeni(i.il)}
|
||
<span className="ml-1.5 text-xs font-medium opacity-70">{i.adet}</span>
|
||
</Cip>
|
||
))}
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
|
||
{iller.length > 0 ? (
|
||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||
<span className="text-sm font-semibold text-slate-700">
|
||
Seçtiklerin:
|
||
</span>
|
||
{iller.map((il) => (
|
||
<button
|
||
key={il}
|
||
type="button"
|
||
onClick={() => listeDegistir(iller, setIller, il)}
|
||
className="inline-flex h-10 cursor-pointer items-center gap-1.5 rounded-full bg-primary px-3.5 text-sm font-semibold text-white transition-[background-color,transform] duration-150 hover:bg-primary/90 active:scale-[0.97]"
|
||
>
|
||
{trBaslikDuzeni(il)}
|
||
<X className="size-4" aria-hidden />
|
||
</button>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
|
||
{/* Harita geniş ekranda görsel destek olarak kalır */}
|
||
<div className="mt-4 hidden sm:block">
|
||
<IlSecimHaritasi
|
||
iller={haritaIlleri}
|
||
secili={iller}
|
||
onToggle={(il) => listeDegistir(iller, setIller, il, 5)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div ref={panelRef} className="space-y-4">
|
||
<div>
|
||
<h3 className="font-heading text-xl font-bold text-slate-900">
|
||
Üniversite tipi & öncelikler
|
||
</h3>
|
||
<p className="mt-1 text-sm text-slate-500">
|
||
Bunlar listenin dengesini belirler.
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<p className="mb-2 text-sm font-semibold text-slate-700">
|
||
Üniversite tipi
|
||
</p>
|
||
<div
|
||
className={`flex flex-wrap gap-2 ${
|
||
facetYukleniyor ? "animate-pulse opacity-60" : ""
|
||
}`}
|
||
>
|
||
{canliFacetler.uniturler.map((u) => (
|
||
<Cip
|
||
key={u.grup}
|
||
secili={universiteTipi === u.grup}
|
||
onClick={() =>
|
||
setUniversiteTipi(
|
||
universiteTipi === u.grup ? "farketmez" : u.grup,
|
||
)
|
||
}
|
||
>
|
||
{UNIVERSITE_TIPI_ETIKET[u.grup]}
|
||
<span className="ml-1.5 text-xs font-medium opacity-70">{u.adet}</span>
|
||
</Cip>
|
||
))}
|
||
<Cip
|
||
secili={universiteTipi === "farketmez"}
|
||
onClick={() => setUniversiteTipi("farketmez")}
|
||
>
|
||
Fark etmez
|
||
</Cip>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p className="mb-2 text-sm font-semibold text-slate-700">
|
||
Senin için önemli olan
|
||
</p>
|
||
{/* Telefonda iki sütun, uzun ad iki satıra sarar: tek sütunda
|
||
yedi çip 44 px'le pencereyi 812 px'in dışına itiyordu. */}
|
||
<div className="grid grid-cols-2 gap-2 sm:flex sm:flex-wrap">
|
||
{ONCELIKLER.map((o) => (
|
||
<Cip
|
||
key={o}
|
||
className="h-auto min-h-11 justify-center px-3 py-1.5 text-center leading-tight sm:h-11 sm:px-4 sm:py-0"
|
||
secili={oncelikler.includes(o)}
|
||
onClick={() => listeDegistir(oncelikler, setOncelikler, o)}
|
||
>
|
||
{o}
|
||
</Cip>
|
||
))}
|
||
</div>
|
||
<p className="mt-2 text-sm text-slate-500">
|
||
Öncelikler yapay zekânın gerekçelerine ve program seçimine yön
|
||
verir; listeyi daraltmaz.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</TransitionPanel>
|
||
|
||
<div className="flex items-center justify-between gap-2 border-t border-slate-100 pt-4">
|
||
<Button
|
||
type="button"
|
||
variant="ghost"
|
||
onClick={() => adimaGit(adim - 1)}
|
||
disabled={adim === 0}
|
||
className={adim === 0 ? "invisible" : "h-11 cursor-pointer px-3"}
|
||
>
|
||
<ArrowLeft className="size-4" aria-hidden />
|
||
Geri
|
||
</Button>
|
||
{adim < 2 ? (
|
||
<Button
|
||
type="button"
|
||
onClick={() => adimaGit(adim + 1)}
|
||
disabled={!adimGecerli}
|
||
className="h-11 cursor-pointer bg-orange-500 px-5 text-white transition-[background-color,transform] duration-200 hover:bg-orange-600 active:scale-[0.97]"
|
||
>
|
||
Devam
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
type="button"
|
||
onClick={() => {
|
||
olay("sihirbaz_tamamlandi", {
|
||
kategori_sayisi: kategoriler.length,
|
||
il_sayisi: iller.length,
|
||
uni_tipi: universiteTipi,
|
||
oncelik_sayisi: oncelikler.length,
|
||
});
|
||
onTamamla({ kategoriler, iller, universiteTipi, oncelikler });
|
||
}}
|
||
disabled={kategoriler.length === 0}
|
||
className="h-11 cursor-pointer bg-orange-500 px-5 text-white transition-[background-color,transform] duration-200 hover:bg-orange-600 active:scale-[0.97]"
|
||
>
|
||
{sonButonEtiketi}
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|