Add "detay" script to package.json and enhance ListemSekmeleri component functionality
All checks were successful
Deploy / deploy (push) Successful in 16m29s
All checks were successful
Deploy / deploy (push) Successful in 16m29s
- Introduced a new "detay" script in package.json for additional processing. - Refactored ListemSekmeleri component to improve tab functionality and state management. - Updated UI elements for better user interaction and clarity, including renaming "Seçtiklerim" to "Kendi Listem" for consistency across the application. - Made adjustments to related components for improved integration and user experience.
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
} from "@/components/sihirbaz-adimlar-lazy";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import type { SihirbazFacetleri } from "@/lib/db";
|
||||
import { SIHIRBAZ_STORAGE_KEY, type SihirbazSecimleri } from "@/lib/sihirbaz";
|
||||
import { tercihProfiliYaz, type SihirbazSecimleri } from "@/lib/sihirbaz";
|
||||
|
||||
// lib/db sunucu tarafı; etiketler client bundle'a sızmasın diye burada.
|
||||
const PUAN_TURLERI_SECENEK = [
|
||||
@@ -82,12 +82,7 @@ export function HeroForm() {
|
||||
// oradan gelir (değer önce — giriş duvarı sihirbazın hemen arkasında değil).
|
||||
function secimleriTamamla(secimler: SihirbazSecimleri) {
|
||||
if (!sira) return;
|
||||
try {
|
||||
localStorage.setItem(
|
||||
SIHIRBAZ_STORAGE_KEY,
|
||||
JSON.stringify({ sira, tur, secimler }),
|
||||
);
|
||||
} catch {}
|
||||
tercihProfiliYaz({ sira, tur, secimler });
|
||||
setModalAcik(false);
|
||||
router.push(
|
||||
girisli
|
||||
|
||||
@@ -6,12 +6,19 @@ 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 {
|
||||
@@ -50,6 +57,7 @@ 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<AramaSonuclari>(BOS_SONUCLAR);
|
||||
@@ -170,18 +178,43 @@ export function KatalogArama({ rehberler }: { rehberler: RehberOzet[] }) {
|
||||
<>
|
||||
{/* lg altında logo + sağ küme ile çakışır (absolute); o aralıkta gizli */}
|
||||
<nav className="absolute left-1/2 hidden -translate-x-1/2 items-center gap-1 rounded-full border border-slate-200 bg-white p-1.5 text-base font-medium text-slate-600 lg:flex">
|
||||
<Link
|
||||
href="/#nasil-calisir"
|
||||
className="rounded-full px-5 py-2 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
|
||||
>
|
||||
Nasıl çalışır?
|
||||
</Link>
|
||||
<Link
|
||||
href="/#karsilastirma"
|
||||
className="rounded-full px-5 py-2 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
|
||||
>
|
||||
Karşılaştır
|
||||
</Link>
|
||||
{profil ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={profilDuzenlemeyiAc}
|
||||
aria-label={`Sıralaman ${profil.sira.toLocaleString("tr-TR")} (${PUAN_TURU_ETIKET[profil.tur] ?? profil.tur}) — tercihlerini düzenle`}
|
||||
className="group/profil flex cursor-pointer items-center gap-2.5 rounded-full py-1.5 pl-4 pr-1.5 transition-colors duration-200 hover:bg-slate-100"
|
||||
>
|
||||
<Trophy className="size-4 text-orange-500" aria-hidden />
|
||||
<span className="flex items-baseline gap-1.5">
|
||||
<span className="font-semibold tabular-nums tracking-tight text-slate-900">
|
||||
{profil.sira.toLocaleString("tr-TR")}
|
||||
</span>
|
||||
<span className="text-xs font-medium text-slate-400">
|
||||
{PUAN_TURU_ETIKET[profil.tur] ?? profil.tur}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1 rounded-full bg-slate-100 px-2.5 py-1.5 text-xs font-medium text-slate-500 transition-colors duration-200 group-hover/profil:bg-white group-hover/profil:text-slate-900">
|
||||
<SquarePen className="size-3" aria-hidden />
|
||||
Düzenle
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
<Link
|
||||
href="/#nasil-calisir"
|
||||
className="rounded-full px-5 py-2 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
|
||||
>
|
||||
Nasıl çalışır?
|
||||
</Link>
|
||||
<Link
|
||||
href="/#karsilastirma"
|
||||
className="rounded-full px-5 py-2 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
|
||||
>
|
||||
Karşılaştır
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAcik(true)}
|
||||
|
||||
@@ -49,10 +49,10 @@ import {
|
||||
} from "./store";
|
||||
|
||||
// Risk renk dili ürünün geri kalanıyla birebir aynı (bkz. rapor-listesi)
|
||||
const RISK_STIL: Record<RiskSeviyesi, { nokta: string; kenar: string }> = {
|
||||
guvenli: { nokta: "bg-emerald-500", kenar: "border-l-emerald-500" },
|
||||
"az-riskli": { nokta: "bg-amber-500", kenar: "border-l-amber-500" },
|
||||
riskli: { nokta: "bg-red-500", kenar: "border-l-red-500" },
|
||||
const RISK_NOKTA: Record<RiskSeviyesi, string> = {
|
||||
guvenli: "bg-emerald-500",
|
||||
"az-riskli": "bg-amber-500",
|
||||
riskli: "bg-red-500",
|
||||
};
|
||||
|
||||
export function ListeCekmecesi() {
|
||||
@@ -137,7 +137,7 @@ function CekmeceSheet() {
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="flex items-center gap-2 font-heading text-lg font-bold">
|
||||
<ListChecks className="size-5 text-primary" aria-hidden />
|
||||
Seçtiklerim
|
||||
Kendi Listem
|
||||
<span className="font-heading text-sm font-bold tabular-nums text-slate-400">
|
||||
{liste.length}/{MANUEL_LISTE_MAX}
|
||||
</span>
|
||||
@@ -182,6 +182,26 @@ function CekmeceSheet() {
|
||||
Bölüm, üniversite veya sonuç tablolarından eklediğin programlar —
|
||||
tutamaktan sürükleyip sırala, sağa kaydırıp sil.
|
||||
</p>
|
||||
<ul
|
||||
className="mt-2.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-slate-500"
|
||||
aria-label="Yerleşme riski renkleri"
|
||||
>
|
||||
{(
|
||||
[
|
||||
["guvenli", "yeşil güvenli"],
|
||||
["az-riskli", "turuncu az riskli"],
|
||||
["riskli", "kırmızı riskli"],
|
||||
] as const
|
||||
).map(([seviye, aciklama]) => (
|
||||
<li key={seviye} className="inline-flex items-center gap-1.5">
|
||||
<span
|
||||
className={`size-2 shrink-0 rounded-full ${RISK_NOKTA[seviye]}`}
|
||||
aria-hidden
|
||||
/>
|
||||
<span>{aciklama}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
|
||||
{/* Gövde: sürüklenebilir satırlar */}
|
||||
@@ -235,7 +255,7 @@ function CekmeceSheet() {
|
||||
className="inline-flex items-center gap-1.5 text-sm font-medium text-primary transition-colors duration-200 hover:text-primary/80"
|
||||
>
|
||||
<ListChecks className="size-4" aria-hidden />
|
||||
Seçtiklerimi ve uyum notlarını aç
|
||||
Kendi listemi ve uyum notlarını aç
|
||||
</Link>
|
||||
<Link
|
||||
href="/listem?sekme=ai"
|
||||
@@ -263,7 +283,6 @@ function CekmeceSatiri({
|
||||
sira: number;
|
||||
projeksiyonAcik: boolean;
|
||||
}) {
|
||||
const stil = tercih.risk ? RISK_STIL[tercih.risk] : null;
|
||||
const siraKontrol = useDragControls();
|
||||
|
||||
// Kartın yatay konumu: sil bandının görünürlüğü buna bağlı
|
||||
@@ -291,11 +310,7 @@ function CekmeceSatiri({
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, x: 96, transition: { duration: 0.18 } }}
|
||||
transition={{ type: "spring", duration: 0.4, bounce: 0 }}
|
||||
whileDrag={{
|
||||
scale: 1.03,
|
||||
boxShadow: "0 12px 32px rgba(15, 23, 42, 0.16)",
|
||||
zIndex: 10,
|
||||
}}
|
||||
whileDrag={{ scale: 1.03, zIndex: 10 }}
|
||||
className="relative select-none"
|
||||
>
|
||||
{/* Sil bandı: kart sağa kaydıkça altından görünür */}
|
||||
@@ -315,9 +330,7 @@ function CekmeceSatiri({
|
||||
dragElastic={{ left: 0, right: 0.7 }}
|
||||
dragSnapToOrigin
|
||||
onDragEnd={kaydirinca}
|
||||
className={`relative flex items-center gap-2.5 rounded-xl border border-l-4 border-slate-200 bg-white px-3 py-2.5 ${
|
||||
stil?.kenar ?? "border-l-slate-300"
|
||||
}`}
|
||||
className="relative flex items-center gap-2.5 rounded-xl border border-slate-200 bg-white px-3 py-2.5"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -350,7 +363,7 @@ function CekmeceSatiri({
|
||||
) : null}
|
||||
{tercih.risk ? (
|
||||
<span
|
||||
className={`size-2.5 shrink-0 rounded-full ${RISK_STIL[tercih.risk].nokta}`}
|
||||
className={`size-2.5 shrink-0 rounded-full ${RISK_NOKTA[tercih.risk]}`}
|
||||
aria-label={RISK_ETIKET[tercih.risk]}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -27,13 +27,13 @@ export function ListemButonu() {
|
||||
type="button"
|
||||
onClick={cekmeceDegistir}
|
||||
aria-expanded={acik}
|
||||
aria-label={`Seçtiklerim — ${liste.length}/${MANUEL_LISTE_MAX} program`}
|
||||
aria-label={`Kendi listem — ${liste.length}/${MANUEL_LISTE_MAX} program`}
|
||||
title="Tablodan seçtiğin programlar"
|
||||
className="relative inline-flex h-9 cursor-pointer items-center gap-1.5 whitespace-nowrap rounded-full border border-slate-200 bg-white px-3 text-xs font-semibold text-slate-700 transition-[color,background-color,border-color,transform] duration-200 hover:border-slate-300 hover:bg-slate-50 active:scale-[0.97] sm:h-12 sm:gap-2 sm:px-5 sm:text-sm"
|
||||
>
|
||||
<ListChecks className="size-3.5 text-primary sm:size-4" aria-hidden />
|
||||
{/* lg–xl: ortadaki nav ile yer dar — metin gizli, ikon+rozet kalır */}
|
||||
<span className="hidden sm:inline lg:hidden xl:inline">Seçtiklerim</span>
|
||||
<span className="hidden sm:inline lg:hidden xl:inline">Kendi Listem</span>
|
||||
{liste.length > 0 ? (
|
||||
// key=length: her eklemede rozet yeniden doğar ve spring ile oturur
|
||||
<motion.span
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import type { Program } from "@/lib/db";
|
||||
import {
|
||||
PROFIL_DEGISTI_EVENT,
|
||||
tercihProfiliOku,
|
||||
tercihProfiliTamamlandiMi,
|
||||
type TercihProfili,
|
||||
@@ -75,6 +76,12 @@ function yukle() {
|
||||
profiliTazele();
|
||||
yayinla();
|
||||
});
|
||||
// Aynı sekmedeki yazmalar (hero formu, AI sihirbazı) storage olayı
|
||||
// tetiklemez; tercihProfiliYaz/Sil bu olayı yayınlar.
|
||||
window.addEventListener(PROFIL_DEGISTI_EVENT, () => {
|
||||
profiliTazele();
|
||||
yayinla();
|
||||
});
|
||||
}
|
||||
|
||||
function abone(cb: () => void) {
|
||||
|
||||
@@ -80,7 +80,7 @@ export function SecimlerimPaneli() {
|
||||
<div>
|
||||
<h2 className="flex items-center gap-2 font-heading text-xl font-bold">
|
||||
<ListChecks className="size-5 text-primary" aria-hidden />
|
||||
Seçtiklerim
|
||||
Kendi Listem
|
||||
<span className="text-sm font-bold tabular-nums text-slate-400">
|
||||
{liste.length}/{MANUEL_LISTE_MAX}
|
||||
</span>
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from "@/components/ui/table";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu";
|
||||
import { UniLogo } from "@/components/uni-logo";
|
||||
|
||||
const DILIMLER: {
|
||||
key: DilimKey;
|
||||
@@ -337,31 +338,38 @@ function ProgramSatiri({
|
||||
) : null}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-0 w-full">
|
||||
<span className="block truncate text-sm font-semibold">{p.isim}</span>
|
||||
<span className="mt-0.5 flex items-center gap-1.5 text-xs text-slate-500">
|
||||
<span
|
||||
className={`shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4 ${
|
||||
devlet
|
||||
? "bg-slate-100 text-slate-600"
|
||||
: "bg-violet-50 text-violet-600"
|
||||
}`}
|
||||
>
|
||||
{devlet ? "Devlet" : "Vakıf"}
|
||||
<span className="flex min-w-0 items-center gap-2.5">
|
||||
<UniLogo ad={p.universite} boy="sm" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-semibold">
|
||||
{p.isim}
|
||||
</span>
|
||||
<span className="mt-0.5 flex items-center gap-1.5 text-xs text-slate-500">
|
||||
<span
|
||||
className={`shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4 ${
|
||||
devlet
|
||||
? "bg-slate-100 text-slate-600"
|
||||
: "bg-violet-50 text-violet-600"
|
||||
}`}
|
||||
>
|
||||
{devlet ? "Devlet" : "Vakıf"}
|
||||
</span>
|
||||
<span className="truncate">
|
||||
<Link
|
||||
href={`/universite/${uniSayfaSlug(p.universite)}`}
|
||||
className="hover:text-primary hover:underline"
|
||||
title={`${p.universite} taban puanları sayfası`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{p.universite}
|
||||
</Link>
|
||||
{p.il ? ` · ${p.il.toLocaleLowerCase("tr-TR")}` : ""}
|
||||
</span>
|
||||
{p.sure ? (
|
||||
<span className="shrink-0 text-slate-400">· {p.sure} yıl</span>
|
||||
) : null}
|
||||
</span>
|
||||
</span>
|
||||
<span className="truncate">
|
||||
<Link
|
||||
href={`/universite/${uniSayfaSlug(p.universite)}`}
|
||||
className="hover:text-primary hover:underline"
|
||||
title={`${p.universite} taban puanları sayfası`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{p.universite}
|
||||
</Link>
|
||||
{p.il ? ` · ${p.il.toLocaleLowerCase("tr-TR")}` : ""}
|
||||
</span>
|
||||
{p.sure ? (
|
||||
<span className="shrink-0 text-slate-400">· {p.sure} yıl</span>
|
||||
) : null}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="hidden text-right text-xs md:table-cell">
|
||||
|
||||
Reference in New Issue
Block a user