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