Yalnız kullanıcıya görünen etiket değişir; iç anahtar, tip, DB değeri, API parametresi, mermaid classDef ve analitik değeri `garanti` olarak kalır (veri göçü yok). - risk.ts: tek kaynak DILIM_ETIKET + dilimdenFarkliRiskEtiketi; rapor listesi, yazdır tablosu, /sonuc sekmeleri, seçimlerim paneli ve meraklısına şeridi buradan okur - "Güvenli · Güvenli" / "Güvenli (Güvenli)": satır detayında risk etiketi yalnız dilimin varsayılanından farklıysa, yazdır ve seçimlerimde yalnız kelime farklıysa yazılır - LLM prompt'ları: `garanti` teknik anahtar, adaya "güvenli" denir sözlüğü (rapor, soru, tadımlık); liste özeti modele görünen dilim adıyla gider - meraklısına şeması (SEMA düğümü, aria-label), 2. bölüm anlatımı ve AGENTS.md şema kuralı görünen adla eşitlendi - "yerleşme garantisi vermez" türü vaat reddi cümleleri aynen kaldı Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
157 lines
5.9 KiB
TypeScript
157 lines
5.9 KiB
TypeScript
import Image from "next/image";
|
||
import { redirect } from "next/navigation";
|
||
import { verifySession, getCurrentUser } from "@/lib/session";
|
||
import { kullanicininRaporu } from "../rapor-queries";
|
||
import { PUAN_TURLERI, type DilimKey } from "@/types/yokatlas";
|
||
import { DILIM_ETIKET, RISK_ETIKET, dilimdenRisk } from "@/lib/risk";
|
||
import type { RaporParams, RaporSonuc } from "../types/rapor";
|
||
import { Skeleton } from "@/components/ui/skeleton";
|
||
import { YazdirButonu } from "./yazdir-butonu";
|
||
|
||
// Dilim sütunu: dilim adı + varsayılan riski. Üçüncü dilimde ikisi aynı
|
||
// kelime ("Güvenli") olduğu için parantez düşer — "Güvenli (Güvenli)" yazmaz.
|
||
function dilimLabel(dilim: DilimKey): string {
|
||
const ad = DILIM_ETIKET[dilim] ?? dilim;
|
||
const risk = RISK_ETIKET[dilimdenRisk(dilim)];
|
||
return ad === risk ? ad : `${ad} (${risk})`;
|
||
}
|
||
|
||
export async function RaporYazdir() {
|
||
const session = await verifySession("/rapor/yazdir");
|
||
// Kullanıcı satırı ile rapor sorgusu bağımsız — paralel çöz
|
||
const [user, satir] = await Promise.all([
|
||
getCurrentUser(),
|
||
kullanicininRaporu(session.user.id),
|
||
]);
|
||
if (!user) redirect("/giris");
|
||
if (!user.hasPaket) redirect("/paket");
|
||
if (!satir?.result) redirect("/sonuc");
|
||
|
||
const rapor = satir.result as RaporSonuc;
|
||
const params = satir.params as RaporParams;
|
||
const tarih = new Date(satir.updatedAt).toLocaleDateString("tr-TR", {
|
||
day: "numeric",
|
||
month: "long",
|
||
year: "numeric",
|
||
});
|
||
|
||
return (
|
||
<div className="mx-auto max-w-3xl bg-white p-8 text-slate-900 print:p-[10mm]">
|
||
<YazdirButonu />
|
||
|
||
<header className="border-b-2 border-slate-900 pb-4">
|
||
<h1 className="font-heading text-2xl font-bold">
|
||
{rapor.tercihler.length} Tercihlik Kişisel Liste ve Risk Raporu
|
||
</h1>
|
||
<p className="mt-1 text-sm text-slate-600">
|
||
Başarı sıralaması: {params.sira.toLocaleString("tr-TR")} · Puan
|
||
türü: {PUAN_TURLERI[params.tur]}
|
||
{params.il ? ` · İl: ${params.il}` : ""} · {tarih}
|
||
</p>
|
||
{rapor.kapsam?.uyarlandi ? (
|
||
<p className="mt-1 text-sm text-slate-600">
|
||
Not: Tercihlerine göre {rapor.kapsam.toplam} uygun program bulundu;
|
||
liste {rapor.kapsam.dagilim.hayal} hayal ·{" "}
|
||
{rapor.kapsam.dagilim.dengeli} dengeli ·{" "}
|
||
{rapor.kapsam.dagilim.garanti} güvenli olarak kuruldu.
|
||
</p>
|
||
) : null}
|
||
</header>
|
||
|
||
<section className="mt-5">
|
||
<h2 className="font-heading text-base font-bold">
|
||
Genel değerlendirme
|
||
</h2>
|
||
<p className="mt-1 whitespace-pre-line text-sm leading-relaxed">
|
||
{rapor.genelDegerlendirme}
|
||
</p>
|
||
</section>
|
||
|
||
<table className="mt-5 w-full border-collapse text-xs">
|
||
<thead>
|
||
<tr className="border-b-2 border-slate-900 text-left">
|
||
<th className="py-2 pr-2">#</th>
|
||
<th className="py-2 pr-2">Program / Üniversite</th>
|
||
<th className="py-2 pr-2">Dilim</th>
|
||
<th className="py-2 pr-2">Gerekçe</th>
|
||
<th className="py-2 pr-2">Risk notu</th>
|
||
<th className="py-2">Trend</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{rapor.tercihler.map((t) => {
|
||
const p = rapor.programlar[t.programId];
|
||
return (
|
||
<tr
|
||
key={t.sira}
|
||
className="break-inside-avoid border-b border-slate-200 align-top"
|
||
>
|
||
<td className="py-2 pr-2 font-bold">{t.sira}</td>
|
||
<td className="py-2 pr-2">
|
||
<span className="font-semibold">{p?.isim}</span>
|
||
<br />
|
||
<span className="text-slate-600">
|
||
{p?.universite} · {p?.il ?? "—"}
|
||
</span>
|
||
</td>
|
||
<td className="py-2 pr-2">{dilimLabel(t.dilim)}</td>
|
||
<td className="py-2 pr-2">{t.gerekce}</td>
|
||
<td className="py-2 pr-2">{t.riskNotu}</td>
|
||
<td className="py-2">{t.trendOzeti}</td>
|
||
</tr>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
|
||
<footer className="mt-6 border-t border-slate-300 pt-3 text-[10px] leading-relaxed text-slate-500">
|
||
<p>
|
||
Bu rapor KolayTercih tarafından resmî YÖK Atlas verisi (2021–2025
|
||
taban sıralamaları, kontenjan ve yerleşme istatistikleri) kullanılarak
|
||
üretilmiştir. Taban sıralamaları her yıl değişir; bu rapor yerleşme
|
||
garantisi vermez. Tercih listesinin ÖSYM sistemine girilmesi ve nihai
|
||
sorumluluğu adaya aittir.
|
||
</p>
|
||
<div className="mt-4 flex break-inside-avoid items-center justify-between">
|
||
<div className="flex items-center font-bricolage text-slate-900">
|
||
<Image
|
||
src="/kolay-tercih-mark.svg"
|
||
alt=""
|
||
width={44}
|
||
height={44}
|
||
aria-hidden="true"
|
||
className="size-11 shrink-0"
|
||
/>
|
||
<span>
|
||
<span className="font-light text-3xl tracking-tighter">Kolay</span>
|
||
<span className="font-semibold text-3xl tracking-tighter">
|
||
Tercih
|
||
</span>
|
||
</span>
|
||
</div>
|
||
<span className="font-bricolage text-sm font-medium text-slate-900">
|
||
kolaytercih.com
|
||
</span>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Rapor sorgusu beklenirken belge düzeninde iskelet.
|
||
export function RaporYazdirSkeleton() {
|
||
return (
|
||
<div aria-live="polite" className="mx-auto w-full max-w-3xl bg-white p-8">
|
||
<div className="border-b-2 border-slate-200 pb-4">
|
||
<Skeleton className="h-8 w-64" />
|
||
<Skeleton className="mt-2 h-4 w-40" />
|
||
</div>
|
||
<div className="mt-6 space-y-3">
|
||
{Array.from({ length: 8 }).map((_, i) => (
|
||
<Skeleton key={i} className="h-14 w-full rounded-lg" />
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|