Update Next.js configuration and package dependencies
All checks were successful
Deploy / deploy (push) Successful in 9m29s

- Modified redirects in next.config.ts to change the destination for "/sohbet" to "/listem".
- Added new dependencies in package.json: marked, motion, react-markdown, remark-breaks, remark-gfm, shiki, and use-stick-to-bottom.
- Updated pnpm-lock.yaml to reflect the new package versions and dependencies.
- Removed obsolete stack files for Flutter, Nuxt, Nuxt.js, React Native, and Svelte from the UI/UX Pro Max skill data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-07-26 04:26:52 +03:00
parent 1d0328d8eb
commit 209d203fe8
59 changed files with 6599 additions and 1446 deletions

View File

@@ -0,0 +1,253 @@
"use client";
// Sonuç sayfasının AI'sız ham listesi: yalnızca başarı sıralamasıyla
// yokatlas'tan gelen programlar, dilim sekmeleri halinde. Her satırın "+"
// butonu programı manuel 24'lük listeye ekler; pill navbar'daki (ya da açık
// çekmecedeki) Listem hedefine uçar (bkz. manuel-liste/liste-cekmecesi).
import { useState } from "react";
import { Check, Plus, Rocket, Scale, ShieldCheck } from "lucide-react";
import type { Program, RankResults } from "@/lib/db";
import { riskHesapla, type RiskSeviyesi } from "@/lib/risk";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
MANUEL_LISTE_MAX,
ekle,
ucusBaslat,
useManuelListe,
type ManuelTercih,
} from "@/components/manuel-liste/store";
import { ManuelHarita } from "@/components/manuel-liste/manuel-harita";
type DilimKey = keyof RankResults;
const DILIMLER: {
key: DilimKey;
etiket: string;
aciklama: string;
icon: typeof Rocket;
renk: string;
}[] = [
{
key: "hayal",
etiket: "Hayal",
aciklama: "Tabanı sıralamanın üstünde — şansını denediğin satırlar.",
icon: Rocket,
renk: "text-red-500",
},
{
key: "dengeli",
etiket: "Dengeli",
aciklama: "Tabanı sıralamana denk — listenin bel kemiği.",
icon: Scale,
renk: "text-amber-500",
},
{
key: "garanti",
etiket: "Garanti",
aciklama: "Tabanı belirgin şekilde altında — güvenli bölge.",
icon: ShieldCheck,
renk: "text-emerald-600",
},
];
const RISK_NOKTA: Record<RiskSeviyesi, string> = {
guvenli: "bg-emerald-500",
"az-riskli": "bg-amber-500",
riskli: "bg-red-500",
};
function efektifSira(p: Program): number | null {
return p.sira2025 ?? p.sira2024;
}
function tercihYap(p: Program, adaySira: number): ManuelTercih {
const taban = efektifSira(p);
return {
id: p.id,
isim: p.isim,
universite: p.universite,
il: p.il,
efektifSira: taban,
risk: riskHesapla(taban, adaySira),
};
}
export function ProgramTablosu({
sonuclar,
adaySira,
}: {
sonuclar: RankResults;
adaySira: number;
}) {
const liste = useManuelListe();
const [aktifDilim, setAktifDilim] = useState<DilimKey>("dengeli");
const listedekiler = new Set(liste.map((t) => t.id));
const doldu = liste.length >= MANUEL_LISTE_MAX;
function eklemeyiDene(p: Program, buton: HTMLElement) {
const tercih = tercihYap(p, adaySira);
if (!ekle(tercih)) return;
const r = buton.getBoundingClientRect();
ucusBaslat({
tercih,
kaynak: { x: r.left + r.width / 2, y: r.top + r.height / 2 },
});
}
return (
<section className="mt-16" aria-label="Sıralamanla açılan programlar">
<div className="flex flex-wrap items-end justify-between gap-3">
<div>
<h2 className="font-heading text-2xl font-bold">
Sıralamanla ılan programlar
</h2>
<p className="mt-1 max-w-2xl text-sm text-slate-500">
Yapay zekâsız, ham YÖK Atlas verisi: geçen yılın taban
sıralamalarına göre ulaşabildiğin bölümler.{" "}
<span className="font-medium text-slate-700">
ile beğendiklerini kendi 24&apos;lük listene at.
</span>
</p>
</div>
</div>
{/* Listendeki üniversitelerin konumu — ekledikçe canlı dolar */}
<div className="mt-5">
<ManuelHarita />
</div>
<Tabs
value={aktifDilim}
onValueChange={(v) => setAktifDilim(v as DilimKey)}
className="mt-5"
>
<TabsList>
{DILIMLER.map((d) => (
<TabsTrigger key={d.key} value={d.key} className="cursor-pointer">
<d.icon className={`size-3.5 ${d.renk}`} aria-hidden />
{d.etiket}
<span className="text-xs tabular-nums text-slate-400">
{sonuclar[d.key].length}
</span>
</TabsTrigger>
))}
</TabsList>
{DILIMLER.map((d) => (
<TabsContent key={d.key} value={d.key}>
<p className="mt-2 text-xs text-slate-500">{d.aciklama}</p>
<div className="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white">
{sonuclar[d.key].length === 0 ? (
<p className="px-5 py-8 text-center text-sm text-slate-500">
Bu dilimde filtrene uyan program bulunamadı.
</p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-8" />
<TableHead>Program</TableHead>
<TableHead className="text-right">Taban sıra</TableHead>
<TableHead className="w-16 text-right">
<span className="sr-only">Listeye ekle</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sonuclar[d.key].map((p) => (
<ProgramSatiri
key={p.id}
program={p}
adaySira={adaySira}
listede={listedekiler.has(p.id)}
doldu={doldu}
onEkle={eklemeyiDene}
/>
))}
</TableBody>
</Table>
)}
</div>
</TabsContent>
))}
</Tabs>
</section>
);
}
function ProgramSatiri({
program: p,
adaySira,
listede,
doldu,
onEkle,
}: {
program: Program;
adaySira: number;
listede: boolean;
doldu: boolean;
onEkle: (p: Program, buton: HTMLElement) => void;
}) {
const taban = efektifSira(p);
const risk = riskHesapla(taban, adaySira);
return (
<TableRow>
<TableCell className="pl-4 pr-0">
{risk ? (
<span
className={`block size-2.5 rounded-full ${RISK_NOKTA[risk]}`}
aria-hidden
/>
) : null}
</TableCell>
<TableCell className="max-w-0 w-full">
<span className="block truncate text-sm font-semibold">{p.isim}</span>
<span className="block truncate text-xs text-slate-500">
{p.universite}
{p.il ? ` · ${p.il.toLocaleLowerCase("tr-TR")}` : ""}
</span>
</TableCell>
<TableCell className="text-right text-xs tabular-nums text-slate-500">
{taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—"}
</TableCell>
<TableCell className="pr-4 text-right">
<button
type="button"
disabled={listede || doldu}
onClick={(e) => onEkle(p, e.currentTarget)}
title={
listede
? "Zaten listende"
: doldu
? `Liste dolu (${MANUEL_LISTE_MAX}/${MANUEL_LISTE_MAX})`
: "Listeme ekle"
}
className={`inline-flex size-8 cursor-pointer items-center justify-center rounded-full border transition-[background-color,border-color,color,transform] duration-150 active:scale-[0.92] disabled:cursor-default ${
listede
? "border-emerald-200 bg-emerald-50 text-emerald-600"
: "border-slate-200 bg-white text-slate-500 hover:border-primary hover:bg-primary/5 hover:text-primary disabled:opacity-40"
}`}
>
{listede ? (
<Check className="size-4" aria-hidden />
) : (
<Plus className="size-4" aria-hidden />
)}
<span className="sr-only">
{listede ? `${p.isim} listende` : `${p.isim} listeye ekle`}
</span>
</button>
</TableCell>
</TableRow>
);
}