From 8d26e375ff89369fed2ab46bcef51475b89dae53 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Sat, 8 Aug 2026 01:14:00 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20katalog=20feature'=C4=B1=20?= =?UTF-8?q?=E2=80=94=20sayfalar=20senkron=20kompozisyona=20ge=C3=A7ti=20(F?= =?UTF-8?q?az=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 8 katalog bileşeni src/features/katalog/components/ altına taşındı; katalog-queries.ts (server-only) lib/katalog'un feature kapısı oldu - /bolumler ve /universiteler'in sayfa içi fetch+inline bileşenleri BolumKatalogu/UniversiteKatalogu feature bileşenlerine çıkarıldı - 6 sayfa async'ten senkron'a: params.then() + sayfada Suspense, skeleton'lar feature bileşeninin yanında (BolumIcerikSkeleton vb.) - generateStaticParams artık *-queries yerine bileşen dosyasının helper'larını kullanıyor (bolumStaticSlugs, uniSayfaStaticParams) - Davranış değişikliği yok; route durumları aynı (◐), 1917 statik sayfa Co-Authored-By: Claude Fable 5 --- src/app/bolum/[slug]/page.tsx | 30 +++-- src/app/bolum/[slug]/sayfa/[no]/page.tsx | 38 +++--- src/app/bolumler/page.tsx | 100 ++------------- src/app/universite/[slug]/page.tsx | 30 +++-- src/app/universite/[slug]/sayfa/[no]/page.tsx | 38 +++--- src/app/universiteler/page.tsx | 95 ++------------- src/components/program-tablosu.tsx | 2 +- src/components/site-header.tsx | 2 +- .../katalog/components}/bolum-icerik.tsx | 46 ++++++- .../katalog/components/bolum-listesi.tsx | 115 ++++++++++++++++++ .../components/bolum-program-listesi.tsx | 4 +- .../katalog}/components/katalog-arama.tsx | 0 .../katalog}/components/program-netleri.tsx | 0 .../katalog}/components/sayfalama.tsx | 0 .../katalog/components}/universite-icerik.tsx | 54 +++++++- .../components/universite-konum-haritasi.tsx | 0 .../katalog/components/universite-listesi.tsx | 110 +++++++++++++++++ .../components/universite-program-tablosu.tsx | 2 +- src/features/katalog/katalog-queries.ts | 17 +++ 19 files changed, 425 insertions(+), 258 deletions(-) rename src/{app/bolum/[slug] => features/katalog/components}/bolum-icerik.tsx (76%) create mode 100644 src/features/katalog/components/bolum-listesi.tsx rename src/{ => features/katalog}/components/bolum-program-listesi.tsx (98%) rename src/{ => features/katalog}/components/katalog-arama.tsx (100%) rename src/{ => features/katalog}/components/program-netleri.tsx (100%) rename src/{ => features/katalog}/components/sayfalama.tsx (100%) rename src/{app/universite/[slug] => features/katalog/components}/universite-icerik.tsx (84%) rename src/{ => features/katalog}/components/universite-konum-haritasi.tsx (100%) create mode 100644 src/features/katalog/components/universite-listesi.tsx rename src/{ => features/katalog}/components/universite-program-tablosu.tsx (98%) create mode 100644 src/features/katalog/katalog-queries.ts diff --git a/src/app/bolum/[slug]/page.tsx b/src/app/bolum/[slug]/page.tsx index 675939e..46d9973 100644 --- a/src/app/bolum/[slug]/page.tsx +++ b/src/app/bolum/[slug]/page.tsx @@ -1,28 +1,32 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; -import { getAllBolumSlugs } from "@/lib/katalog"; -import { BolumIcerik, bolumSayfaMetadata } from "./bolum-icerik"; +import { + BolumIcerik, + BolumIcerikSkeleton, + bolumSayfaMetadata, + bolumStaticSlugs, +} from "@/features/katalog/components/bolum-icerik"; // Tüm bölümler build sırasında bilinir (veri ancak ingest + deploy ile // değişir); bilinmeyen slug getBolumBySlug'dan null dönüp notFound()'a düşer. // (dynamicParams cacheComponents ile uyumsuz olduğundan kullanılamıyor.) export function generateStaticParams() { - return getAllBolumSlugs().map((slug) => ({ slug })); + return bolumStaticSlugs().map((slug) => ({ slug })); } export async function generateMetadata({ params, -}: { - params: Promise<{ slug: string }>; -}): Promise { +}: PageProps<"/bolum/[slug]">): Promise { const { slug } = await params; return bolumSayfaMetadata(slug, 1); } -export default async function BolumPage({ - params, -}: { - params: Promise<{ slug: string }>; -}) { - const { slug } = await params; - return ; +export default function BolumPage({ params }: PageProps<"/bolum/[slug]">) { + return ( + }> + {params.then(({ slug }) => ( + + ))} + + ); } diff --git a/src/app/bolum/[slug]/sayfa/[no]/page.tsx b/src/app/bolum/[slug]/sayfa/[no]/page.tsx index d1d6b19..f1bd474 100644 --- a/src/app/bolum/[slug]/sayfa/[no]/page.tsx +++ b/src/app/bolum/[slug]/sayfa/[no]/page.tsx @@ -1,22 +1,17 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; import { notFound } from "next/navigation"; -import { getAllBolumler } from "@/lib/katalog"; import { BolumIcerik, + BolumIcerikSkeleton, bolumSayfaMetadata, - bolumToplamSayfa, -} from "../../bolum-icerik"; + bolumSayfaStaticParams, +} from "@/features/katalog/components/bolum-icerik"; // Yalnızca 1'den fazla sayfası olan bölümler için 2..N sayfaları üretilir; // 1. sayfa taban rota (/bolum/[slug]) olduğundan burada üretilmez. export function generateStaticParams() { - return getAllBolumler().flatMap((b) => { - const toplam = bolumToplamSayfa(b.programSayisi); - return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({ - slug: b.slug, - no: String(i + 2), - })); - }); + return bolumSayfaStaticParams(); } function sayfaNo(no: string): number | null { @@ -26,22 +21,23 @@ function sayfaNo(no: string): number | null { export async function generateMetadata({ params, -}: { - params: Promise<{ slug: string; no: string }>; -}): Promise { +}: PageProps<"/bolum/[slug]/sayfa/[no]">): Promise { const { slug, no } = await params; const n = sayfaNo(no); if (n == null) return {}; return bolumSayfaMetadata(slug, n); } -export default async function BolumSayfaPage({ +export default function BolumSayfaPage({ params, -}: { - params: Promise<{ slug: string; no: string }>; -}) { - const { slug, no } = await params; - const n = sayfaNo(no); - if (n == null) notFound(); - return ; +}: PageProps<"/bolum/[slug]/sayfa/[no]">) { + return ( + }> + {params.then(({ slug, no }) => { + const n = sayfaNo(no); + if (n == null) notFound(); + return ; + })} + + ); } diff --git a/src/app/bolumler/page.tsx b/src/app/bolumler/page.tsx index 715f8e1..df19cbf 100644 --- a/src/app/bolumler/page.tsx +++ b/src/app/bolumler/page.tsx @@ -1,11 +1,11 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; -import Link from "next/link"; -import { getAllBolumler, type BolumOzet } from "@/lib/katalog"; -import { KATEGORILER } from "@/lib/kategoriler"; import { JsonLd, breadcrumbJsonLd } from "@/lib/seo"; -import { CtaSiraForm } from "@/components/cta-sira-form"; import { SiteFooter } from "@/components/site-footer"; -import { PagePixelDivider } from "@/components/pixel-decor"; +import { + BolumKatalogu, + BolumKataloguSkeleton, +} from "@/features/katalog/components/bolum-listesi"; export const metadata: Metadata = { title: "Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026", @@ -14,68 +14,7 @@ export const metadata: Metadata = { alternates: { canonical: "/bolumler" }, }; -const sayi = (n: number) => n.toLocaleString("tr-TR"); - -function BolumLinkleri({ bolumler }: { bolumler: BolumOzet[] }) { - return ( -
    - {bolumler.map((b) => ( -
  • - - {b.ad} - - {sayi(b.programSayisi)} - - -
  • - ))} -
