refactor: katalog feature'ı — sayfalar senkron kompozisyona geçti (Faz 2)
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,28 +1,32 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { getAllBolumSlugs } from "@/lib/katalog";
|
import {
|
||||||
import { BolumIcerik, bolumSayfaMetadata } from "./bolum-icerik";
|
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
|
// 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.
|
// değişir); bilinmeyen slug getBolumBySlug'dan null dönüp notFound()'a düşer.
|
||||||
// (dynamicParams cacheComponents ile uyumsuz olduğundan kullanılamıyor.)
|
// (dynamicParams cacheComponents ile uyumsuz olduğundan kullanılamıyor.)
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return getAllBolumSlugs().map((slug) => ({ slug }));
|
return bolumStaticSlugs().map((slug) => ({ slug }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: PageProps<"/bolum/[slug]">): Promise<Metadata> {
|
||||||
params: Promise<{ slug: string }>;
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
return bolumSayfaMetadata(slug, 1);
|
return bolumSayfaMetadata(slug, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function BolumPage({
|
export default function BolumPage({ params }: PageProps<"/bolum/[slug]">) {
|
||||||
params,
|
return (
|
||||||
}: {
|
<Suspense fallback={<BolumIcerikSkeleton />}>
|
||||||
params: Promise<{ slug: string }>;
|
{params.then(({ slug }) => (
|
||||||
}) {
|
<BolumIcerik slug={slug} sayfa={1} />
|
||||||
const { slug } = await params;
|
))}
|
||||||
return <BolumIcerik slug={slug} sayfa={1} />;
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getAllBolumler } from "@/lib/katalog";
|
|
||||||
import {
|
import {
|
||||||
BolumIcerik,
|
BolumIcerik,
|
||||||
|
BolumIcerikSkeleton,
|
||||||
bolumSayfaMetadata,
|
bolumSayfaMetadata,
|
||||||
bolumToplamSayfa,
|
bolumSayfaStaticParams,
|
||||||
} from "../../bolum-icerik";
|
} from "@/features/katalog/components/bolum-icerik";
|
||||||
|
|
||||||
// Yalnızca 1'den fazla sayfası olan bölümler için 2..N sayfaları üretilir;
|
// 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.
|
// 1. sayfa taban rota (/bolum/[slug]) olduğundan burada üretilmez.
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return getAllBolumler().flatMap((b) => {
|
return bolumSayfaStaticParams();
|
||||||
const toplam = bolumToplamSayfa(b.programSayisi);
|
|
||||||
return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({
|
|
||||||
slug: b.slug,
|
|
||||||
no: String(i + 2),
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sayfaNo(no: string): number | null {
|
function sayfaNo(no: string): number | null {
|
||||||
@@ -26,22 +21,23 @@ function sayfaNo(no: string): number | null {
|
|||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: PageProps<"/bolum/[slug]/sayfa/[no]">): Promise<Metadata> {
|
||||||
params: Promise<{ slug: string; no: string }>;
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const { slug, no } = await params;
|
const { slug, no } = await params;
|
||||||
const n = sayfaNo(no);
|
const n = sayfaNo(no);
|
||||||
if (n == null) return {};
|
if (n == null) return {};
|
||||||
return bolumSayfaMetadata(slug, n);
|
return bolumSayfaMetadata(slug, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function BolumSayfaPage({
|
export default function BolumSayfaPage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: PageProps<"/bolum/[slug]/sayfa/[no]">) {
|
||||||
params: Promise<{ slug: string; no: string }>;
|
return (
|
||||||
}) {
|
<Suspense fallback={<BolumIcerikSkeleton />}>
|
||||||
const { slug, no } = await params;
|
{params.then(({ slug, no }) => {
|
||||||
const n = sayfaNo(no);
|
const n = sayfaNo(no);
|
||||||
if (n == null) notFound();
|
if (n == null) notFound();
|
||||||
return <BolumIcerik slug={slug} sayfa={n} />;
|
return <BolumIcerik slug={slug} sayfa={n} />;
|
||||||
|
})}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
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 { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||||
import { CtaSiraForm } from "@/components/cta-sira-form";
|
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
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 = {
|
export const metadata: Metadata = {
|
||||||
title: "Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026",
|
title: "Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026",
|
||||||
@@ -14,68 +14,7 @@ export const metadata: Metadata = {
|
|||||||
alternates: { canonical: "/bolumler" },
|
alternates: { canonical: "/bolumler" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
|
||||||
|
|
||||||
function BolumLinkleri({ bolumler }: { bolumler: BolumOzet[] }) {
|
|
||||||
return (
|
|
||||||
<ul className="mt-3 grid gap-x-6 gap-y-1.5 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{bolumler.map((b) => (
|
|
||||||
<li key={b.slug}>
|
|
||||||
<Link
|
|
||||||
href={`/bolum/${b.slug}`}
|
|
||||||
className="group inline-flex items-baseline gap-2 text-sm text-slate-700 hover:text-primary"
|
|
||||||
>
|
|
||||||
<span className="group-hover:underline">{b.ad}</span>
|
|
||||||
<span className="text-xs text-slate-400">
|
|
||||||
{sayi(b.programSayisi)}
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }) => (
|
|
||||||
<section key={`${seviye}-${kategori}`} className="mt-8">
|
|
||||||
<h3 className="font-heading text-lg font-bold text-slate-900">
|
|
||||||
{kategori}
|
|
||||||
<span className="ml-2 text-xs font-normal text-slate-400">
|
|
||||||
{sayi(uyeler.length)} bölüm
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
<BolumLinkleri bolumler={uyeler} />
|
|
||||||
</section>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BolumlerPage() {
|
export default function BolumlerPage() {
|
||||||
const hepsi = getAllBolumler();
|
|
||||||
const lisans = hepsi.filter((b) => b.lisans);
|
|
||||||
const onlisans = hepsi.filter((b) => b.onlisans && !b.lisans);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
||||||
@@ -89,33 +28,10 @@ export default function BolumlerPage() {
|
|||||||
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
||||||
Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026
|
Tüm Bölümlerin Taban Puanları ve Sıralamaları 2026
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
|
||||||
{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.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<PagePixelDivider seed={29} className="mt-8" />
|
<Suspense fallback={<BolumKataloguSkeleton />}>
|
||||||
|
<BolumKatalogu />
|
||||||
<section className="mt-8">
|
</Suspense>
|
||||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
|
||||||
Lisans Bölümleri (4 Yıllık)
|
|
||||||
</h2>
|
|
||||||
<KategoriGruplari bolumler={lisans} seviye="lisans" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="mt-12">
|
|
||||||
<CtaSiraForm />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section className="mt-12">
|
|
||||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
|
||||||
Önlisans Bölümleri (2 Yıllık — TYT)
|
|
||||||
</h2>
|
|
||||||
<KategoriGruplari bolumler={onlisans} seviye="onlisans" />
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { getAllUniversiteSlugs } from "@/lib/katalog";
|
import {
|
||||||
import { UniversiteIcerik, uniSayfaMetadata } from "./universite-icerik";
|
UniversiteIcerik,
|
||||||
|
UniversiteIcerikSkeleton,
|
||||||
|
uniSayfaMetadata,
|
||||||
|
uniStaticSlugs,
|
||||||
|
} from "@/features/katalog/components/universite-icerik";
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return getAllUniversiteSlugs().map((slug) => ({ slug }));
|
return uniStaticSlugs().map((slug) => ({ slug }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: PageProps<"/universite/[slug]">): Promise<Metadata> {
|
||||||
params: Promise<{ slug: string }>;
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
return uniSayfaMetadata(slug, 1);
|
return uniSayfaMetadata(slug, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function UniversitePage({
|
export default function UniversitePage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: PageProps<"/universite/[slug]">) {
|
||||||
params: Promise<{ slug: string }>;
|
return (
|
||||||
}) {
|
<Suspense fallback={<UniversiteIcerikSkeleton />}>
|
||||||
const { slug } = await params;
|
{params.then(({ slug }) => (
|
||||||
return <UniversiteIcerik slug={slug} sayfa={1} />;
|
<UniversiteIcerik slug={slug} sayfa={1} />
|
||||||
|
))}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getAllUniversiteler } from "@/lib/katalog";
|
|
||||||
import {
|
import {
|
||||||
UniversiteIcerik,
|
UniversiteIcerik,
|
||||||
|
UniversiteIcerikSkeleton,
|
||||||
uniSayfaMetadata,
|
uniSayfaMetadata,
|
||||||
uniToplamSayfa,
|
uniSayfaStaticParams,
|
||||||
} from "../../universite-icerik";
|
} from "@/features/katalog/components/universite-icerik";
|
||||||
|
|
||||||
// Yalnızca 1'den fazla sayfası olan üniversiteler için 2..N üretilir;
|
// Yalnızca 1'den fazla sayfası olan üniversiteler için 2..N üretilir;
|
||||||
// 1. sayfa taban rota (/universite/[slug]) olduğundan burada üretilmez.
|
// 1. sayfa taban rota (/universite/[slug]) olduğundan burada üretilmez.
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return getAllUniversiteler().flatMap((u) => {
|
return uniSayfaStaticParams();
|
||||||
const toplam = uniToplamSayfa(u.programSayisi);
|
|
||||||
return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({
|
|
||||||
slug: u.slug,
|
|
||||||
no: String(i + 2),
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sayfaNo(no: string): number | null {
|
function sayfaNo(no: string): number | null {
|
||||||
@@ -26,22 +21,23 @@ function sayfaNo(no: string): number | null {
|
|||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: PageProps<"/universite/[slug]/sayfa/[no]">): Promise<Metadata> {
|
||||||
params: Promise<{ slug: string; no: string }>;
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const { slug, no } = await params;
|
const { slug, no } = await params;
|
||||||
const n = sayfaNo(no);
|
const n = sayfaNo(no);
|
||||||
if (n == null) return {};
|
if (n == null) return {};
|
||||||
return uniSayfaMetadata(slug, n);
|
return uniSayfaMetadata(slug, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function UniversiteSayfaPage({
|
export default function UniversiteSayfaPage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: PageProps<"/universite/[slug]/sayfa/[no]">) {
|
||||||
params: Promise<{ slug: string; no: string }>;
|
return (
|
||||||
}) {
|
<Suspense fallback={<UniversiteIcerikSkeleton />}>
|
||||||
const { slug, no } = await params;
|
{params.then(({ slug, no }) => {
|
||||||
const n = sayfaNo(no);
|
const n = sayfaNo(no);
|
||||||
if (n == null) notFound();
|
if (n == null) notFound();
|
||||||
return <UniversiteIcerik slug={slug} sayfa={n} />;
|
return <UniversiteIcerik slug={slug} sayfa={n} />;
|
||||||
|
})}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
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 { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||||
import { CtaSiraForm } from "@/components/cta-sira-form";
|
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
import {
|
||||||
import { UniLogo } from "@/components/uni-logo";
|
UniversiteKatalogu,
|
||||||
|
UniversiteKataloguSkeleton,
|
||||||
|
} from "@/features/katalog/components/universite-listesi";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Üniversitelerin Taban Puanları ve Sıralamaları 2026",
|
title: "Üniversitelerin Taban Puanları ve Sıralamaları 2026",
|
||||||
@@ -15,44 +14,7 @@ export const metadata: Metadata = {
|
|||||||
alternates: { canonical: "/universiteler" },
|
alternates: { canonical: "/universiteler" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
|
||||||
|
|
||||||
function UniLinkleri({ uniler }: { uniler: UniOzet[] }) {
|
|
||||||
return (
|
|
||||||
<ul className="mt-4 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
|
||||||
{uniler.map((u) => (
|
|
||||||
<li key={u.slug}>
|
|
||||||
<Link
|
|
||||||
href={`/universite/${u.slug}`}
|
|
||||||
className="group flex items-center gap-3 text-sm text-slate-700 hover:text-primary"
|
|
||||||
>
|
|
||||||
<UniLogo slug={u.slug} ad={u.ad} boy="sm" />
|
|
||||||
<span className="min-w-0">
|
|
||||||
<span className="block truncate group-hover:underline">
|
|
||||||
{u.ad}
|
|
||||||
</span>
|
|
||||||
<span className="block text-xs text-slate-400">
|
|
||||||
{u.il ? `${trBaslikDuzeni(u.il)} · ` : ""}
|
|
||||||
{sayi(u.programSayisi)} program
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function UniversitelerPage() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
||||||
@@ -66,51 +28,10 @@ export default function UniversitelerPage() {
|
|||||||
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
||||||
Üniversitelerin Taban Puanları ve Sıralamaları 2026
|
Üniversitelerin Taban Puanları ve Sıralamaları 2026
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
|
||||||
{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.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<PagePixelDivider seed={63} className="mt-8" />
|
<Suspense fallback={<UniversiteKataloguSkeleton />}>
|
||||||
|
<UniversiteKatalogu />
|
||||||
<section className="mt-8">
|
</Suspense>
|
||||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
|
||||||
Devlet Üniversiteleri
|
|
||||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
|
||||||
{sayi(devlet.length)}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<UniLinkleri uniler={devlet} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="mt-10">
|
|
||||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
|
||||||
Vakıf Üniversiteleri
|
|
||||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
|
||||||
{sayi(vakif.length)}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<UniLinkleri uniler={vakif} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{digerUniler.length > 0 ? (
|
|
||||||
<section className="mt-10">
|
|
||||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
|
||||||
KKTC ve Yurt Dışı
|
|
||||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
|
||||||
{sayi(digerUniler.length)}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<UniLinkleri uniler={digerUniler} />
|
|
||||||
</section>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="mt-12">
|
|
||||||
<CtaSiraForm />
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu";
|
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";
|
import { UniLogo } from "@/components/uni-logo";
|
||||||
|
|
||||||
const DILIMLER: {
|
const DILIMLER: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
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 { UserNav, UserNavFallback } from "@/components/user-nav";
|
||||||
import { getAllRehberler } from "@/lib/rehber";
|
import { getAllRehberler } from "@/lib/rehber";
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ import type { Metadata } from "next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { ChevronRight } from "lucide-react";
|
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 { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||||
import { SAYFA_BOYU } from "@/components/sayfalama";
|
import { SAYFA_BOYU } from "./sayfalama";
|
||||||
import { BolumProgramListesi } from "@/components/bolum-program-listesi";
|
import { BolumProgramListesi } from "./bolum-program-listesi";
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
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));
|
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 {
|
export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
|
||||||
const bolum = getBolumBySlug(slug);
|
const bolum = getBolumBySlug(slug);
|
||||||
if (!bolum) return {};
|
if (!bolum) return {};
|
||||||
@@ -123,3 +145,21 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function BolumIcerikSkeleton() {
|
||||||
|
return (
|
||||||
|
<main className="mx-auto w-full max-w-5xl px-4 py-10 sm:py-14">
|
||||||
|
<Skeleton className="h-5 w-56" />
|
||||||
|
<div className="border-b pb-7 sm:pb-9">
|
||||||
|
<Skeleton className="mt-5 h-10 w-2/3 max-w-xl" />
|
||||||
|
<Skeleton className="mt-3 h-5 w-full max-w-2xl" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 space-y-3">
|
||||||
|
<Skeleton className="h-7 w-64" />
|
||||||
|
{Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<Skeleton key={i} className="h-12 w-full" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
115
src/features/katalog/components/bolum-listesi.tsx
Normal file
115
src/features/katalog/components/bolum-listesi.tsx
Normal file
@@ -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 (
|
||||||
|
<ul className="mt-3 grid gap-x-6 gap-y-1.5 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{bolumler.map((b) => (
|
||||||
|
<li key={b.slug}>
|
||||||
|
<Link
|
||||||
|
href={`/bolum/${b.slug}`}
|
||||||
|
className="group inline-flex items-baseline gap-2 text-sm text-slate-700 hover:text-primary"
|
||||||
|
>
|
||||||
|
<span className="group-hover:underline">{b.ad}</span>
|
||||||
|
<span className="text-xs text-slate-400">
|
||||||
|
{sayi(b.programSayisi)}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }) => (
|
||||||
|
<section key={`${seviye}-${kategori}`} className="mt-8">
|
||||||
|
<h3 className="font-heading text-lg font-bold text-slate-900">
|
||||||
|
{kategori}
|
||||||
|
<span className="ml-2 text-xs font-normal text-slate-400">
|
||||||
|
{sayi(uyeler.length)} bölüm
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<BolumLinkleri bolumler={uyeler} />
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BolumKatalogu() {
|
||||||
|
const hepsi = getAllBolumler();
|
||||||
|
const lisans = hepsi.filter((b) => b.lisans);
|
||||||
|
const onlisans = hepsi.filter((b) => b.onlisans && !b.lisans);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
||||||
|
{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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<PagePixelDivider seed={29} className="mt-8" />
|
||||||
|
|
||||||
|
<section className="mt-8">
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
Lisans Bölümleri (4 Yıllık)
|
||||||
|
</h2>
|
||||||
|
<KategoriGruplari bolumler={lisans} seviye="lisans" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="mt-12">
|
||||||
|
<CtaSiraForm />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section className="mt-12">
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
Önlisans Bölümleri (2 Yıllık — TYT)
|
||||||
|
</h2>
|
||||||
|
<KategoriGruplari bolumler={onlisans} seviye="onlisans" />
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BolumKataloguSkeleton() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Skeleton className="mt-4 h-5 w-full max-w-3xl" />
|
||||||
|
<div className="mt-8 space-y-4">
|
||||||
|
<Skeleton className="h-8 w-72" />
|
||||||
|
{Array.from({ length: 4 }, (_, i) => (
|
||||||
|
<Skeleton key={i} className="h-6 w-full max-w-2xl" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,13 +5,13 @@ import Link from "next/link";
|
|||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
import { trBaslikDuzeni } from "@/lib/slug";
|
import { trBaslikDuzeni } from "@/lib/slug";
|
||||||
import { Sayfalama, SAYFA_BOYU } from "@/components/sayfalama";
|
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
|
||||||
import {
|
import {
|
||||||
ProgramSiraGecmisi,
|
ProgramSiraGecmisi,
|
||||||
ProgramTabanTrendi,
|
ProgramTabanTrendi,
|
||||||
} from "@/components/program-liste-verileri";
|
} from "@/components/program-liste-verileri";
|
||||||
import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu";
|
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 { UniLogo } from "@/components/uni-logo";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -2,17 +2,23 @@ import type { Metadata } from "next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { ChevronRight } from "lucide-react";
|
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 { trBaslikDuzeni } from "@/lib/slug";
|
||||||
import { JsonLd, breadcrumbJsonLd, SITE_URL } from "@/lib/seo";
|
import { JsonLd, breadcrumbJsonLd, SITE_URL } from "@/lib/seo";
|
||||||
import { CtaSiraForm } from "@/components/cta-sira-form";
|
import { CtaSiraForm } from "@/components/cta-sira-form";
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
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 { PagePixelDivider } from "@/components/pixel-decor";
|
||||||
import { UniversiteKonumHaritasi } from "@/components/universite-konum-haritasi";
|
import { UniversiteKonumHaritasi } from "./universite-konum-haritasi";
|
||||||
import { UniversiteProgramTablosu } from "@/components/universite-program-tablosu";
|
import { UniversiteProgramTablosu } from "./universite-program-tablosu";
|
||||||
import { UniLogo } from "@/components/uni-logo";
|
import { UniLogo } from "@/components/uni-logo";
|
||||||
import { uniLogoYolu } from "@/lib/uni-logolar";
|
import { uniLogoYolu } from "@/lib/uni-logolar";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
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));
|
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 {
|
export function uniSayfaMetadata(slug: string, sayfa: number): Metadata {
|
||||||
const uni = getUniversiteBySlug(slug);
|
const uni = getUniversiteBySlug(slug);
|
||||||
if (!uni) return {};
|
if (!uni) return {};
|
||||||
@@ -268,3 +290,27 @@ export function UniversiteIcerik({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function UniversiteIcerikSkeleton() {
|
||||||
|
return (
|
||||||
|
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
||||||
|
<Skeleton className="h-4 w-64" />
|
||||||
|
<div className="mt-4 flex items-center gap-4">
|
||||||
|
<Skeleton className="size-14 rounded-lg" />
|
||||||
|
<Skeleton className="h-10 w-2/3 max-w-xl" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="mt-4 h-5 w-full max-w-3xl" />
|
||||||
|
<div className="mt-6 grid grid-cols-2 gap-3 lg:grid-cols-[1fr_1fr_1.35fr] lg:grid-rows-2">
|
||||||
|
{Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<Skeleton key={i} className="h-24 w-full rounded-xl" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-16 space-y-3">
|
||||||
|
<Skeleton className="h-7 w-64" />
|
||||||
|
{Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<Skeleton key={i} className="h-12 w-full" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
src/features/katalog/components/universite-listesi.tsx
Normal file
110
src/features/katalog/components/universite-listesi.tsx
Normal file
@@ -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 (
|
||||||
|
<ul className="mt-4 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
||||||
|
{uniler.map((u) => (
|
||||||
|
<li key={u.slug}>
|
||||||
|
<Link
|
||||||
|
href={`/universite/${u.slug}`}
|
||||||
|
className="group flex items-center gap-3 text-sm text-slate-700 hover:text-primary"
|
||||||
|
>
|
||||||
|
<UniLogo slug={u.slug} ad={u.ad} boy="sm" />
|
||||||
|
<span className="min-w-0">
|
||||||
|
<span className="block truncate group-hover:underline">
|
||||||
|
{u.ad}
|
||||||
|
</span>
|
||||||
|
<span className="block text-xs text-slate-400">
|
||||||
|
{u.il ? `${trBaslikDuzeni(u.il)} · ` : ""}
|
||||||
|
{sayi(u.programSayisi)} program
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
||||||
|
{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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<PagePixelDivider seed={63} className="mt-8" />
|
||||||
|
|
||||||
|
<section className="mt-8">
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
Devlet Üniversiteleri
|
||||||
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
|
{sayi(devlet.length)}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<UniLinkleri uniler={devlet} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-10">
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
Vakıf Üniversiteleri
|
||||||
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
|
{sayi(vakif.length)}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<UniLinkleri uniler={vakif} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{digerUniler.length > 0 ? (
|
||||||
|
<section className="mt-10">
|
||||||
|
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||||
|
KKTC ve Yurt Dışı
|
||||||
|
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||||
|
{sayi(digerUniler.length)}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<UniLinkleri uniler={digerUniler} />
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="mt-12">
|
||||||
|
<CtaSiraForm />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UniversiteKataloguSkeleton() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Skeleton className="mt-4 h-5 w-full max-w-3xl" />
|
||||||
|
<div className="mt-8 space-y-4">
|
||||||
|
<Skeleton className="h-8 w-72" />
|
||||||
|
{Array.from({ length: 4 }, (_, i) => (
|
||||||
|
<Skeleton key={i} className="h-9 w-full max-w-2xl" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
programEtkinSira,
|
programEtkinSira,
|
||||||
} from "@/components/program-liste-verileri";
|
} from "@/components/program-liste-verileri";
|
||||||
import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu";
|
import { ProgramEkleButonu } from "@/components/manuel-liste/program-ekle-butonu";
|
||||||
import { ProgramNetleriButonu } from "@/components/program-netleri";
|
import { ProgramNetleriButonu } from "./program-netleri";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
17
src/features/katalog/katalog-queries.ts
Normal file
17
src/features/katalog/katalog-queries.ts
Normal file
@@ -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";
|
||||||
Reference in New Issue
Block a user