From f4df52000b576749dd2622278c7ce2ce33d72b28 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Thu, 24 Sep 2026 01:23:20 +0300 Subject: [PATCH] =?UTF-8?q?feat(ux):=20/sonuc'a=20tek=20c=C3=BCmlelik=20ce?= =?UTF-8?q?vap,=20liste=20durumu=20ve=20kilometre=20ta=C5=9Flar=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sayfanın h1'i yoktu: "Sıralamanla açılan programlar" + "85.000. sıradasın (Sayısal). 4.760 program sana açık" başlığı en üstte; tadımlık altında (375 px'te hâlâ ilk ekranda, 474 px) - Başlık altında liste durumu: boşsa + ipucu, doluysa "Listende 3/24 program · 21 yer kaldı" düğmesi çekmeceyi açar - + ile ilk programda ve 24/24'te tek seferlik sakin onay; liste doluyken ne yapılacağını söyleyen mesaj - Bildirimler 16 px başlık, okunur açıklama, sitenin köşesiyle - Başlık ve tablo aynı sorguyu React cache ile paylaşıyor Co-Authored-By: Claude Opus 5.5 --- src/app/sonuc/page.tsx | 12 +++- src/components/ui/sonner.tsx | 7 ++- .../liste/components/liste-durumu-satiri.tsx | 55 ++++++++++++++++ .../liste/components/manuel-harita.tsx | 14 +++-- .../liste/components/program-ekle-butonu.tsx | 26 +++++++- .../components/sonuc-program-tablosu.tsx | 63 ++++++++++++++++++- .../rapor/components/tadimlik-satiri.tsx | 11 ++-- 7 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 src/features/liste/components/liste-durumu-satiri.tsx 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