- ); -} - -function KategoriGruplari({ - bolumler, - seviye, -}: { - bolumler: BolumOzet[]; - seviye: "lisans" | "onlisans"; -}) { - const kalanlar = new Set(bolumler.map((b) => b.slug)); - const gruplar = KATEGORILER.map((kategori) => { - const uyeler = bolumler.filter((b) => - (b.kategoriler as readonly string[]).includes(kategori), - ); - for (const b of uyeler) kalanlar.delete(b.slug); - return { kategori: kategori as string, uyeler }; - }).filter((g) => g.uyeler.length > 0); - const diger = bolumler.filter((b) => kalanlar.has(b.slug)); - if (diger.length > 0) gruplar.push({ kategori: "Diğer", uyeler: diger }); - - return ( - <> - {gruplar.map(({ kategori, uyeler }) => ( -
-

- {kategori} - - {sayi(uyeler.length)} bölüm - -

- -
- ))} - - ); -} - export default function BolumlerPage() { - const hepsi = getAllBolumler(); - const lisans = hepsi.filter((b) => b.lisans); - const onlisans = hepsi.filter((b) => b.onlisans && !b.lisans); - return ( <>
@@ -89,33 +28,10 @@ export default function BolumlerPage() {

Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026

-

- {sayi(hepsi.length)} bölümün 2025 YKS yerleştirme verileri: taban - puanları, başarı sıralamaları, kontenjanlar ve devlet/vakıf dağılımı. - Bölüme tıklayınca o bölümü sunan tüm üniversitelerin tablosunu - görürsün. Veriler YÖK Atlas 2025 yerleştirme sonuçlarına dayanır; 2026 - tercihleri için rehber niteliğindedir. -

- - -
-

- Lisans Bölümleri (4 Yıllık) -

- -
- -
- -
- -
-

- Önlisans Bölümleri (2 Yıllık — TYT) -

- -
+ }> + +
diff --git a/src/app/universite/[slug]/page.tsx b/src/app/universite/[slug]/page.tsx index 4695948..001904b 100644 --- a/src/app/universite/[slug]/page.tsx +++ b/src/app/universite/[slug]/page.tsx @@ -1,25 +1,31 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; -import { getAllUniversiteSlugs } from "@/lib/katalog"; -import { UniversiteIcerik, uniSayfaMetadata } from "./universite-icerik"; +import { + UniversiteIcerik, + UniversiteIcerikSkeleton, + uniSayfaMetadata, + uniStaticSlugs, +} from "@/features/katalog/components/universite-icerik"; export function generateStaticParams() { - return getAllUniversiteSlugs().map((slug) => ({ slug })); + return uniStaticSlugs().map((slug) => ({ slug })); } export async function generateMetadata({ params, -}: { - params: Promise<{ slug: string }>; -}): Promise { +}: PageProps<"/universite/[slug]">): Promise { const { slug } = await params; return uniSayfaMetadata(slug, 1); } -export default async function UniversitePage({ +export default function UniversitePage({ params, -}: { - params: Promise<{ slug: string }>; -}) { - const { slug } = await params; - return ; +}: PageProps<"/universite/[slug]">) { + return ( + }> + {params.then(({ slug }) => ( + + ))} + + ); } diff --git a/src/app/universite/[slug]/sayfa/[no]/page.tsx b/src/app/universite/[slug]/sayfa/[no]/page.tsx index a5484ed..a203996 100644 --- a/src/app/universite/[slug]/sayfa/[no]/page.tsx +++ b/src/app/universite/[slug]/sayfa/[no]/page.tsx @@ -1,22 +1,17 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; import { notFound } from "next/navigation"; -import { getAllUniversiteler } from "@/lib/katalog"; import { UniversiteIcerik, + UniversiteIcerikSkeleton, uniSayfaMetadata, - uniToplamSayfa, -} from "../../universite-icerik"; + uniSayfaStaticParams, +} from "@/features/katalog/components/universite-icerik"; // Yalnızca 1'den fazla sayfası olan üniversiteler için 2..N üretilir; // 1. sayfa taban rota (/universite/[slug]) olduğundan burada üretilmez. export function generateStaticParams() { - return getAllUniversiteler().flatMap((u) => { - const toplam = uniToplamSayfa(u.programSayisi); - return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({ - slug: u.slug, - no: String(i + 2), - })); - }); + return uniSayfaStaticParams(); } function sayfaNo(no: string): number | null { @@ -26,22 +21,23 @@ function sayfaNo(no: string): number | null { export async function generateMetadata({ params, -}: { - params: Promise<{ slug: string; no: string }>; -}): Promise { +}: PageProps<"/universite/[slug]/sayfa/[no]">): Promise { const { slug, no } = await params; const n = sayfaNo(no); if (n == null) return {}; return uniSayfaMetadata(slug, n); } -export default async function UniversiteSayfaPage({ +export default function UniversiteSayfaPage({ params, -}: { - params: Promise<{ slug: string; no: string }>; -}) { - const { slug, no } = await params; - const n = sayfaNo(no); - if (n == null) notFound(); - return ; +}: PageProps<"/universite/[slug]/sayfa/[no]">) { + return ( + }> + {params.then(({ slug, no }) => { + const n = sayfaNo(no); + if (n == null) notFound(); + return ; + })} + + ); } diff --git a/src/app/universiteler/page.tsx b/src/app/universiteler/page.tsx index b41ccb4..1571394 100644 --- a/src/app/universiteler/page.tsx +++ b/src/app/universiteler/page.tsx @@ -1,12 +1,11 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; -import Link from "next/link"; -import { getAllUniversiteler, type UniOzet } from "@/lib/katalog"; -import { trBaslikDuzeni } from "@/lib/slug"; import { JsonLd, breadcrumbJsonLd } from "@/lib/seo"; -import { CtaSiraForm } from "@/components/cta-sira-form"; import { SiteFooter } from "@/components/site-footer"; -import { PagePixelDivider } from "@/components/pixel-decor"; -import { UniLogo } from "@/components/uni-logo"; +import { + UniversiteKatalogu, + UniversiteKataloguSkeleton, +} from "@/features/katalog/components/universite-listesi"; export const metadata: Metadata = { title: "Üniversitelerin Taban Puanları ve Sıralamaları 2026", @@ -15,44 +14,7 @@ export const metadata: Metadata = { alternates: { canonical: "/universiteler" }, }; -const sayi = (n: number) => n.toLocaleString("tr-TR"); - -function UniLinkleri({ uniler }: { uniler: UniOzet[] }) { - return ( -
    - {uniler.map((u) => ( -
  • - - - - - {u.ad} - - - {u.il ? `${trBaslikDuzeni(u.il)} · ` : ""} - {sayi(u.programSayisi)} program - - - -
  • - ))} -
