refactor: /sonuc senkron kompozisyona geçti (Faz 9)
- liste-queries.ts (siraylaProgramAra) ve sihirbaz-queries.ts (getSihirbazFacetleri) eklendi — /sonuc'un SQLite okumaları feature sorgularına indi - Yeni sunucu sarmalayıcıları: SonucProgramTablosu (+skeleton), TadimlikBolumu (girişliyse null — gruplu sınırda skeleton bilerek yok), SonucSihirbazi (session + rapor-varlığı + facetler içeride; client orkestratöre çözülmüş değerler iner) - Sayfa senkron: searchParams.then() içinde sıra doğrulaması; geçersiz sıra SiraGerekli'ye düşer; geçerli akışta tablo+tadımlık+sihirbaz+dipnot+footer tek gruplu Suspense'te (bugünkü tek await'li akışla aynı anda belirir) - sonuc/loading.tsx silindi; geometrisi sayfadaki fallback'te yaşıyor - Davranış değişikliği yok (getSession React cache ile tek okuma) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +0,0 @@
|
|||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
|
|
||||||
// /sonuc senkron SQLite sorgularıyla açılır; yavaş bağlantıda beyaz ekran
|
|
||||||
// yerine sayfanın iskeletini göster.
|
|
||||||
export default function SonucLoading() {
|
|
||||||
return (
|
|
||||||
<main className="mx-auto w-full max-w-6xl px-4 py-10">
|
|
||||||
<Skeleton className="h-6 w-40 rounded-full" />
|
|
||||||
<Skeleton className="mt-4 h-9 w-3/4 max-w-xl" />
|
|
||||||
<Skeleton className="mt-3 h-5 w-2/3 max-w-lg" />
|
|
||||||
<Skeleton className="mt-8 h-36 w-full rounded-2xl" />
|
|
||||||
<div className="mt-8 flex flex-col gap-2">
|
|
||||||
{Array.from({ length: 8 }).map((_, i) => (
|
|
||||||
<Skeleton key={i} className="h-14 w-full rounded-xl" />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { rankWindowFacets, searchByRank } from "@/lib/db";
|
|
||||||
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
|
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
|
||||||
import { getSession } from "@/lib/session";
|
import {
|
||||||
import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
|
SonucProgramTablosu,
|
||||||
import { tadimlikSec } from "@/lib/tadimlik-havuzu";
|
SonucProgramTablosuSkeleton,
|
||||||
import { SihirbazBolumu } from "@/features/sihirbaz/components/sihirbaz-cagri-karti";
|
} from "@/features/liste/components/sonuc-program-tablosu";
|
||||||
import { TadimlikSatiri } from "@/features/rapor/components/tadimlik-satiri";
|
|
||||||
import { ProgramTablosu } from "@/features/liste/components/program-tablosu";
|
|
||||||
import { ManuelHarita } from "@/features/liste/components/manuel-harita";
|
import { ManuelHarita } from "@/features/liste/components/manuel-harita";
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
import { TadimlikBolumu } from "@/features/rapor/components/tadimlik-bolumu";
|
||||||
|
import { SonucSihirbazi } from "@/features/sihirbaz/components/sihirbaz-bolumu";
|
||||||
import { SiraGerekli } from "@/features/sihirbaz/components/sira-gerekli";
|
import { SiraGerekli } from "@/features/sihirbaz/components/sira-gerekli";
|
||||||
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Tercih Listeni Kur",
|
title: "Tercih Listeni Kur",
|
||||||
@@ -19,7 +20,7 @@ export const metadata: Metadata = {
|
|||||||
robots: { index: false, follow: true },
|
robots: { index: false, follow: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function SonucPage({
|
export default function SonucPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: Promise<{
|
searchParams: Promise<{
|
||||||
@@ -29,7 +30,22 @@ export default async function SonucPage({
|
|||||||
hazir?: string;
|
hazir?: string;
|
||||||
}>;
|
}>;
|
||||||
}) {
|
}) {
|
||||||
const params = await searchParams;
|
return (
|
||||||
|
// Tablo değişken boylu olduğundan altındaki tadımlık, sihirbaz, dipnot ve
|
||||||
|
// footer aynı sınırda gruplu — bugünkü tek await'li akışla birebir aynı
|
||||||
|
// anda beliriyorlar, ara durumda zıplama yok.
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<main className="mx-auto w-full max-w-6xl px-4 py-10">
|
||||||
|
<Skeleton className="h-6 w-40 rounded-full" />
|
||||||
|
<Skeleton className="mt-4 h-9 w-3/4 max-w-xl" />
|
||||||
|
<Skeleton className="mt-3 h-5 w-2/3 max-w-lg" />
|
||||||
|
<Skeleton className="mt-8 h-36 w-full rounded-2xl" />
|
||||||
|
<SonucProgramTablosuSkeleton />
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{searchParams.then((params) => {
|
||||||
const sira = Number.parseInt(params.sira ?? "", 10);
|
const sira = Number.parseInt(params.sira ?? "", 10);
|
||||||
const turKey: PuanTuruKey =
|
const turKey: PuanTuruKey =
|
||||||
params.tur && params.tur in PUAN_TURLERI
|
params.tur && params.tur in PUAN_TURLERI
|
||||||
@@ -45,23 +61,6 @@ export default async function SonucPage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sihirbaz için: kullanıcının kayıtlı raporu var mı? Listenin kendisi
|
|
||||||
// /listem'de yaşar; burada yalnızca CTA'nın hangi hâlde render edileceği
|
|
||||||
// için varlık bilgisi gerekir.
|
|
||||||
const session = await getSession();
|
|
||||||
const raporSatiri = session
|
|
||||||
? await kullanicininRaporu(session.user.id)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const facetler = rankWindowFacets(sira, turKey);
|
|
||||||
// Yapay Zeka'sız ham liste: yalnızca sıralamayla ulaşılabilen programlar (dilimli)
|
|
||||||
const hamSonuclar = searchByRank(sira, turKey, { limitPerBucket: 20 });
|
|
||||||
|
|
||||||
// Girişsiz tadımlık (İP-B): SSR'da kovaya uyan "genel" satır seçilir;
|
|
||||||
// sihirbaz kategorisi client'ta okunup /api/tadimlik ile kişiselleştirilir.
|
|
||||||
// Girişli kullanıcı gerçek ürünü zaten görebildiği için tadımlık almaz.
|
|
||||||
const tadimlik = session ? null : await tadimlikSec(sira, turKey);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-50 text-slate-900">
|
<div className="min-h-screen bg-slate-50 text-slate-900">
|
||||||
<main className="mx-auto max-w-4xl px-4 py-12">
|
<main className="mx-auto max-w-4xl px-4 py-12">
|
||||||
@@ -70,35 +69,34 @@ export default async function SonucPage({
|
|||||||
buraya canlı yansır. */}
|
buraya canlı yansır. */}
|
||||||
<ManuelHarita />
|
<ManuelHarita />
|
||||||
|
|
||||||
{/* Yapay Zeka'dan bağımsız ham tablo — haritanın hemen altında, sayfanın ana
|
{/* Yapay Zeka'dan bağımsız ham tablo — haritanın hemen altında,
|
||||||
gövdesi; puan türü navbar'daki sıralama rozetinden değiştirilir */}
|
sayfanın ana gövdesi; puan türü navbar'daki sıralama
|
||||||
<ProgramTablosu sonuclar={hamSonuclar} adaySira={sira} tur={turKey} />
|
rozetinden değiştirilir */}
|
||||||
|
<SonucProgramTablosu sira={sira} tur={turKey} />
|
||||||
|
|
||||||
{/* Girişsiz tadımlık: gerçek rapor satırı formatında tek örnek.
|
{/* Girişsiz tadımlık: gerçek rapor satırı formatında tek örnek.
|
||||||
Havuzda içerik yoksa bileşen hiç render edilmez. */}
|
Havuzda içerik yoksa bileşen hiç render edilmez. */}
|
||||||
{!session ? (
|
<TadimlikBolumu sira={sira} tur={turKey} />
|
||||||
<TadimlikSatiri sira={sira} tur={turKey} ilk={tadimlik} />
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Sihirbaz akışı (CTA + modal) — üretim ve liste /listem'de yaşar,
|
{/* Sihirbaz akışı (CTA + modal) — üretim ve liste /listem'de
|
||||||
burada yalnızca funnel kartları render edilir */}
|
yaşar, burada yalnızca funnel kartları render edilir */}
|
||||||
<SihirbazBolumu
|
<SonucSihirbazi
|
||||||
sira={sira}
|
sira={sira}
|
||||||
tur={turKey}
|
tur={turKey}
|
||||||
facetler={facetler}
|
|
||||||
girisliMi={Boolean(session)}
|
|
||||||
raporVar={Boolean(raporSatiri?.result)}
|
|
||||||
otomatikAc={params.sihirbaz === "1"}
|
otomatikAc={params.sihirbaz === "1"}
|
||||||
secimlerHazir={params.hazir === "1"}
|
secimlerHazir={params.hazir === "1"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className="mt-10 text-center text-xs leading-relaxed text-slate-500">
|
<p className="mt-10 text-center text-xs leading-relaxed text-slate-500">
|
||||||
Veriler resmî YÖK Atlas kaynağından derlenmiştir. KolayTercih bir karar
|
Veriler resmî YÖK Atlas kaynağından derlenmiştir. KolayTercih
|
||||||
destek aracıdır; yerleşme garantisi vermez, tercih sorumluluğu adaya
|
bir karar destek aracıdır; yerleşme garantisi vermez, tercih
|
||||||
aittir.
|
sorumluluğu adaya aittir.
|
||||||
</p>
|
</p>
|
||||||
</main>
|
</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
})}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/features/liste/components/sonuc-program-tablosu.tsx
Normal file
27
src/features/liste/components/sonuc-program-tablosu.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { siraylaProgramAra } from "../liste-queries";
|
||||||
|
import { ProgramTablosu } from "./program-tablosu";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import type { PuanTuruKey } from "@/types/yokatlas";
|
||||||
|
|
||||||
|
// /sonuc'un ham tablosunun sunucu sarmalayıcısı: okuma burada yaşar, sayfa
|
||||||
|
// yalnızca kompoze eder.
|
||||||
|
export function SonucProgramTablosu({
|
||||||
|
sira,
|
||||||
|
tur,
|
||||||
|
}: {
|
||||||
|
sira: number;
|
||||||
|
tur: PuanTuruKey;
|
||||||
|
}) {
|
||||||
|
const sonuclar = siraylaProgramAra(sira, tur);
|
||||||
|
return <ProgramTablosu sonuclar={sonuclar} adaySira={sira} tur={tur} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SonucProgramTablosuSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="mt-8 flex flex-col gap-2">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<Skeleton key={i} className="h-14 w-full rounded-xl" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/features/liste/liste-queries.ts
Normal file
12
src/features/liste/liste-queries.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import "server-only";
|
||||||
|
import { searchByRank } from "@/lib/db";
|
||||||
|
import type { PuanTuruKey, RankResults } from "@/types/yokatlas";
|
||||||
|
|
||||||
|
// /sonuc'un ham tablosu: yalnızca sıralamayla ulaşılabilen programlar,
|
||||||
|
// hayal/dengeli/garanti dilimli, dilim başına ilk 20 satır.
|
||||||
|
export function siraylaProgramAra(
|
||||||
|
sira: number,
|
||||||
|
tur: PuanTuruKey,
|
||||||
|
): RankResults {
|
||||||
|
return searchByRank(sira, tur, { limitPerBucket: 20 });
|
||||||
|
}
|
||||||
22
src/features/rapor/components/tadimlik-bolumu.tsx
Normal file
22
src/features/rapor/components/tadimlik-bolumu.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { getSession } from "@/lib/session";
|
||||||
|
import { tadimlikSec } from "../rapor-queries";
|
||||||
|
import { TadimlikSatiri } from "./tadimlik-satiri";
|
||||||
|
import type { PuanTuruKey } from "@/types/yokatlas";
|
||||||
|
|
||||||
|
// Girişsiz tadımlık (İP-B): SSR'da kovaya uyan "genel" satır seçilir;
|
||||||
|
// sihirbaz kategorisi client'ta okunup /api/tadimlik ile kişiselleştirilir.
|
||||||
|
// Girişli kullanıcı gerçek ürünü zaten görebildiği için tadımlık almaz —
|
||||||
|
// bu bileşen o durumda hiç render edilmez (skeleton bilerek yok; sayfadaki
|
||||||
|
// gruplu Suspense sınırı yer ayırmaz).
|
||||||
|
export async function TadimlikBolumu({
|
||||||
|
sira,
|
||||||
|
tur,
|
||||||
|
}: {
|
||||||
|
sira: number;
|
||||||
|
tur: PuanTuruKey;
|
||||||
|
}) {
|
||||||
|
const session = await getSession();
|
||||||
|
if (session) return null;
|
||||||
|
const ilk = await tadimlikSec(sira, tur);
|
||||||
|
return <TadimlikSatiri sira={sira} tur={tur} ilk={ilk} />;
|
||||||
|
}
|
||||||
41
src/features/sihirbaz/components/sihirbaz-bolumu.tsx
Normal file
41
src/features/sihirbaz/components/sihirbaz-bolumu.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { getSession } from "@/lib/session";
|
||||||
|
import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
|
||||||
|
import { getSihirbazFacetleri } from "../sihirbaz-queries";
|
||||||
|
import { SihirbazBolumu } from "./sihirbaz-cagri-karti";
|
||||||
|
import type { PuanTuruKey } from "@/types/yokatlas";
|
||||||
|
|
||||||
|
// /sonuc'taki sihirbaz funnel'ının sunucu sarmalayıcısı: session, rapor-var-mı
|
||||||
|
// ve SSR facetleri burada okunur; client orkestratör (SihirbazBolumu,
|
||||||
|
// sihirbaz-cagri-karti.tsx) yalnızca çözülmüş değerleri alır. Üretim ve
|
||||||
|
// listenin kendisi /listem'de yaşar.
|
||||||
|
export async function SonucSihirbazi({
|
||||||
|
sira,
|
||||||
|
tur,
|
||||||
|
otomatikAc,
|
||||||
|
secimlerHazir,
|
||||||
|
}: {
|
||||||
|
sira: number;
|
||||||
|
tur: PuanTuruKey;
|
||||||
|
otomatikAc: boolean;
|
||||||
|
secimlerHazir: boolean;
|
||||||
|
}) {
|
||||||
|
// Kullanıcının kayıtlı raporu var mı? Yalnızca CTA'nın hangi hâlde render
|
||||||
|
// edileceği için varlık bilgisi gerekir.
|
||||||
|
const session = await getSession();
|
||||||
|
const raporSatiri = session
|
||||||
|
? await kullanicininRaporu(session.user.id)
|
||||||
|
: null;
|
||||||
|
const facetler = getSihirbazFacetleri(sira, tur);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SihirbazBolumu
|
||||||
|
sira={sira}
|
||||||
|
tur={tur}
|
||||||
|
facetler={facetler}
|
||||||
|
girisliMi={Boolean(session)}
|
||||||
|
raporVar={Boolean(raporSatiri?.result)}
|
||||||
|
otomatikAc={otomatikAc}
|
||||||
|
secimlerHazir={secimlerHazir}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/features/sihirbaz/sihirbaz-queries.ts
Normal file
12
src/features/sihirbaz/sihirbaz-queries.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import "server-only";
|
||||||
|
import { rankWindowFacets } from "@/lib/db";
|
||||||
|
import type { PuanTuruKey, SihirbazFacetleri } from "@/types/yokatlas";
|
||||||
|
|
||||||
|
// Sihirbaz adımlarının SSR başlangıç facetleri; adım geçişleri client'ta
|
||||||
|
// /api/facetler ile yeniden hesaplanır (kademeli süzme orada).
|
||||||
|
export function getSihirbazFacetleri(
|
||||||
|
sira: number,
|
||||||
|
tur: PuanTuruKey,
|
||||||
|
): SihirbazFacetleri {
|
||||||
|
return rankWindowFacets(sira, tur);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user