Sihirbaz akışı ana sayfaya taşındı: sıralama girilince modal orada açılır
Some checks failed
Deploy / deploy (push) Failing after 1m30s
Some checks failed
Deploy / deploy (push) Failing after 1m30s
- Ortak SihirbazAdimlar bileşeni (3 adımlık seçim UI'ı) çıkarıldı; hem hero modalı hem /sonuc modalı kullanıyor - /api/facetler: sıraya göre kategori/il/üniversite tipi facet'leri - HeroForm: "Listemi oluştur" artık modalı açar; seçimler tamamlanınca localStorage'a (SIHIRBAZ_STORAGE_KEY) yazılıp /sonuc'a yönlendirilir - /sonuc: modal yalnızca giriş dönüşünde (?sihirbaz=1) otomatik açılır; kayıtlı seçimler prefill olarak kullanılır Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
22
src/app/api/facetler/route.ts
Normal file
22
src/app/api/facetler/route.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
import { PUAN_TURLERI, rankWindowFacets, type PuanTuruKey } from "@/lib/db";
|
||||
|
||||
// Ana sayfadaki sihirbaz modalı için: sıralamaya uygun kategori/il/üniversite
|
||||
// tipi facet'leri. /sonuc bu veriyi server component'te doğrudan alır; burada
|
||||
// client'tan sıra girildikten sonra çekilir.
|
||||
export function GET(request: NextRequest) {
|
||||
const { searchParams } = request.nextUrl;
|
||||
const sira = Number.parseInt(searchParams.get("sira") ?? "", 10);
|
||||
const turParam = searchParams.get("tur") ?? "say";
|
||||
const tur: PuanTuruKey =
|
||||
turParam in PUAN_TURLERI ? (turParam as PuanTuruKey) : "say";
|
||||
|
||||
if (!Number.isFinite(sira) || sira < 1 || sira > 4_000_000) {
|
||||
return NextResponse.json(
|
||||
{ error: "Geçerli bir sıralama gerekli." },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ facetler: rankWindowFacets(sira, tur) });
|
||||
}
|
||||
@@ -1,22 +1,11 @@
|
||||
import Link from "next/link";
|
||||
import type { Metadata } from "next";
|
||||
import {
|
||||
ArrowLeft,
|
||||
MapPin,
|
||||
Minus,
|
||||
Rocket,
|
||||
Scale,
|
||||
ShieldCheck,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
PUAN_TURLERI,
|
||||
searchByRank,
|
||||
rankWindowFacets,
|
||||
type Program,
|
||||
type PuanTuruKey,
|
||||
} from "@/lib/db";
|
||||
import { getSession, getCurrentUser } from "@/lib/session";
|
||||
@@ -24,24 +13,12 @@ import { appDb, schema } from "@/lib/appdb";
|
||||
import type { RaporSonuc } from "@/lib/ai/rapor";
|
||||
import type { RaporParams } from "@/lib/rapor-havuzu";
|
||||
import { SihirbazBolumu } from "./sihirbaz-cagri-karti";
|
||||
import type {
|
||||
MevcutRapor,
|
||||
OnizlemeSatir,
|
||||
} from "./sihirbaz-modal";
|
||||
import { BolgeHaritasi, type IlOzeti } from "@/components/bolge-haritasi";
|
||||
import type { MevcutRapor, OnizlemeSatir } from "./sihirbaz-modal";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Sıralamana Uygun Programlar — KolayTercih",
|
||||
title: "Tercih Listeni Kur — KolayTercih",
|
||||
};
|
||||
|
||||
const TUR_LABELS: Record<PuanTuruKey, string> = {
|
||||
@@ -52,106 +29,12 @@ const TUR_LABELS: Record<PuanTuruKey, string> = {
|
||||
tyt: "TYT (Önlisans)",
|
||||
};
|
||||
|
||||
function TrendIcon({ p }: { p: Program }) {
|
||||
if (p.sira2025 == null || p.sira2024 == null) {
|
||||
return <Minus className="size-4 text-slate-400" aria-label="Veri yok" />;
|
||||
}
|
||||
const degisim = (p.sira2024 - p.sira2025) / p.sira2024;
|
||||
// taban sıralaması küçülüyorsa bölüm zorlaşıyor demektir
|
||||
if (degisim > 0.05) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 text-orange-600">
|
||||
<TrendingUp className="size-4" aria-hidden />
|
||||
<span className="text-xs">zorlaşıyor</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (degisim < -0.05) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 text-emerald-600">
|
||||
<TrendingDown className="size-4" aria-hidden />
|
||||
<span className="text-xs">rahatlıyor</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 text-slate-500">
|
||||
<Minus className="size-4" aria-hidden />
|
||||
<span className="text-xs">stabil</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function fmt(n: number | null): string {
|
||||
return n == null ? "—" : n.toLocaleString("tr-TR");
|
||||
}
|
||||
|
||||
function ProgramTable({ programs }: { programs: Program[] }) {
|
||||
if (programs.length === 0) {
|
||||
return (
|
||||
<p className="rounded-xl border border-dashed border-slate-300 p-6 text-center text-sm text-slate-500">
|
||||
Bu dilimde uygun program bulunamadı.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="overflow-x-auto rounded-2xl border border-slate-200 bg-white">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-slate-50">
|
||||
<TableHead>Program</TableHead>
|
||||
<TableHead>Üniversite</TableHead>
|
||||
<TableHead className="text-right">2025 taban sırası</TableHead>
|
||||
<TableHead className="text-right">2024</TableHead>
|
||||
<TableHead>Trend</TableHead>
|
||||
<TableHead className="text-right">Kontenjan</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{programs.map((p) => (
|
||||
<TableRow key={p.id}>
|
||||
<TableCell className="font-medium">
|
||||
{p.isim}
|
||||
{p.fakulte ? (
|
||||
<span className="block text-xs text-slate-500">
|
||||
{p.fakulte}
|
||||
</span>
|
||||
) : null}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{p.universite}
|
||||
<span className="block text-xs text-slate-500">
|
||||
{p.il}
|
||||
{p.unitur ? ` · ${p.unitur.toLocaleLowerCase("tr-TR")}` : ""}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium tabular-nums">
|
||||
{fmt(p.sira2025)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums text-slate-500">
|
||||
{fmt(p.sira2024)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TrendIcon p={p} />
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums">
|
||||
{fmt(p.kontenjan2025)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function SonucPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{
|
||||
sira?: string;
|
||||
tur?: string;
|
||||
il?: string;
|
||||
sihirbaz?: string;
|
||||
}>;
|
||||
}) {
|
||||
@@ -161,7 +44,6 @@ export default async function SonucPage({
|
||||
params.tur && params.tur in PUAN_TURLERI
|
||||
? (params.tur as PuanTuruKey)
|
||||
: "say";
|
||||
const seciliIl = params.il?.trim() || null;
|
||||
|
||||
if (!Number.isFinite(sira) || sira < 1 || sira > 4_000_000) {
|
||||
return (
|
||||
@@ -170,7 +52,7 @@ export default async function SonucPage({
|
||||
Geçerli bir sıralama gerekli
|
||||
</h1>
|
||||
<p className="mt-3 text-slate-600">
|
||||
Sonuçları görebilmek için ana sayfadan YKS başarı sıralamanı gir.
|
||||
Listeni kurabilmek için ana sayfadan YKS başarı sıralamanı gir.
|
||||
</p>
|
||||
<Button asChild className="mt-6 cursor-pointer">
|
||||
<Link href="/">
|
||||
@@ -211,65 +93,12 @@ export default async function SonucPage({
|
||||
il: p.il,
|
||||
}));
|
||||
|
||||
const results = searchByRank(sira, turKey, {
|
||||
il: seciliIl ?? undefined,
|
||||
});
|
||||
// Harita her zaman filtresiz dağılımı gösterir ki il değiştirilebilsin
|
||||
const haritaSonuclari = seciliIl ? searchByRank(sira, turKey) : results;
|
||||
|
||||
// Harita için il bazında özet (81 il haritada; KKTC/yurtdışı ayrıca listelenir)
|
||||
const iller: Record<string, IlOzeti> = {};
|
||||
const dilimler = [
|
||||
["hayal", haritaSonuclari.hayal],
|
||||
["dengeli", haritaSonuclari.dengeli],
|
||||
["garanti", haritaSonuclari.garanti],
|
||||
] as const;
|
||||
for (const [dilim, programs] of dilimler) {
|
||||
for (const p of programs) {
|
||||
if (!p.il) continue;
|
||||
iller[p.il] ??= { hayal: 0, dengeli: 0, garanti: 0 };
|
||||
iller[p.il][dilim]++;
|
||||
}
|
||||
}
|
||||
|
||||
const sections = [
|
||||
{
|
||||
key: "hayal",
|
||||
icon: Rocket,
|
||||
title: "Hayal",
|
||||
tone: "text-orange-600",
|
||||
description:
|
||||
"Geçen yıl tabanı senin sıralamanın üzerinde kapanan programlar. Şansın düşük ama listenin başında birkaç tane bulunmalı.",
|
||||
programs: results.hayal,
|
||||
},
|
||||
{
|
||||
key: "dengeli",
|
||||
icon: Scale,
|
||||
title: "Dengeli",
|
||||
tone: "text-primary",
|
||||
description:
|
||||
"Tabanı sıralamanın hemen civarında. Listenin bel kemiği bu dilimden kurulur.",
|
||||
programs: results.dengeli,
|
||||
},
|
||||
{
|
||||
key: "garanti",
|
||||
icon: ShieldCheck,
|
||||
title: "Garanti",
|
||||
tone: "text-emerald-600",
|
||||
description:
|
||||
"Tabanı sıralamanın belirgin altında. Boşta kalmamak için listenin sonunda mutlaka yer almalı.",
|
||||
programs: results.garanti,
|
||||
},
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 text-slate-900">
|
||||
<main
|
||||
className={`mx-auto max-w-6xl px-4 py-12 ${mevcutRapor ? "" : "pb-56"}`}
|
||||
>
|
||||
<main className="mx-auto max-w-4xl px-4 py-12">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Badge variant="secondary" className="bg-primary/10 text-primary">
|
||||
Ücretsiz ön izleme
|
||||
{sira.toLocaleString("tr-TR")}. sıra · {TUR_LABELS[turKey]}
|
||||
</Badge>
|
||||
<Button
|
||||
asChild
|
||||
@@ -284,12 +113,15 @@ export default async function SonucPage({
|
||||
</Button>
|
||||
</div>
|
||||
<h1 className="mt-4 font-heading text-3xl font-bold sm:text-4xl">
|
||||
{sira.toLocaleString("tr-TR")}. sıradaki bir aday için görünüm
|
||||
{sira.toLocaleString("tr-TR")}. sıradaki bir aday için tercih planı
|
||||
</h1>
|
||||
<p className="mt-2 max-w-2xl text-slate-600">
|
||||
2025 YÖK Atlas taban sıralamalarına göre üç dilime ayrılmış öneriler.
|
||||
Taban sıralamaları her yıl değişir; bu liste garanti değil, başlangıç
|
||||
noktasıdır.
|
||||
Birkaç adımda ilgi alanlarını ve şehir tercihini seç; yapay zekâ
|
||||
gerçek YÖK Atlas verisiyle 24 tercihlik dengeli listeni kursun. Her
|
||||
satır yerleşme riskine göre renklenir:{" "}
|
||||
<span className="font-semibold text-emerald-600">yeşil güvenli</span>,{" "}
|
||||
<span className="font-semibold text-amber-600">sarı az riskli</span>,{" "}
|
||||
<span className="font-semibold text-red-600">kırmızı riskli</span>.
|
||||
</p>
|
||||
|
||||
{/* Puan türü seçimi */}
|
||||
@@ -302,82 +134,15 @@ export default async function SonucPage({
|
||||
variant={key === turKey ? "default" : "outline"}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Link
|
||||
href={`/sonuc?sira=${sira}&tur=${key}${
|
||||
seciliIl ? `&il=${encodeURIComponent(seciliIl)}` : ""
|
||||
}`}
|
||||
>
|
||||
<Link href={`/sonuc?sira=${sira}&tur=${key}`}>
|
||||
{TUR_LABELS[key]}
|
||||
</Link>
|
||||
</Button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{seciliIl ? (
|
||||
<div className="mt-4 flex flex-wrap items-center gap-3 rounded-xl border border-primary/30 bg-primary/5 px-4 py-3">
|
||||
<MapPin className="size-4 text-primary" aria-hidden />
|
||||
<p className="text-sm font-medium">
|
||||
Yalnızca <span className="font-bold">{seciliIl}</span>{" "}
|
||||
programları gösteriliyor
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Link href={`/sonuc?sira=${sira}&tur=${turKey}`}>
|
||||
<X className="size-3.5" aria-hidden />
|
||||
Filtreyi kaldır
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Bölge haritası */}
|
||||
<section aria-labelledby="baslik-harita" className="mt-10">
|
||||
<h2 id="baslik-harita" className="font-heading text-2xl font-bold">
|
||||
Nerede okuyacaksın?
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-slate-600">
|
||||
Aşağıdaki önerilerin illere dağılımı. Koyu renk, o ilde daha fazla
|
||||
öneri olduğunu gösterir. Bir ile tıklayarak listeyi o ile
|
||||
daraltabilir, tekrar tıklayarak filtreyi kaldırabilirsin.
|
||||
</p>
|
||||
<div className="mt-4 rounded-2xl border border-slate-200 bg-white p-4 sm:p-6">
|
||||
<BolgeHaritasi
|
||||
iller={iller}
|
||||
sira={sira}
|
||||
turKey={turKey}
|
||||
seciliIl={seciliIl}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="mt-10 space-y-12">
|
||||
{sections.map((s) => (
|
||||
<section key={s.key} aria-labelledby={`baslik-${s.key}`}>
|
||||
<div className="mb-4 flex items-start gap-3">
|
||||
<s.icon className={`mt-1 size-6 ${s.tone}`} aria-hidden />
|
||||
<div>
|
||||
<h2
|
||||
id={`baslik-${s.key}`}
|
||||
className="font-heading text-2xl font-bold"
|
||||
>
|
||||
{s.title}
|
||||
<span className="ml-2 text-base font-normal text-slate-500">
|
||||
{s.programs.length} program
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-sm text-slate-600">{s.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ProgramTable programs={s.programs} />
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Ücretli katman — sihirbaz akışı (CTA + alt kart + modal) */}
|
||||
{/* Sihirbaz akışı (CTA + modal) — seçimler ana sayfadaki modalda yapılır,
|
||||
burada yalnızca giriş dönüşünde (?sihirbaz=1) otomatik açılır */}
|
||||
<SihirbazBolumu
|
||||
sira={sira}
|
||||
tur={turKey}
|
||||
|
||||
@@ -5,12 +5,10 @@ import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
FileText,
|
||||
Loader2,
|
||||
Lock,
|
||||
RefreshCcw,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -22,16 +20,13 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { RaporListesi } from "@/components/rapor-listesi";
|
||||
import { SoruKutusu } from "./soru-kutusu";
|
||||
import { SihirbazAdimlar } from "@/components/sihirbaz-adimlar";
|
||||
import type { SihirbazFacetleri } from "@/lib/db";
|
||||
import type { RaporSonuc } from "@/lib/ai/rapor";
|
||||
import type { RaporParams } from "@/lib/rapor-havuzu";
|
||||
import {
|
||||
ONCELIKLER,
|
||||
SIHIRBAZ_STORAGE_KEY,
|
||||
UNIVERSITE_TIPI_ETIKET,
|
||||
type SihirbazSecimleri,
|
||||
type UniversiteTipi,
|
||||
} from "@/lib/sihirbaz";
|
||||
import { listeOlustur, listeRevize } from "./actions";
|
||||
|
||||
@@ -52,31 +47,6 @@ export type MevcutRapor = {
|
||||
|
||||
type Faz = "adimlar" | "onizleme" | "uretiliyor" | "liste";
|
||||
|
||||
function Cip({
|
||||
secili,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
secili: boolean;
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-pressed={secili}
|
||||
className={
|
||||
secili
|
||||
? "cursor-pointer rounded-full border border-primary bg-primary px-3.5 py-2 text-sm font-medium text-white transition-colors duration-200"
|
||||
: "cursor-pointer rounded-full border border-slate-200 bg-white px-3.5 py-2 text-sm text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50"
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const URETIM_MESAJLARI = [
|
||||
"Sıralamana uygun programlar taranıyor…",
|
||||
"Gerçek YÖK Atlas verisiyle dengeli liste kuruluyor…",
|
||||
@@ -115,48 +85,19 @@ export function SihirbazModal({
|
||||
const router = useRouter();
|
||||
|
||||
const [faz, setFaz] = useState<Faz>(mevcutRapor ? "liste" : "adimlar");
|
||||
const [adim, setAdim] = useState(0);
|
||||
const [kredi, setKredi] = useState(baslangicKredi);
|
||||
const [rapor, setRapor] = useState<MevcutRapor | null>(mevcutRapor);
|
||||
const [uretimMesajIdx, setUretimMesajIdx] = useState(0);
|
||||
|
||||
// Seçimler
|
||||
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 ?? [],
|
||||
// Adımlarda yapılan son seçimler (önizlemeden geri dönüşte prefill için)
|
||||
const [sonSecimler, setSonSecimler] = useState<SihirbazSecimleri | null>(
|
||||
baslangicSecimler ?? null,
|
||||
);
|
||||
|
||||
// Aynı üretim için sabit requestId (retry çifte harcama yapmaz)
|
||||
const requestIdRef = useRef<string | null>(null);
|
||||
const otomatikBasladi = useRef(false);
|
||||
|
||||
function secimleriTopla(): SihirbazSecimleri {
|
||||
return { kategoriler, iller, universiteTipi, oncelikler };
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
async function uret(secimler: SihirbazSecimleri) {
|
||||
if (!requestIdRef.current) requestIdRef.current = crypto.randomUUID();
|
||||
setFaz("uretiliyor");
|
||||
@@ -241,8 +182,8 @@ export function SihirbazModal({
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function ilerle() {
|
||||
const secimler = secimleriTopla();
|
||||
function ilerle(secimler: SihirbazSecimleri) {
|
||||
setSonSecimler(secimler);
|
||||
if (!girisliMi) {
|
||||
secimleriKaydetVeGirise(secimler);
|
||||
return;
|
||||
@@ -255,162 +196,17 @@ export function SihirbazModal({
|
||||
void uret(secimler);
|
||||
}
|
||||
|
||||
const adimGecerli = adim === 0 ? kategoriler.length > 0 : true;
|
||||
|
||||
// ---- Faz gövdeleri ----
|
||||
|
||||
const adimlarGovde = (
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={
|
||||
i === adim
|
||||
? "h-1.5 w-6 rounded-full bg-primary transition-all"
|
||||
: "h-1.5 w-1.5 rounded-full bg-slate-200 transition-all"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<span className="ml-2 text-xs font-medium text-slate-400">
|
||||
Adım {adim + 1}/3
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{adim === 0 ? (
|
||||
<div>
|
||||
<h3 className="font-heading text-lg font-bold">
|
||||
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 opacity-70">{k.adet}</span>
|
||||
</Cip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : adim === 1 ? (
|
||||
<div>
|
||||
<h3 className="font-heading text-lg font-bold">
|
||||
Nerede okumak istersin?
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
İstersen boş bırak (fark etmez). En fazla 5 il.
|
||||
</p>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{facetler.iller.map((i) => (
|
||||
<Cip
|
||||
key={i.il}
|
||||
secili={iller.includes(i.il)}
|
||||
onClick={() => listeDegistir(iller, setIller, i.il, 5)}
|
||||
>
|
||||
{i.il}
|
||||
<span className="ml-1.5 text-xs opacity-70">{i.adet}</span>
|
||||
</Cip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
<h3 className="font-heading text-lg font-bold">
|
||||
Ü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-xs font-semibold tracking-wide text-slate-400 uppercase">
|
||||
Üniversite tipi
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{facetler.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 opacity-70">{u.adet}</span>
|
||||
</Cip>
|
||||
))}
|
||||
<Cip
|
||||
secili={universiteTipi === "farketmez"}
|
||||
onClick={() => setUniversiteTipi("farketmez")}
|
||||
>
|
||||
Fark etmez
|
||||
</Cip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-2 text-xs font-semibold tracking-wide text-slate-400 uppercase">
|
||||
Senin için önemli olan
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{ONCELIKLER.map((o) => (
|
||||
<Cip
|
||||
key={o}
|
||||
secili={oncelikler.includes(o)}
|
||||
onClick={() => listeDegistir(oncelikler, setOncelikler, o)}
|
||||
>
|
||||
{o}
|
||||
</Cip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between border-t border-slate-100 pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => setAdim((a) => a - 1)}
|
||||
disabled={adim === 0}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<ArrowLeft className="size-4" aria-hidden />
|
||||
Geri
|
||||
</Button>
|
||||
{adim < 2 ? (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setAdim((a) => a + 1)}
|
||||
disabled={!adimGecerli}
|
||||
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
>
|
||||
Devam
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={ilerle}
|
||||
disabled={kategoriler.length === 0}
|
||||
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
>
|
||||
<Sparkles className="size-4" aria-hidden />
|
||||
{hasPaket ? "24'lük listemi oluştur (3 kredi)" : "Listemi gör"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<SihirbazAdimlar
|
||||
facetler={facetler}
|
||||
baslangicSecimler={sonSecimler}
|
||||
sonButonEtiketi={
|
||||
hasPaket ? "24'lük listemi oluştur (3 kredi)" : "Listemi gör"
|
||||
}
|
||||
onTamamla={ilerle}
|
||||
/>
|
||||
);
|
||||
|
||||
const onizlemeGovde = (
|
||||
@@ -517,7 +313,7 @@ export function SihirbazModal({
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<RaporListesi rapor={rapor.rapor} />
|
||||
<RaporListesi rapor={rapor.rapor} adaySira={rapor.params.sira} />
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
@@ -544,8 +340,22 @@ export function SihirbazModal({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<SoruKutusu kredi={kredi} onKrediDegisti={setKredi} />
|
||||
<div className="mt-6 flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-white px-5 py-4">
|
||||
<div>
|
||||
<p className="font-heading text-sm font-bold">
|
||||
Listen hakkında soruların mı var?
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
AI danışman listeni ve seçimlerini bilir; her mesaj 1 kredi.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
size="sm"
|
||||
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
>
|
||||
<Link href="/listem">Danışmana sor</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
Reference in New Issue
Block a user