- ); -} - export default function UniversitelerPage() { - const hepsi = getAllUniversiteler(); - const devlet = hepsi.filter((u) => u.unitur === "DEVLET"); - const vakif = hepsi.filter( - (u) => u.unitur != null && ["VAKIF", "VAKIF MYO"].includes(u.unitur), - ); - const digerUniler = hepsi.filter( - (u) => !devlet.includes(u) && !vakif.includes(u), - ); - return ( <>
@@ -66,51 +28,10 @@ export default function UniversitelerPage() {

Üniversitelerin Taban Puanları ve Sıralamaları 2026

-

- {sayi(hepsi.length)} üniversitenin 2025 YKS yerleştirme verileri. - Üniversiteye tıklayınca tüm programlarının taban puanlarını, başarı - sıralamalarını ve kontenjanlarını fakülte fakülte görürsün. Veriler - YÖK Atlas 2025 yerleştirme sonuçlarına dayanır; 2026 tercihleri için - rehber niteliğindedir. -

- - -
-

- Devlet Üniversiteleri - - {sayi(devlet.length)} - -

- -
- -
-

- Vakıf Üniversiteleri - - {sayi(vakif.length)} - -

- -
- - {digerUniler.length > 0 ? ( -
-

- KKTC ve Yurt Dışı - - {sayi(digerUniler.length)} - -

- -
- ) : null} - -
- -
+ }> + +
diff --git a/src/components/program-tablosu.tsx b/src/components/program-tablosu.tsx index b8f1121..5b1be51 100644 --- a/src/components/program-tablosu.tsx +++ b/src/components/program-tablosu.tsx @@ -31,7 +31,7 @@ import { } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu"; -import { ProgramNetleriButonu } from "@/components/program-netleri"; +import { ProgramNetleriButonu } from "@/features/katalog/components/program-netleri"; import { UniLogo } from "@/components/uni-logo"; const DILIMLER: { diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index a5252bb..d48bf18 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -1,7 +1,7 @@ import { Suspense } from "react"; import Image from "next/image"; import Link from "next/link"; -import { KatalogArama } from "@/components/katalog-arama"; +import { KatalogArama } from "@/features/katalog/components/katalog-arama"; import { UserNav, UserNavFallback } from "@/components/user-nav"; import { getAllRehberler } from "@/lib/rehber"; diff --git a/src/app/bolum/[slug]/bolum-icerik.tsx b/src/features/katalog/components/bolum-icerik.tsx similarity index 76% rename from src/app/bolum/[slug]/bolum-icerik.tsx rename to src/features/katalog/components/bolum-icerik.tsx index 84d0ca4..8adf09f 100644 --- a/src/app/bolum/[slug]/bolum-icerik.tsx +++ b/src/features/katalog/components/bolum-icerik.tsx @@ -2,11 +2,17 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { ChevronRight } from "lucide-react"; -import { getBolumBySlug, uniSlugFromAd } from "@/lib/katalog"; +import { + getAllBolumler, + getAllBolumSlugs, + getBolumBySlug, + uniSlugFromAd, +} from "../katalog-queries"; import { JsonLd, breadcrumbJsonLd } from "@/lib/seo"; -import { SAYFA_BOYU } from "@/components/sayfalama"; -import { BolumProgramListesi } from "@/components/bolum-program-listesi"; +import { SAYFA_BOYU } from "./sayfalama"; +import { BolumProgramListesi } from "./bolum-program-listesi"; import { SiteFooter } from "@/components/site-footer"; +import { Skeleton } from "@/components/ui/skeleton"; const sayi = (n: number) => n.toLocaleString("tr-TR"); @@ -14,6 +20,22 @@ export function bolumToplamSayfa(programSayisi: number): number { return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU)); } +// Sayfaların generateStaticParams'ı için — sayfalar *-queries import etmez. +export function bolumStaticSlugs(): string[] { + return getAllBolumSlugs(); +} + +// 2..N sayfaları; 1. sayfa taban rota (/bolum/[slug]) olduğundan üretilmez. +export function bolumSayfaStaticParams(): { slug: string; no: string }[] { + return getAllBolumler().flatMap((b) => { + const toplam = bolumToplamSayfa(b.programSayisi); + return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({ + slug: b.slug, + no: String(i + 2), + })); + }); +} + export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata { const bolum = getBolumBySlug(slug); if (!bolum) return {}; @@ -123,3 +145,21 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) { ); } + +export function BolumIcerikSkeleton() { + return ( +
+ +
+ + +
+
+ + {Array.from({ length: 5 }, (_, i) => ( + + ))} +
+
+ ); +} diff --git a/src/features/katalog/components/bolum-listesi.tsx b/src/features/katalog/components/bolum-listesi.tsx new file mode 100644 index 0000000..8da25b4 --- /dev/null +++ b/src/features/katalog/components/bolum-listesi.tsx @@ -0,0 +1,115 @@ +import Link from "next/link"; +import { getAllBolumler, type BolumOzet } from "../katalog-queries"; +import { KATEGORILER } from "@/lib/kategoriler"; +import { CtaSiraForm } from "@/components/cta-sira-form"; +import { PagePixelDivider } from "@/components/pixel-decor"; +import { Skeleton } from "@/components/ui/skeleton"; + +const sayi = (n: number) => n.toLocaleString("tr-TR"); + +function BolumLinkleri({ bolumler }: { bolumler: BolumOzet[] }) { + return ( +
    + {bolumler.map((b) => ( +
  • + + {b.ad} + + {sayi(b.programSayisi)} + + +
  • + ))} +
+ ); +} + +function KategoriGruplari({ + bolumler, + seviye, +}: { + bolumler: BolumOzet[]; + seviye: "lisans" | "onlisans"; +}) { + const kalanlar = new Set(bolumler.map((b) => b.slug)); + const gruplar = KATEGORILER.map((kategori) => { + const uyeler = bolumler.filter((b) => + (b.kategoriler as readonly string[]).includes(kategori), + ); + for (const b of uyeler) kalanlar.delete(b.slug); + return { kategori: kategori as string, uyeler }; + }).filter((g) => g.uyeler.length > 0); + const diger = bolumler.filter((b) => kalanlar.has(b.slug)); + if (diger.length > 0) gruplar.push({ kategori: "Diğer", uyeler: diger }); + + return ( + <> + {gruplar.map(({ kategori, uyeler }) => ( +
+

+ {kategori} + + {sayi(uyeler.length)} bölüm + +

+ +
+ ))} + + ); +} + +export function BolumKatalogu() { + const hepsi = getAllBolumler(); + const lisans = hepsi.filter((b) => b.lisans); + const onlisans = hepsi.filter((b) => b.onlisans && !b.lisans); + + return ( + <> +

+ {sayi(hepsi.length)} bölümün 2025 YKS yerleştirme verileri: taban + puanları, başarı sıralamaları, kontenjanlar ve devlet/vakıf dağılımı. + Bölüme tıklayınca o bölümü sunan tüm üniversitelerin tablosunu görürsün. + Veriler YÖK Atlas 2025 yerleştirme sonuçlarına dayanır; 2026 tercihleri + için rehber niteliğindedir. +

+ + + +
+

+ Lisans Bölümleri (4 Yıllık) +

+ +
+ +
+ +
+ +
+

+ Önlisans Bölümleri (2 Yıllık — TYT) +

+ +
+ + ); +} + +export function BolumKataloguSkeleton() { + return ( +
+ +
+ + {Array.from({ length: 4 }, (_, i) => ( + + ))} +
+
+ ); +} diff --git a/src/components/bolum-program-listesi.tsx b/src/features/katalog/components/bolum-program-listesi.tsx similarity index 98% rename from src/components/bolum-program-listesi.tsx rename to src/features/katalog/components/bolum-program-listesi.tsx index 0f75446..ba9fc1c 100644 --- a/src/components/bolum-program-listesi.tsx +++ b/src/features/katalog/components/bolum-program-listesi.tsx @@ -5,13 +5,13 @@ import Link from "next/link"; import { ChevronDown, ChevronUp } from "lucide-react"; import type { Program } from "@/types/yokatlas"; import { trBaslikDuzeni } from "@/lib/slug"; -import { Sayfalama, SAYFA_BOYU } from "@/components/sayfalama"; +import { Sayfalama, SAYFA_BOYU } from "./sayfalama"; import { ProgramSiraGecmisi, ProgramTabanTrendi, } from "@/components/program-liste-verileri"; import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu"; -import { ProgramNetleriButonu } from "@/components/program-netleri"; +import { ProgramNetleriButonu } from "./program-netleri"; import { UniLogo } from "@/components/uni-logo"; import { Table, diff --git a/src/components/katalog-arama.tsx b/src/features/katalog/components/katalog-arama.tsx similarity index 100% rename from src/components/katalog-arama.tsx rename to src/features/katalog/components/katalog-arama.tsx diff --git a/src/components/program-netleri.tsx b/src/features/katalog/components/program-netleri.tsx similarity index 100% rename from src/components/program-netleri.tsx rename to src/features/katalog/components/program-netleri.tsx diff --git a/src/components/sayfalama.tsx b/src/features/katalog/components/sayfalama.tsx similarity index 100% rename from src/components/sayfalama.tsx rename to src/features/katalog/components/sayfalama.tsx diff --git a/src/app/universite/[slug]/universite-icerik.tsx b/src/features/katalog/components/universite-icerik.tsx similarity index 84% rename from src/app/universite/[slug]/universite-icerik.tsx rename to src/features/katalog/components/universite-icerik.tsx index 1897afe..d52e0f5 100644 --- a/src/app/universite/[slug]/universite-icerik.tsx +++ b/src/features/katalog/components/universite-icerik.tsx @@ -2,17 +2,23 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { ChevronRight } from "lucide-react"; -import { getUniversiteBySlug, bolumSlugFromIsim } from "@/lib/katalog"; +import { + getAllUniversiteler, + getAllUniversiteSlugs, + getUniversiteBySlug, + bolumSlugFromIsim, +} from "../katalog-queries"; import { trBaslikDuzeni } from "@/lib/slug"; import { JsonLd, breadcrumbJsonLd, SITE_URL } from "@/lib/seo"; import { CtaSiraForm } from "@/components/cta-sira-form"; import { SiteFooter } from "@/components/site-footer"; -import { Sayfalama, SAYFA_BOYU } from "@/components/sayfalama"; +import { Sayfalama, SAYFA_BOYU } from "./sayfalama"; import { PagePixelDivider } from "@/components/pixel-decor"; -import { UniversiteKonumHaritasi } from "@/components/universite-konum-haritasi"; -import { UniversiteProgramTablosu } from "@/components/universite-program-tablosu"; +import { UniversiteKonumHaritasi } from "./universite-konum-haritasi"; +import { UniversiteProgramTablosu } from "./universite-program-tablosu"; import { UniLogo } from "@/components/uni-logo"; import { uniLogoYolu } from "@/lib/uni-logolar"; +import { Skeleton } from "@/components/ui/skeleton"; import type { Program } from "@/types/yokatlas"; const sayi = (n: number) => n.toLocaleString("tr-TR"); @@ -21,6 +27,22 @@ export function uniToplamSayfa(programSayisi: number): number { return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU)); } +// Sayfaların generateStaticParams'ı için — sayfalar *-queries import etmez. +export function uniStaticSlugs(): string[] { + return getAllUniversiteSlugs(); +} + +// 2..N sayfaları; 1. sayfa taban rota (/universite/[slug]) olduğundan üretilmez. +export function uniSayfaStaticParams(): { slug: string; no: string }[] { + return getAllUniversiteler().flatMap((u) => { + const toplam = uniToplamSayfa(u.programSayisi); + return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({ + slug: u.slug, + no: String(i + 2), + })); + }); +} + export function uniSayfaMetadata(slug: string, sayfa: number): Metadata { const uni = getUniversiteBySlug(slug); if (!uni) return {}; @@ -268,3 +290,27 @@ export function UniversiteIcerik({ ); } + +export function UniversiteIcerikSkeleton() { + return ( +
+ +
+ + +
+ +
+ {Array.from({ length: 5 }, (_, i) => ( + + ))} +
+
+ + {Array.from({ length: 5 }, (_, i) => ( + + ))} +
+
+ ); +} diff --git a/src/components/universite-konum-haritasi.tsx b/src/features/katalog/components/universite-konum-haritasi.tsx similarity index 100% rename from src/components/universite-konum-haritasi.tsx rename to src/features/katalog/components/universite-konum-haritasi.tsx diff --git a/src/features/katalog/components/universite-listesi.tsx b/src/features/katalog/components/universite-listesi.tsx new file mode 100644 index 0000000..8608769 --- /dev/null +++ b/src/features/katalog/components/universite-listesi.tsx @@ -0,0 +1,110 @@ +import Link from "next/link"; +import { getAllUniversiteler, type UniOzet } from "../katalog-queries"; +import { trBaslikDuzeni } from "@/lib/slug"; +import { CtaSiraForm } from "@/components/cta-sira-form"; +import { PagePixelDivider } from "@/components/pixel-decor"; +import { UniLogo } from "@/components/uni-logo"; +import { Skeleton } from "@/components/ui/skeleton"; + +const sayi = (n: number) => n.toLocaleString("tr-TR"); + +function UniLinkleri({ uniler }: { uniler: UniOzet[] }) { + return ( +
    + {uniler.map((u) => ( +
  • + + + + + {u.ad} + + + {u.il ? `${trBaslikDuzeni(u.il)} · ` : ""} + {sayi(u.programSayisi)} program + + + +
  • + ))} +
+ ); +} + +export function UniversiteKatalogu() { + const hepsi = getAllUniversiteler(); + const devlet = hepsi.filter((u) => u.unitur === "DEVLET"); + const vakif = hepsi.filter( + (u) => u.unitur != null && ["VAKIF", "VAKIF MYO"].includes(u.unitur), + ); + const digerUniler = hepsi.filter( + (u) => !devlet.includes(u) && !vakif.includes(u), + ); + + return ( + <> +

+ {sayi(hepsi.length)} üniversitenin 2025 YKS yerleştirme verileri. + Üniversiteye tıklayınca tüm programlarının taban puanlarını, başarı + sıralamalarını ve kontenjanlarını fakülte fakülte görürsün. Veriler YÖK + Atlas 2025 yerleştirme sonuçlarına dayanır; 2026 tercihleri için rehber + niteliğindedir. +

+ + + +
+

+ Devlet Üniversiteleri + + {sayi(devlet.length)} + +

+ +
+ +
+

+ Vakıf Üniversiteleri + + {sayi(vakif.length)} + +

+ +
+ + {digerUniler.length > 0 ? ( +
+

+ KKTC ve Yurt Dışı + + {sayi(digerUniler.length)} + +

+ +
+ ) : null} + +
+ +
+ + ); +} + +export function UniversiteKataloguSkeleton() { + return ( +
+ +
+ + {Array.from({ length: 4 }, (_, i) => ( + + ))} +
+
+ ); +} diff --git a/src/components/universite-program-tablosu.tsx b/src/features/katalog/components/universite-program-tablosu.tsx similarity index 98% rename from src/components/universite-program-tablosu.tsx rename to src/features/katalog/components/universite-program-tablosu.tsx index 47b3791..b6e2035 100644 --- a/src/components/universite-program-tablosu.tsx +++ b/src/features/katalog/components/universite-program-tablosu.tsx @@ -8,7 +8,7 @@ import { programEtkinSira, } from "@/components/program-liste-verileri"; import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu"; -import { ProgramNetleriButonu } from "@/components/program-netleri"; +import { ProgramNetleriButonu } from "./program-netleri"; import { Table, TableBody, diff --git a/src/features/katalog/katalog-queries.ts b/src/features/katalog/katalog-queries.ts new file mode 100644 index 0000000..fac7d6d --- /dev/null +++ b/src/features/katalog/katalog-queries.ts @@ -0,0 +1,17 @@ +import "server-only"; + +// Katalog feature'ının sorgu kapısı. Veri motoru @/lib/katalog'da yaşar +// (yokatlas.db üzerinde salt-okunur, globalThis'te önbellekli); feature +// bileşenleri ve sayfa yardımcıları yalnızca buradan okur. +export { + getAllBolumler, + getAllBolumSlugs, + getAllUniversiteler, + getAllUniversiteSlugs, + getBolumBySlug, + getUniversiteBySlug, + uniSlugFromAd, + bolumSlugFromIsim, + type BolumOzet, + type UniOzet, +} from "@/lib/katalog";