diff --git a/src/app/sonuc/page.tsx b/src/app/sonuc/page.tsx
index 2cc4b0b..10a57a8 100644
--- a/src/app/sonuc/page.tsx
+++ b/src/app/sonuc/page.tsx
@@ -7,6 +7,7 @@ import {
PROFIL_CEREZ_ADI,
} from "@/features/sihirbaz/sihirbaz-sabitler";
import {
+ SonucBasligi,
SonucProgramTablosu,
SonucProgramTablosuSkeleton,
} from "@/features/liste/components/sonuc-program-tablosu";
@@ -133,7 +134,16 @@ export default function SonucPage({
boyada haritanın tam ölçülü kutusunu çizer, chunk inince
kutunun yalnızca İÇİ dolar. Haritanın yerini bir daha
aşağı almadan önce bu mekanizmayı kontrol et. */}
-
+ {/* Sayfanın tek cümlelik cevabı ve h1'i: öğrenci önce kendi
+ sonucunu görür (DESIGN.md §10). Harita ve tadımlık altında. */}
+
+
+
{/* Girişsiz tadımlık: gerçek rapor satırı formatında tek örnek.
Havuzda içerik yoksa bileşen hiç render edilmez. Listesi
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index ce25f68..1c51819 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -37,8 +37,13 @@ const Toaster = ({ ...props }: ToasterProps) => {
} as React.CSSProperties
}
toastOptions={{
+ // Şişko ve sade (DESIGN.md §10): başlık 16 px, açıklama okunur
+ // koyulukta; kutu sitenin kart köşesiyle aynı.
classNames: {
- toast: "cn-toast",
+ toast: "cn-toast !rounded-2xl !border-slate-200 !p-4 !shadow-lg",
+ title: "!text-base !font-semibold !text-slate-900",
+ description: "!text-sm !leading-relaxed !text-slate-600",
+ icon: "!text-primary",
},
}}
{...props}
diff --git a/src/features/liste/components/liste-durumu-satiri.tsx b/src/features/liste/components/liste-durumu-satiri.tsx
new file mode 100644
index 0000000..51c71b4
--- /dev/null
+++ b/src/features/liste/components/liste-durumu-satiri.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import { ArrowRight, Plus } from "lucide-react";
+import {
+ MANUEL_LISTE_MAX,
+ cekmeceAc,
+ useListeHidreMi,
+ useManuelListe,
+} from "../liste-store";
+
+/**
+ * /sonuc başlığının altındaki "listen nerede?" satırı (DESIGN.md §10):
+ * öğrenci kendi kurduğu listenin durumunu sayıyla görür (hedef eğimi:
+ * "5/24"), tek dokunuşla çekmeceyi açar. Liste boşsa ne yapılacağını söyler.
+ * Liste localStorage'da yaşadığı için hidrasyondan önce yer tutucu çizilir,
+ * satır zıplamasın.
+ */
+export function ListeDurumuSatiri() {
+ const hidre = useListeHidreMi();
+ const liste = useManuelListe();
+
+ if (!hidre) return
;
+
+ if (liste.length === 0) {
+ return (
+
+
+
+ Artı düğmesi
+
+ ile beğendiğin programı listene ekle; sırasını sonra sen belirlersin.
+
+ );
+ }
+
+ const kalan = MANUEL_LISTE_MAX - liste.length;
+ return (
+
+
+
+ );
+}
diff --git a/src/features/liste/components/manuel-harita.tsx b/src/features/liste/components/manuel-harita.tsx
index 007a867..8b2f810 100644
--- a/src/features/liste/components/manuel-harita.tsx
+++ b/src/features/liste/components/manuel-harita.tsx
@@ -20,7 +20,13 @@ const RISK_AGIRLIK: Record = {
riskli: 2,
};
-export function ManuelHarita({ yerAyir = false }: { yerAyir?: boolean }) {
+export function ManuelHarita({
+ yerAyir = false,
+ className,
+}: {
+ yerAyir?: boolean;
+ className?: string;
+}) {
const liste = useManuelListe();
const hidre = useListeHidreMi();
const [seciliIl, setSeciliIl] = useState(null);
@@ -85,10 +91,10 @@ export function ManuelHarita({ yerAyir = false }: { yerAyir?: boolean }) {
// durumunda boş harita ekranda kalmaz.
if (liste.length === 0 && (hidre || !yerAyir)) return null;
- // Margin yok: harita /sonuc'ta main'in İLK çocuğudur, üst boşluğunu
- // main'in py-12'sinden alır (bkz. sonuc/page.tsx).
+ // Üst boşluk çağırandan gelir: /sonuc'ta harita sonuç başlığının altında
+ // durur (bkz. sonuc/page.tsx). Harita çizilmezse boşluk da oluşmaz.
return (
-
+
n.toLocaleString("tr-TR");
+
+// Başlık ve tablo aynı isteği okur; cache tek istek içinde bir kez sorgular.
+// Anahtar ilkel olsun diye iller birleştirilmiş metin olarak geçer.
+const sonuclariOku = cache(
+ (sira: number, tur: PuanTuruKey, illerAnahtari: string, tip?: UniturGrubu) =>
+ siraylaProgramAra(sira, tur, {
+ iller: illerAnahtari ? illerAnahtari.split("|") : [],
+ uniturGrubu: tip,
+ }),
+);
+
+/**
+ * /sonuc'un tek cümlelik cevabı (DESIGN.md §10): sayfanın h1'i, öğrencinin
+ * sırası ve kaç programın açık olduğu. Tadımlık ve tablo bunun altında.
+ * Geniş ekran altında ortalı (§5.2).
+ */
+export function SonucBasligi({
+ sira,
+ tur,
+ iller,
+ uniturGrubu,
+}: {
+ sira: number;
+ tur: PuanTuruKey;
+ iller?: string[];
+ uniturGrubu?: UniturGrubu;
+}) {
+ const { toplam } = sonuclariOku(sira, tur, (iller ?? []).join("|"), uniturGrubu);
+ const acik = toplam.hayal + toplam.dengeli + toplam.garanti;
+ const filtre = [
+ ...(iller ?? []).map(trBaslikDuzeni),
+ ...(uniturGrubu === "devlet"
+ ? ["yalnız devlet"]
+ : uniturGrubu === "vakif"
+ ? ["yalnız vakıf"]
+ : []),
+ ];
+ return (
+
+
+ Sıralamanla açılan programlar
+
+
+ {sayi(sira)}.{" "}
+ sıradasın ({PUAN_TURU_ETIKET[tur] ?? tur}).{" "}
+ {filtre.length > 0 ? `Seçimlerine göre (${filtre.join(", ")}) ` : ""}
+
+ {sayi(acik)} program
+ {" "}
+ sana açık; aşağıda riskine göre ayrıldı.
+
+
+
+ );
+}
+
// /sonuc'un ham tablosunun sunucu sarmalayıcısı: okuma burada yaşar, sayfa
// yalnızca kompoze eder.
export function SonucProgramTablosu({
@@ -16,7 +77,7 @@ export function SonucProgramTablosu({
iller?: string[];
uniturGrubu?: UniturGrubu;
}) {
- const sonuclar = siraylaProgramAra(sira, tur, { iller, uniturGrubu });
+ const sonuclar = sonuclariOku(sira, tur, (iller ?? []).join("|"), uniturGrubu);
return (
+ // bu bileşen hiç dönmez.
+
Yapay zekâdan örnek satır