"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Link from "next/link"; import { RehberKapak } from "@/components/rehber-kapak"; import { UniLogo } from "@/components/uni-logo"; import { aramaNormalize, eslesmePuani } from "@/lib/arama"; import type { RehberOzet } from "@/lib/rehber"; import { PUAN_TURU_ETIKET } from "@/lib/sihirbaz"; import { profilDuzenlemeyiAc, useTercihProfili, } from "@/components/manuel-liste/profil-kapisi-store"; import { ArrowRight, BookOpenText, Building2, GraduationCap, Search, SquarePen, Trophy, X, } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogTitle, } from "@/components/ui/dialog"; import { Kbd, KbdGroup } from "@/components/ui/kbd"; import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41"; type BolumSonucu = { href: string; ad: string; programSayisi: number; enIyiSira: number | null; seviye: string; }; type UniversiteSonucu = { href: string; ad: string; il: string | null; tur: string | null; programSayisi: number; fakulteSayisi: number; }; type AramaSonuclari = { bolumler: BolumSonucu[]; universiteler: UniversiteSonucu[]; }; const BOS_SONUCLAR: AramaSonuclari = { bolumler: [], universiteler: [] }; const REHBER_SONUC_SINIRI = 4; const sayi = (deger: number) => deger.toLocaleString("tr-TR"); export function KatalogArama({ rehberler }: { rehberler: RehberOzet[] }) { const profil = useTercihProfili(); const [acik, setAcik] = useState(false); const [sorgu, setSorgu] = useState(""); const [sonuclar, setSonuclar] = useState(BOS_SONUCLAR); const [yukleniyor, setYukleniyor] = useState(false); const [macMi, setMacMi] = useState(true); const inputRef = useRef(null); const kaydirmaRef = useRef(null); const [kenarBlur, setKenarBlur] = useState({ ust: false, alt: false }); const temizSorgu = sorgu.trim(); const aramaAktif = temizSorgu.length >= 2; const kenarBlurGuncelle = useCallback(() => { const alan = kaydirmaRef.current; if (!alan) return; const ust = alan.scrollTop > 4; const alt = alan.scrollTop + alan.clientHeight < alan.scrollHeight - 4; setKenarBlur((onceki) => onceki.ust === ust && onceki.alt === alt ? onceki : { ust, alt }, ); }, []); // Rehber özetleri build sırasında server'dan prop olarak gelir (~10 yazı); // arama client'ta yapılır — prod imajında content/ dizini bulunmaz. const rehberSonuclari = useMemo(() => { if (!aramaAktif) return []; const normalSorgu = aramaNormalize(temizSorgu).slice(0, 80); return rehberler .filter((yazi) => aramaNormalize(`${yazi.baslik} ${yazi.aciklama}`).includes(normalSorgu), ) .sort( (a, b) => eslesmePuani(a.baslik, normalSorgu) - eslesmePuani(b.baslik, normalSorgu), ) .slice(0, REHBER_SONUC_SINIRI); }, [aramaAktif, temizSorgu, rehberler]); const sonucVar = sonuclar.bolumler.length > 0 || sonuclar.universiteler.length > 0 || rehberSonuclari.length > 0; useEffect(() => { const platform = (navigator as { userAgentData?: { platform?: string } }).userAgentData ?.platform ?? navigator.platform ?? ""; setMacMi(/mac|iphone|ipad/i.test(platform)); }, []); useEffect(() => { function kisayolDinle(event: KeyboardEvent) { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { event.preventDefault(); modalDegisti(!acik); } } window.addEventListener("keydown", kisayolDinle); return () => window.removeEventListener("keydown", kisayolDinle); }, [acik]); useEffect(() => { if (!acik || !aramaAktif) return; const controller = new AbortController(); const zamanlayici = window.setTimeout(async () => { setYukleniyor(true); try { const response = await fetch( `/api/katalog-ara?q=${encodeURIComponent(temizSorgu)}`, { signal: controller.signal }, ); if (!response.ok) throw new Error("Arama başarısız"); setSonuclar((await response.json()) as AramaSonuclari); } catch (hata) { if (!(hata instanceof DOMException && hata.name === "AbortError")) { setSonuclar(BOS_SONUCLAR); } } finally { if (!controller.signal.aborted) setYukleniyor(false); } }, 160); return () => { window.clearTimeout(zamanlayici); controller.abort(); }; }, [acik, aramaAktif, temizSorgu]); // Sonuçlar, logolar ve kapak görselleri yüklendikçe scrollHeight değişir; // kenar blur görünürlüğünü scroll olayına ek olarak boyut değişiminde de tazele. useEffect(() => { if (!acik) return; const alan = kaydirmaRef.current; if (!alan) return; kenarBlurGuncelle(); const gozlemci = new ResizeObserver(kenarBlurGuncelle); gozlemci.observe(alan); for (const cocuk of alan.children) gozlemci.observe(cocuk); return () => gozlemci.disconnect(); }, [acik, aramaAktif, yukleniyor, sonucVar, kenarBlurGuncelle]); function modalDegisti(yeniAcik: boolean) { setAcik(yeniAcik); if (!yeniAcik) { setSorgu(""); setSonuclar(BOS_SONUCLAR); } } function kapat() { setAcik(false); } return ( <> {/* lg altında logo + sağ küme ile çakışır (absolute); o aralıkta gizli */} { event.preventDefault(); inputRef.current?.focus(); }} className="max-h-[min(720px,calc(100dvh-2rem))] grid-rows-[auto_minmax(0,1fr)_auto] gap-0 overflow-hidden rounded-2xl bg-white p-0 duration-200 sm:max-w-2xl motion-reduce:data-open:zoom-in-100 motion-reduce:data-closed:zoom-out-100" > Bölüm, üniversite ve rehber ara YKS bölümlerini, üniversiteleri ve rehber yazılarını tek yerden ara.
{ const yeniSorgu = event.target.value; setSorgu(yeniSorgu); setSonuclar(BOS_SONUCLAR); setYukleniyor(yeniSorgu.trim().length >= 2); }} placeholder="Bölüm, üniversite veya rehber ara..." aria-label="Bölüm, üniversite veya rehber ara" autoComplete="off" className="h-16 min-w-0 flex-1 bg-transparent text-base text-slate-950 outline-none placeholder:text-slate-400 sm:h-18 sm:text-lg" />
{!aramaAktif ? ( ) : yukleniyor ? ( ) : sonucVar ? (
{sonuclar.bolumler.length > 0 ? ( } > {sonuclar.bolumler.map((bolum) => ( ))} ) : null} {sonuclar.universiteler.length > 0 ? ( } > {sonuclar.universiteler.map((universite) => ( } baslik={universite.ad} meta={ [universite.il, universite.tur] .filter(Boolean) .join(" · ") || "Üniversite" } detay={`${sayi(universite.programSayisi)} program · ${sayi(universite.fakulteSayisi)} fakülte`} onSec={kapat} /> ))} ) : null} {rehberSonuclari.length > 0 ? ( } >
{rehberSonuclari.map((yazi) => ( ))}
) : null}
) : (

"{temizSorgu}" için sonuç bulunamadı

Bölüm, üniversite veya rehber konusunu farklı yazarak tekrar dene.

)}
En az 2 harf yazarak ara Esc ile kapat
); } function BaslangicIcerigi({ onSec }: { onSec: () => void }) { return (

Keşfet

} baslik="Tüm bölümler" aciklama="Lisans ve önlisans" onSec={onSec} /> } baslik="Üniversiteler" aciklama="Devlet ve vakıf" onSec={onSec} /> } baslik="Tercih rehberi" aciklama="Sorularına yanıtlar" onSec={onSec} />

Neyi merak ediyorsun?

“Psikoloji” veya “Boğaziçi” gibi bir ad yaz; eşleşen sayfaları ve temel bilgilerini burada önizle.

); } function KesfetLinki({ href, ikon, baslik, aciklama, onSec, }: { href: string; ikon: React.ReactNode; baslik: string; aciklama: string; onSec: () => void; }) { return ( {ikon} {baslik} {aciklama} ); } function SonucGrubu({ baslik, ikon, children, }: { baslik: string; ikon: React.ReactNode; children: React.ReactNode; }) { return (

{ikon} {baslik}

{children}
); } function SonucLinki({ href, baslik, meta, detay, gorsel, onSec, }: { href: string; baslik: string; meta: string; detay: string; gorsel?: React.ReactNode; onSec: () => void; }) { return ( {gorsel} {baslik} {meta} {detay} ); } function RehberSonucKarti({ yazi, onSec, }: { yazi: RehberOzet; onSec: () => void; }) { return ( {yazi.aciklama} Yazıyı oku ); } function AramaIskeleti() { return (
{[0, 1, 2, 3].map((sira) => (
))}
); }