Danışman araçları, TÜİK mezun istihdamı ve Jev çıktı denetimi
- Danışman: program_netleri, program_karsilastir ve bolum_istihdam araçları; program_ara'ya devlet/vakıf, öğretim türü ve sıralama parametreleri; program_detay'a trend cümlesi; bozuk araç argümanı modele hata döner. - TÜİK Yükseköğretim İstihdam Göstergeleri 2025: bölüm bazında istihdam oranı, ilk işi bulma süresi ve kazanç grubu (src/lib/bolum-istihdam.ts, üretici scripts/tuik-istihdam-uret.py, kaynak Excel'ler data/kaynak/). - Bölüm sayfasında "Mezun istihdamı" ayrıntı satırı ve alt sayfası; AkademikAyrinti başlık/ikon alır. - Jev (TypeSafe AI) ile danışman cevabında garanti/olasılık dili denetimi; ihlalde cevabın sonuna düzeltme notu. Anahtar yoksa denetim atlanır. - /gizlilik: TypeSafe AI (ABD) sağlayıcı satırı. KARARLAR.md: 5 Ekim kaydı. - Üç katalog dosyasındaki bg-white → bg-card değişiklikleri de bu commit'te. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,9 @@ import {
|
||||
} from "@/components/ui/drawer";
|
||||
|
||||
/**
|
||||
* Üniversite sayfasında özet kartlarının altındaki "Akademik göstergeler ›"
|
||||
* satırı (Apple Sağlık'taki "Tüm verileri göster" gibi): sık bakılan sayılar
|
||||
* Üniversite ve bölüm sayfasında özet kartlarının altındaki ayrıntı satırı
|
||||
* ("Akademik göstergeler ›", "Mezun istihdamı ›"; Apple Sağlık'taki "Tüm
|
||||
* verileri göster" gibi): sık bakılan sayılar
|
||||
* kartlarda, ayrıntı bir dokunuş derinde. İkinci satır içeride ne olduğunu
|
||||
* önceden söyler. Ayrıntı alttan açılan çekmecede; çekmece ve kapatma
|
||||
* çarpısı cta-sira-form.tsx'teki çekmeceyle birebir aynı sınıf setinde.
|
||||
@@ -22,12 +23,18 @@ import {
|
||||
* AcilisSahnesi oynatır.
|
||||
*/
|
||||
export function AkademikAyrinti({
|
||||
universite,
|
||||
baglam,
|
||||
onizleme,
|
||||
baslik = "Akademik göstergeler",
|
||||
ikon,
|
||||
children,
|
||||
}: {
|
||||
universite: string;
|
||||
/** Çekmece başlığının altındaki bağlam satırı (üniversite ya da bölüm adı) */
|
||||
baglam: string;
|
||||
onizleme: string;
|
||||
baslik?: string;
|
||||
/** İkon kutusunun içi; verilmezse mezuniyet kepi */
|
||||
ikon?: ReactNode;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
@@ -35,14 +42,16 @@ export function AkademikAyrinti({
|
||||
<DrawerTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 flex w-full cursor-pointer items-center gap-4 group rounded-2xl border border-slate-200 bg-white p-4 text-left transition-[background-color,border-color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:p-5"
|
||||
className="mt-4 flex w-full cursor-pointer items-center gap-4 group rounded-2xl border border-slate-200 bg-card p-4 text-left transition-[background-color,border-color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:p-5"
|
||||
>
|
||||
<span className="flex size-11 shrink-0 items-center justify-center rounded-xl bg-primary/10">
|
||||
<GraduationCap className="size-5 text-primary" aria-hidden />
|
||||
{ikon ?? (
|
||||
<GraduationCap className="size-5 text-primary" aria-hidden />
|
||||
)}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block font-heading text-lg font-bold text-slate-900">
|
||||
Akademik göstergeler
|
||||
{baslik}
|
||||
</span>
|
||||
<span className="block truncate text-sm text-slate-500">
|
||||
{onizleme}
|
||||
@@ -54,17 +63,17 @@ export function AkademikAyrinti({
|
||||
/>
|
||||
</button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="max-h-[92dvh] bg-white sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl">
|
||||
<DrawerContent className="max-h-[92dvh] bg-card sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl">
|
||||
<DrawerClose className="absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]">
|
||||
<X className="size-4" aria-hidden />
|
||||
<span className="sr-only">Kapat</span>
|
||||
</DrawerClose>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 pt-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
|
||||
<DrawerTitle className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||
Akademik göstergeler
|
||||
{baslik}
|
||||
</DrawerTitle>
|
||||
<DrawerDescription className="mt-1 px-10 text-center text-sm text-slate-500">
|
||||
{universite}
|
||||
{baglam}
|
||||
</DrawerDescription>
|
||||
<AcilisSahnesi>{children}</AcilisSahnesi>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
Briefcase,
|
||||
Building2,
|
||||
Calculator,
|
||||
ChevronRight,
|
||||
@@ -32,7 +33,16 @@ import {
|
||||
import { BolumProgramListesi } from "./bolum-program-listesi";
|
||||
import { VeriKaynagi } from "./veri-kaynagi";
|
||||
import { NavBaslik } from "./nav-baslik";
|
||||
import { AkademikAyrinti } from "./akademik-ayrinti";
|
||||
import { OzetKart } from "@/components/ozet-kart";
|
||||
import {
|
||||
BOLUM_ISTIHDAM_KAYNAK_URL,
|
||||
BOLUM_ISTIHDAM_YIL,
|
||||
ISTIHDAM_ORTALAMA,
|
||||
bolumIstihdamBul,
|
||||
type BolumIstihdam,
|
||||
type IstihdamDuzeyi,
|
||||
} from "@/lib/bolum-istihdam";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
@@ -64,6 +74,134 @@ function kartSayisi(n: number): { deger: string; birim?: string } {
|
||||
return { deger: sayi(n) };
|
||||
}
|
||||
|
||||
const ondalik = (n: number) =>
|
||||
n.toLocaleString("tr-TR", { maximumFractionDigits: 1 });
|
||||
|
||||
/** Ayrıntı satırının ikinci satırı: içeride ne olduğunu söyler (DESIGN.md §4.9). */
|
||||
function istihdamOnizleme(v: BolumIstihdam): string {
|
||||
const parcalar = [
|
||||
v.istihdam != null
|
||||
? `Mezunların %${ondalik(v.istihdam)}'i kayıtlı çalışıyor`
|
||||
: null,
|
||||
v.isBulmaAy != null ? `ilk iş ${ondalik(v.isBulmaAy)} ayda` : null,
|
||||
v.istihdam == null && v.kazanc
|
||||
? `kazanç grubu: ${v.kazanc.toLocaleLowerCase("tr-TR")}`
|
||||
: null,
|
||||
].filter(Boolean);
|
||||
const metin = parcalar.join(" · ");
|
||||
return metin.charAt(0).toLocaleUpperCase("tr-TR") + metin.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mezun istihdamı: TÜİK Yükseköğretim İstihdam Göstergeleri (ülke geneli,
|
||||
* bölüm bazında). Sayfada değil, özet kartlarının altındaki "Mezun
|
||||
* istihdamı ›" satırının açtığı alt sayfada durur (DESIGN.md §4.9–4.11):
|
||||
* kart ve süs ayraç yok, gösterge kareleri masonry, altında tek not ve
|
||||
* kaynak. Renk sempati katar, anlam taşımaz — risk dili değildir. data-*
|
||||
* işaretleri açılış koreografisinin sözleşmesi (akademik-ayrinti.tsx).
|
||||
*/
|
||||
function MezunIstihdami({
|
||||
veri,
|
||||
duzey,
|
||||
}: {
|
||||
veri: BolumIstihdam;
|
||||
duzey: IstihdamDuzeyi;
|
||||
}) {
|
||||
const ort = ISTIHDAM_ORTALAMA[duzey];
|
||||
const kare =
|
||||
"flex flex-col items-center justify-center rounded-3xl px-3 text-white";
|
||||
const etiket = "text-sm font-bold text-white/90";
|
||||
const devSayi =
|
||||
"font-heading text-5xl font-bold leading-none tracking-tight";
|
||||
const orta = "font-heading text-3xl font-bold leading-none";
|
||||
|
||||
const istihdamKaresi =
|
||||
veri.istihdam != null ? (
|
||||
<div data-kare="sol" className={`${kare} min-h-40 bg-orange-500 py-4`}>
|
||||
<dt className={etiket}>Kayıtlı çalışan</dt>
|
||||
<dd className={`mt-3 ${devSayi}`}>%{ondalik(veri.istihdam)}</dd>
|
||||
{ort.istihdam != null ? (
|
||||
<dd className="mt-2 text-base font-bold text-white/90">
|
||||
Türkiye: %{ondalik(ort.istihdam)}
|
||||
</dd>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
const kazancKaresi = veri.kazanc ? (
|
||||
<div data-kare="sol" className={`${kare} min-h-24 bg-indigo-500 py-3`}>
|
||||
<dd className={orta}>{veri.kazanc}</dd>
|
||||
<dt className={`mt-1.5 ${etiket}`}>Kazanç grubu</dt>
|
||||
</div>
|
||||
) : null;
|
||||
const sureKaresi =
|
||||
veri.isBulmaAy != null ? (
|
||||
<div data-kare="sag-ust" className={`${kare} min-h-40 bg-primary py-4`}>
|
||||
<dt className={etiket}>İlk işi bulma</dt>
|
||||
<dd className="mt-3 flex items-baseline justify-center gap-1">
|
||||
<span className={devSayi}>{ondalik(veri.isBulmaAy)}</span>
|
||||
<span className="text-base font-bold text-white/80">ay</span>
|
||||
</dd>
|
||||
{ort.isBulmaAy != null ? (
|
||||
<dd className="mt-2 text-base font-bold text-white/90">
|
||||
Türkiye: {ondalik(ort.isBulmaAy)} ay
|
||||
</dd>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
const solVar = istihdamKaresi != null || kazancKaresi != null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<dl
|
||||
data-sahne
|
||||
className="relative flex w-full max-w-md items-start gap-3"
|
||||
>
|
||||
{solVar ? (
|
||||
<div className="flex flex-1 flex-col gap-3">
|
||||
{istihdamKaresi}
|
||||
{kazancKaresi}
|
||||
</div>
|
||||
) : null}
|
||||
{sureKaresi ? (
|
||||
<div
|
||||
className={`flex flex-1 flex-col gap-3 ${solVar ? "mt-12" : ""}`}
|
||||
>
|
||||
{sureKaresi}
|
||||
</div>
|
||||
) : null}
|
||||
</dl>
|
||||
|
||||
<p
|
||||
data-sonra
|
||||
className="mt-4 max-w-md text-sm leading-relaxed text-slate-500"
|
||||
>
|
||||
Türkiye geneli, tüm üniversitelerin{" "}
|
||||
{duzey === "lisans" ? "lisans" : "ön lisans"} mezunları birlikte;
|
||||
üniversiteye göre değişir. Kayıtlı çalışan, sigortalı bir işte
|
||||
çalışanların oranıdır. Kazanç grubu bölümler arası kıyastır, maaş
|
||||
değildir.
|
||||
</p>
|
||||
|
||||
<div
|
||||
data-sonra
|
||||
className="mt-5 w-full max-w-md border-t border-slate-100 pt-4 text-xs leading-5 text-slate-500"
|
||||
>
|
||||
<p>
|
||||
TÜİK, Yükseköğretim İstihdam Göstergeleri {BOLUM_ISTIHDAM_YIL} ·{" "}
|
||||
<a
|
||||
href={BOLUM_ISTIHDAM_KAYNAK_URL}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="font-medium text-primary transition-colors hover:text-orange-500 hover:underline"
|
||||
>
|
||||
veriportali.tuik.gov.tr
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Lisans puan türünün kimlik hapındaki adı (DB: SAYISAL, EŞİT AĞIRLIK…). */
|
||||
const PUAN_TURU_HAP: Record<string, string> = {
|
||||
SAYISAL: "Sayısal puan",
|
||||
@@ -295,13 +433,18 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
}
|
||||
}
|
||||
const { stats } = bolum;
|
||||
// TÜİK mezun istihdamı: lisansı olan bölümde lisans tablosu, yalnız ön
|
||||
// lisansı olanda ön lisans tablosu. Tabloda yoksa satır hiç çizilmez.
|
||||
const istihdamDuzeyi: IstihdamDuzeyi =
|
||||
bolum.lisansProgramlari.length > 0 ? "lisans" : "onlisans";
|
||||
const istihdam = bolumIstihdamBul(slug, istihdamDuzeyi);
|
||||
// Sayfaya özgü, veriden türeyen özet cümle (GEO: yapay zekâ cevap motorları
|
||||
// sayfa başına ilk metni saklar; 1011 katalog sayfasının aynı şablon cümleyle
|
||||
// açılması hem alıntıyı hem Google'ın "ölçekli içerik" politikasını zorlar —
|
||||
// docs/arastirma/2026-09-24-geo-seo). Rakamlar tablodakiyle aynı kaynaktan.
|
||||
const veriOzeti = ilkSayfa ? bolumVeriOzeti(bolum, tumProgramlar) : null;
|
||||
const hapSinifi =
|
||||
"inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800";
|
||||
"inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800";
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -460,6 +603,16 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
/>
|
||||
</dl>
|
||||
) : null}
|
||||
{ilkSayfa && istihdam ? (
|
||||
<AkademikAyrinti
|
||||
baglam={bolum.ad}
|
||||
baslik="Mezun istihdamı"
|
||||
onizleme={istihdamOnizleme(istihdam)}
|
||||
ikon={<Briefcase className="size-5 text-primary" aria-hidden />}
|
||||
>
|
||||
<MezunIstihdami veri={istihdam} duzey={istihdamDuzeyi} />
|
||||
</AkademikAyrinti>
|
||||
) : null}
|
||||
|
||||
<PagePixelDivider seed={83} className="mx-auto mt-12 lg:mx-0" />
|
||||
|
||||
|
||||
@@ -605,13 +605,13 @@ export function UniversiteIcerik({
|
||||
<Link
|
||||
href={href}
|
||||
title={`Tüm ${turEtiketi.cip.toLocaleLowerCase("tr-TR")} üniversiteleri`}
|
||||
className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]"
|
||||
className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]"
|
||||
>
|
||||
{ikon}
|
||||
{turEtiketi.cip}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800">
|
||||
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800">
|
||||
{ikon}
|
||||
{turEtiketi.cip}
|
||||
</span>
|
||||
@@ -624,7 +624,7 @@ export function UniversiteIcerik({
|
||||
<Link
|
||||
href={ilSayfaHref(uni.il)}
|
||||
title={`${trBaslikDuzeni(uni.il)} üniversiteleri`}
|
||||
className="inline-flex h-10 min-w-0 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]"
|
||||
className="inline-flex h-10 min-w-0 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]"
|
||||
>
|
||||
<MapPin className="size-4 shrink-0 text-primary" aria-hidden />
|
||||
<span className="truncate">{trBaslikDuzeni(uni.il)}</span>
|
||||
@@ -710,7 +710,7 @@ export function UniversiteIcerik({
|
||||
</div>
|
||||
{uni.urap || uni.istatistik ? (
|
||||
<AkademikAyrinti
|
||||
universite={uni.ad}
|
||||
baglam={uni.ad}
|
||||
onizleme={akademikOnizleme(uni)}
|
||||
>
|
||||
<AkademikGostergeler uni={uni} />
|
||||
|
||||
Reference in New Issue
Block a user