feat(ui): 11 Ekim gece işi — program ayrıntıda açılan gösterge kareleri, veri akışı sahnesi, haritada kenar eritme
- program-ayrinti: kontenjan türleri, netler ve mezun istihdamı kapalıyken gösterge karesi; dokununca kare panele dönüşüp bölümün tamamını açar. Kontenjan türü satırlarına "kime açık" alt satırı (KONTENJAN_TURU_KIME). - veri-akisi: üstte öğrencinin üç seçimi, altta YÖK Atlas'ın üç verisi, ortada DanışmanlaKonuş çekirdeği; kayıtlar kıvrımlı yollardan çekirdeğe akar (kt-akis-* kuralları globals.css'te). - tercih-haritasi: il'e yakınlaşınca kenarlar iki katlı mask'la erir; tam görünümde mask yok, ülke uçları solmaz. - DESIGN.md: ilgili bölümler güncellendi (§4.19, §4.28). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,9 @@
|
||||
// türleri, son yerleşenin netleri, yerleşme koşulları, TÜİK mezun istihdamı,
|
||||
// üniversite özeti. API'nin `gecmis` (yıllara göre sıra/puan/kontenjan)
|
||||
// alanı burada gösterilmez (Bilal, 7 Eki 2026: yıl tablosu kaldırıldı;
|
||||
// backlog). Açılış: veri ilk gelince logo kareleri belirir, söner, içerik
|
||||
// backlog). Kontenjan türleri, netler ve mezun istihdamı kapalıyken gösterge
|
||||
// karesidir; dokununca kare panele dönüşüp bölümün tamamını açar
|
||||
// (BolumKutulari; Bilal, 11 Eki 2026). Açılış: veri ilk gelince logo kareleri belirir, söner, içerik
|
||||
// bloklar hâlinde netleşir (AcilisSahnesi kartsız modu); veri önbellekteyken
|
||||
// (çekmece yeniden açıldı) sahne yok, içerik 0,25 s solmayla gelir.
|
||||
// Kadın-erkek dağılımı program düzeyinde bilinçli olarak yok: YÖK Atlas 2026
|
||||
@@ -18,7 +20,8 @@
|
||||
// değiştiği için takılmış görünüyordu (7 Eki 2026).
|
||||
|
||||
import { SATIR_BASLIK, SATIR_DUGME_BOYU } from "@/components/liste-satiri";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { LayoutGroup, MotionConfig, motion } from "motion/react";
|
||||
import {
|
||||
BadgeCheck,
|
||||
Clock,
|
||||
@@ -27,17 +30,27 @@ import {
|
||||
Info,
|
||||
Languages,
|
||||
MapPin,
|
||||
Maximize2,
|
||||
Monitor,
|
||||
Users,
|
||||
Wallet,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import type { ProgramAyrintisi } from "@/app/api/program-ayrinti/route";
|
||||
import type { KontenjanTuruAnahtari, ProgramNetSatiri } from "@/types/yokatlas";
|
||||
import type {
|
||||
KontenjanTuruAnahtari,
|
||||
KontenjanTurleri,
|
||||
ProgramNetSatiri,
|
||||
} from "@/types/yokatlas";
|
||||
import { olay } from "@/lib/analitik";
|
||||
import { SURE, YAY } from "@/lib/hareket";
|
||||
// Yalnız saf modüller: bu bileşen istemcide çalışır, db'ye dokunan
|
||||
// program-ozellik.ts import EDİLMEZ (kadro toplamı API'den gelir)
|
||||
import { CINSIYET_KOSULU_METNI, KONTENJAN_TURU_ADI } from "@/lib/ozel-durum";
|
||||
import {
|
||||
CINSIYET_KOSULU_METNI,
|
||||
KONTENJAN_TURU_ADI,
|
||||
KONTENJAN_TURU_KIME,
|
||||
} from "@/lib/ozel-durum";
|
||||
import { trBaslikDuzeni } from "@/lib/slug";
|
||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -252,18 +265,8 @@ function AyrintiIcerik({ durum, veri }: { durum: Durum; veri: ProgramAyrintisi |
|
||||
))}
|
||||
</div>
|
||||
<Kunye veri={veri} />
|
||||
<KontenjanTurleriBolumu veri={veri} />
|
||||
<div data-sonra>
|
||||
<h3 className={BOLUM_BASLIK}>Son yerleşenin netleri</h3>
|
||||
<p className={NOT}>
|
||||
Bölüme en düşük puanla yerleşen adayın sınav netleri (YÖK Atlas).
|
||||
</p>
|
||||
<div className="mt-3">
|
||||
<NetlerIcerik netler={veri.netler} />
|
||||
</div>
|
||||
</div>
|
||||
<BolumKutulari veri={veri} />
|
||||
<Kosullar veri={veri} />
|
||||
<Istihdam veri={veri} />
|
||||
<UniversiteOzeti veri={veri} />
|
||||
<p data-sonra className="mt-7 border-t border-slate-100 pt-3 text-xs leading-5 text-slate-500">
|
||||
Kaynaklar: YÖK Atlas (yerleştirme, kontenjan, netler, koşullar; {SON_YIL}{" "}
|
||||
@@ -316,35 +319,364 @@ function Kunye({ veri }: { veri: ProgramAyrintisi }) {
|
||||
);
|
||||
}
|
||||
|
||||
function KontenjanTurleriBolumu({ veri }: { veri: ProgramAyrintisi }) {
|
||||
const k = veri.kontenjanTurleri;
|
||||
if (!k) return null;
|
||||
const satirlar = KONTENJAN_TURU_SIRASI.filter((t) => (k[t].kontenjan ?? 0) > 0);
|
||||
if (satirlar.length === 0) return null;
|
||||
const ozelVar = satirlar.some((t) => t !== "genel");
|
||||
/** "2 yerleşti · 1 boş kaldı" — yerleşen kontenjanı aşabilir (aktarım), o zaman yalnız sayı. */
|
||||
function yerlesenMetni(kontenjan: number, yerlesen: number | null): string {
|
||||
if (yerlesen == null) return "Yerleşen verisi yok";
|
||||
if (yerlesen === 0) return "Yerleşen olmadı";
|
||||
const bos = kontenjan - yerlesen;
|
||||
return bos > 0 ? `${sayi(yerlesen)} yerleşti · ${sayi(bos)} boş kaldı` : `${sayi(yerlesen)} yerleşti`;
|
||||
}
|
||||
|
||||
function KontenjanSatiri({ tur, k }: { tur: KontenjanTuruAnahtari; k: KontenjanTurleri }) {
|
||||
const kontenjan = k[tur].kontenjan ?? 0;
|
||||
return (
|
||||
<div data-sonra>
|
||||
<h3 className={BOLUM_BASLIK}>Kontenjan türleri ({k.yil})</h3>
|
||||
<p className={NOT}>
|
||||
{ozelVar
|
||||
? "ÖSYM okul birincileri, şehit ve gazi yakınları, depremzedeler ve 34 yaş üstü kadınlar için ayrı kontenjan ayırır; bu kontenjanlar yalnız o durumdaki adaylara açıktır ve her birinin taban sırası ayrı yayımlanır — o sıra burada yok."
|
||||
: "Bu programda yalnız genel kontenjan var."}
|
||||
<li className="flex items-start justify-between gap-4 border-b border-slate-100 py-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-base font-semibold text-slate-900">{KONTENJAN_TURU_ADI[tur]}</p>
|
||||
<p className="text-sm leading-snug text-slate-500">{KONTENJAN_TURU_KIME[tur]}</p>
|
||||
</div>
|
||||
<div className="shrink-0 text-right">
|
||||
<p className="font-heading text-xl font-bold leading-6 text-slate-900">
|
||||
{sayi(kontenjan)}
|
||||
<span className="font-sans text-sm font-semibold text-slate-400"> kişi</span>
|
||||
</p>
|
||||
<p className="text-sm leading-snug tabular-nums text-slate-500">
|
||||
{yerlesenMetni(kontenjan, k[tur].yerlesen)}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
const GRUP_ETIKET = "flex items-center gap-2 text-sm font-semibold text-slate-500";
|
||||
|
||||
/** Kontenjan kırılımının özeti: kutudaki sayı ve açık paneldeki satırlar aynı hesaptan. */
|
||||
function kontenjanOzeti(k: KontenjanTurleri) {
|
||||
const satirlar = KONTENJAN_TURU_SIRASI.filter((t) => (k[t].kontenjan ?? 0) > 0);
|
||||
const adet = (t: KontenjanTuruAnahtari) => k[t].kontenjan ?? 0;
|
||||
const ozel = satirlar.filter((t) => t !== "genel");
|
||||
const genelVar = satirlar.includes("genel");
|
||||
const genel = genelVar ? adet("genel") : 0;
|
||||
const ozelToplam = ozel.reduce((a, t) => a + adet(t), 0);
|
||||
return {
|
||||
satirlar,
|
||||
ozel,
|
||||
genelVar,
|
||||
genel,
|
||||
ozelToplam,
|
||||
toplam: genel + ozelToplam,
|
||||
// Dolmayan özel kontenjan genele aktarılır: genelde yerleşen kontenjanı aşar.
|
||||
aktarim: genelVar && (k.genel.yerlesen ?? 0) > genel,
|
||||
};
|
||||
}
|
||||
|
||||
// Önce bütün (toplam + oran çubuğu), sonra parçalar: herkese açık yer ile
|
||||
// yalnız şartı taşıyana açık yer iki ayrı grup. Çubukta yalnız bu ikili ayrım
|
||||
// renklidir (mavi / nötr); türün adı her satırda yazılı, renk tek başına
|
||||
// anlam taşımaz.
|
||||
function KontenjanGovde({ k }: { k: KontenjanTurleri }) {
|
||||
const o = kontenjanOzeti(k);
|
||||
if (o.ozel.length === 0) {
|
||||
return (
|
||||
<>
|
||||
<p className={NOT}>
|
||||
Bu programda yalnız genel kontenjan var; özel durumlar için ayrı yer ayrılmamış.
|
||||
</p>
|
||||
<ul className="mt-1">
|
||||
<KontenjanSatiri tur="genel" k={k} />
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<p className="mt-2 text-base leading-relaxed text-slate-600">
|
||||
Toplam <span className="font-semibold text-slate-900">{sayi(o.toplam)} kişilik</span> yer
|
||||
açıldı
|
||||
{o.genelVar
|
||||
? `: ${sayi(o.genel)} herkese açık, ${sayi(o.ozelToplam)} yalnız şartı taşıyan adaylara.`
|
||||
: "; tamamı yalnız şartı taşıyan adaylara."}
|
||||
</p>
|
||||
<dl className="mt-3 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
||||
{satirlar.map((t) => (
|
||||
<div key={t} className="flex items-baseline justify-between gap-3 border-b border-slate-100 py-2">
|
||||
<dt className="text-sm text-slate-700">{KONTENJAN_TURU_ADI[t]}</dt>
|
||||
<dd className="text-right text-sm tabular-nums text-slate-900">
|
||||
<span className="font-semibold">{sayi(k[t].kontenjan ?? 0)}</span>
|
||||
<span className="text-slate-500">
|
||||
{" "}
|
||||
kontenjan · {k[t].yerlesen != null ? sayi(k[t].yerlesen) : "—"} yerleşen
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div className="mt-3 flex h-3 gap-0.5 overflow-hidden rounded-full" aria-hidden>
|
||||
{o.satirlar.map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
className={cn("min-w-1.5", t === "genel" ? "bg-primary" : "bg-slate-300")}
|
||||
style={{ flexGrow: k[t].kontenjan ?? 0, flexBasis: 0 }}
|
||||
/>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{o.genelVar ? (
|
||||
<>
|
||||
<p className={cn(GRUP_ETIKET, "mt-5")}>
|
||||
<span className="size-2.5 rounded-full bg-primary" aria-hidden />
|
||||
Herkese açık
|
||||
</p>
|
||||
<ul>
|
||||
<KontenjanSatiri tur="genel" k={k} />
|
||||
</ul>
|
||||
</>
|
||||
) : null}
|
||||
<p className={cn(GRUP_ETIKET, "mt-5")}>
|
||||
<span className="size-2.5 rounded-full bg-slate-300" aria-hidden />
|
||||
Yalnız şartı taşıyan adaylara
|
||||
</p>
|
||||
<ul>
|
||||
{o.ozel.map((t) => (
|
||||
<KontenjanSatiri key={t} tur={t} k={k} />
|
||||
))}
|
||||
</ul>
|
||||
<p className={cn(NOT, "mt-3")}>
|
||||
Özel kontenjanların taban sırası ayrı yayımlanır; o sıra burada yok.
|
||||
{o.aktarim
|
||||
? " Dolmayan özel kontenjan genele aktarılır; genel kontenjanda yerleşenin kontenjanı aşması bundandır."
|
||||
: ""}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Gösterge karesi (DESIGN.md §4.11) — akademik göstergelerdeki karelerle aynı
|
||||
// sınıf seti; burada kare bir düğmedir.
|
||||
const KARE = "flex flex-col items-center justify-center rounded-3xl px-3 text-white";
|
||||
const KARE_ETIKET = "text-sm font-bold text-white/90";
|
||||
const KARE_DEV_SAYI = "font-heading text-5xl font-bold leading-none tracking-tight";
|
||||
const KARE_ORTA_SAYI = "font-heading text-3xl font-bold leading-none";
|
||||
const KARE_VURGU = "text-base font-bold text-white/90";
|
||||
const KOSE = 24; // rounded-3xl; motion köşeyi ölçek sırasında düzeltsin diye stilde
|
||||
|
||||
type KutuAnahtari = "kontenjan" | "netler" | "istihdam";
|
||||
type Kutu = {
|
||||
anahtar: KutuAnahtari;
|
||||
baslik: string;
|
||||
/** Karenin zemini (§2 "Gösterge karesi"); anlam taşımaz, etiket yazılı */
|
||||
renk: string;
|
||||
boy: string;
|
||||
ozet: ReactNode;
|
||||
icerik: ReactNode;
|
||||
};
|
||||
|
||||
function kutulariKur(veri: ProgramAyrintisi): Kutu[] {
|
||||
const kutular: Kutu[] = [];
|
||||
const k = veri.kontenjanTurleri;
|
||||
const o = k ? kontenjanOzeti(k) : null;
|
||||
if (k && o && o.satirlar.length > 0) {
|
||||
kutular.push({
|
||||
anahtar: "kontenjan",
|
||||
baslik: `Kontenjan türleri (${k.yil})`,
|
||||
renk: "bg-orange-500",
|
||||
boy: "min-h-40 py-4",
|
||||
ozet: (
|
||||
<>
|
||||
<span className={KARE_ETIKET}>Kontenjan</span>
|
||||
<span className={cn("mt-3", KARE_DEV_SAYI)}>{sayi(o.toplam)}</span>
|
||||
<span className={cn("mt-2", KARE_VURGU)}>
|
||||
{o.ozel.length === 0
|
||||
? "Tamamı genel"
|
||||
: o.genelVar
|
||||
? `${sayi(o.genel)} genel · ${sayi(o.ozelToplam)} özel`
|
||||
: "Tamamı özel"}
|
||||
</span>
|
||||
</>
|
||||
),
|
||||
icerik: <KontenjanGovde k={k} />,
|
||||
});
|
||||
}
|
||||
if (veri.netler.length > 0) {
|
||||
const son = veri.netler.reduce((a, b) => (b.yil > a.yil ? b : a));
|
||||
kutular.push({
|
||||
anahtar: "netler",
|
||||
baslik: "Son yerleşenin netleri",
|
||||
renk: "bg-primary",
|
||||
boy: "min-h-40 py-4",
|
||||
ozet:
|
||||
son.tabanPuan != null ? (
|
||||
<>
|
||||
<span className={KARE_ETIKET}>Son yerleşen</span>
|
||||
<span className={cn("mt-3", KARE_ORTA_SAYI)}>{ondalik(son.tabanPuan)}</span>
|
||||
<span className={cn("mt-2", KARE_VURGU)}>Puan ve netler</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className={KARE_ORTA_SAYI}>{son.yil}</span>
|
||||
<span className={cn("mt-1.5", KARE_ETIKET)}>Son yerleşenin netleri</span>
|
||||
</>
|
||||
),
|
||||
icerik: (
|
||||
<>
|
||||
<p className={NOT}>
|
||||
Bölüme en düşük puanla yerleşen adayın sınav netleri (YÖK Atlas).
|
||||
</p>
|
||||
<div className="mt-3">
|
||||
<NetlerIcerik netler={veri.netler} />
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
});
|
||||
}
|
||||
const istihdamCumlesi = istihdamMetni(veri);
|
||||
const v = veri.istihdam;
|
||||
if (v && istihdamCumlesi) {
|
||||
kutular.push({
|
||||
anahtar: "istihdam",
|
||||
baslik: "Mezun istihdamı",
|
||||
renk: "bg-indigo-500",
|
||||
boy: "min-h-28 py-3",
|
||||
ozet:
|
||||
v.istihdam != null ? (
|
||||
<>
|
||||
<span className={KARE_ORTA_SAYI}>%{ondalik(v.istihdam)}</span>
|
||||
<span className={cn("mt-1.5", KARE_ETIKET)}>İşe yerleşen mezun</span>
|
||||
</>
|
||||
) : v.isBulmaAy != null ? (
|
||||
<>
|
||||
<span className={KARE_ORTA_SAYI}>{ondalik(v.isBulmaAy)} ay</span>
|
||||
<span className={cn("mt-1.5", KARE_ETIKET)}>İlk işi bulma</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className={KARE_ORTA_SAYI}>{v.kazanc}</span>
|
||||
<span className={cn("mt-1.5", KARE_ETIKET)}>Kazanç grubu</span>
|
||||
</>
|
||||
),
|
||||
icerik: <p className={NOT}>{istihdamCumlesi}</p>,
|
||||
});
|
||||
}
|
||||
return kutular;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kontenjan türleri · Son yerleşenin netleri · Mezun istihdamı: kapalıyken
|
||||
* gösterge kareleri (§4.11 masonry), dokununca kare tam genişlikte panele
|
||||
* dönüşür ve bölümün tamamı açılır (Bilal, 11 Eki 2026). Aynı anda tek panel
|
||||
* açıktır; öbür kareler panelin altında yan yana kalır ve dokununca yer
|
||||
* değiştirir. Dönüşüm `layoutId` ile (kare → panel aynı öğe sayılır), yay
|
||||
* `YAY.donusum`; "hareketi azalt"ta dönüşüm yok, içerik doğrudan değişir.
|
||||
*/
|
||||
function BolumKutulari({ veri }: { veri: ProgramAyrintisi }) {
|
||||
const [acik, setAcik] = useState<KutuAnahtari | null>(null);
|
||||
// Az önce kapanan kare: panelden küçülürken içi ezik görünmesin diye solarak gelir.
|
||||
const [kapanan, setKapanan] = useState<KutuAnahtari | null>(null);
|
||||
const kapatRef = useRef<HTMLButtonElement>(null);
|
||||
const kareRef = useRef<Partial<Record<KutuAnahtari, HTMLButtonElement | null>>>({});
|
||||
const ilk = useRef(true);
|
||||
|
||||
// Odak dönüşen öğeyle birlikte gider: açılınca çarpıya, kapanınca kareye.
|
||||
useEffect(() => {
|
||||
if (ilk.current) {
|
||||
ilk.current = false;
|
||||
return;
|
||||
}
|
||||
if (acik) kapatRef.current?.focus({ preventScroll: true });
|
||||
else if (kapanan) kareRef.current[kapanan]?.focus({ preventScroll: true });
|
||||
}, [acik, kapanan]);
|
||||
|
||||
const kutular = kutulariKur(veri);
|
||||
if (kutular.length === 0) return null;
|
||||
const acikKutu = kutular.find((x) => x.anahtar === acik) ?? null;
|
||||
const kapali = kutular.filter((x) => x.anahtar !== acik);
|
||||
// Kapalıyken masonry (sol: 1. ve 3., sağ: 2., aşağı kaymış); panel açıkken
|
||||
// kalan kareler yan yana.
|
||||
const sol = kapali.filter((_, i) => i % 2 === 0);
|
||||
const sag = kapali.filter((_, i) => i % 2 === 1);
|
||||
|
||||
function ac(anahtar: KutuAnahtari) {
|
||||
setKapanan(acik);
|
||||
setAcik(anahtar);
|
||||
}
|
||||
function kapat() {
|
||||
setKapanan(acik);
|
||||
setAcik(null);
|
||||
}
|
||||
|
||||
const kare = (x: Kutu) => (
|
||||
<motion.button
|
||||
key={x.anahtar}
|
||||
ref={(el) => {
|
||||
kareRef.current[x.anahtar] = el;
|
||||
}}
|
||||
layoutId={`ayrinti-kutu-${x.anahtar}`}
|
||||
type="button"
|
||||
aria-expanded={false}
|
||||
onClick={() => ac(x.anahtar)}
|
||||
style={{ borderRadius: KOSE }}
|
||||
className={cn(
|
||||
KARE,
|
||||
"relative w-full cursor-pointer transition-[scale] duration-150 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
x.renk,
|
||||
x.boy,
|
||||
)}
|
||||
>
|
||||
<motion.span
|
||||
className="flex flex-col items-center"
|
||||
initial={kapanan === x.anahtar ? { opacity: 0 } : false}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: SURE.standart, delay: 0.1 }}
|
||||
>
|
||||
{x.ozet}
|
||||
</motion.span>
|
||||
<Maximize2 className="absolute top-3 right-3 size-4 text-white/80" aria-hidden />
|
||||
<span className="sr-only"> — {x.baslik}, tamamını göster</span>
|
||||
</motion.button>
|
||||
);
|
||||
|
||||
return (
|
||||
<MotionConfig reducedMotion="user" transition={YAY.donusum}>
|
||||
<LayoutGroup>
|
||||
<div data-sonra className="mt-7">
|
||||
{acikKutu ? (
|
||||
<motion.section
|
||||
key={acikKutu.anahtar}
|
||||
layoutId={`ayrinti-kutu-${acikKutu.anahtar}`}
|
||||
aria-label={acikKutu.baslik}
|
||||
style={{ borderRadius: KOSE }}
|
||||
className="relative overflow-hidden bg-slate-50 p-4 sm:p-5"
|
||||
>
|
||||
{/* Karenin rengi panelin üstünde söner: renk de biçimle birlikte dönüşür. */}
|
||||
<motion.span
|
||||
aria-hidden
|
||||
className={cn("pointer-events-none absolute inset-0", acikKutu.renk)}
|
||||
initial={{ opacity: 1 }}
|
||||
animate={{ opacity: 0 }}
|
||||
transition={{ duration: SURE.standart, ease: "easeOut" }}
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: SURE.standart, delay: 0.14 }}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h3 className="flex items-center gap-2.5 font-heading text-base font-bold text-slate-900">
|
||||
<span className={cn("size-3 shrink-0 rounded-[4px]", acikKutu.renk)} aria-hidden />
|
||||
{acikKutu.baslik}
|
||||
</h3>
|
||||
<button
|
||||
ref={kapatRef}
|
||||
type="button"
|
||||
onClick={kapat}
|
||||
className="flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-card text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<X className="size-4" aria-hidden />
|
||||
<span className="sr-only">{acikKutu.baslik} bölümünü küçült</span>
|
||||
</button>
|
||||
</div>
|
||||
{acikKutu.icerik}
|
||||
</motion.div>
|
||||
</motion.section>
|
||||
) : null}
|
||||
{kapali.length > 0 ? (
|
||||
<div className={cn("flex items-start gap-3", acikKutu && "mt-3")}>
|
||||
<div className="flex flex-1 flex-col gap-3">{sol.map(kare)}</div>
|
||||
{sag.length > 0 ? (
|
||||
<div className={cn("flex flex-1 flex-col gap-3", !acikKutu && sol.length > 0 && "mt-12")}>
|
||||
{sag.map(kare)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</LayoutGroup>
|
||||
</MotionConfig>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -480,7 +812,8 @@ function Kosullar({ veri }: { veri: ProgramAyrintisi }) {
|
||||
);
|
||||
}
|
||||
|
||||
function Istihdam({ veri }: { veri: ProgramAyrintisi }) {
|
||||
/** Mezun istihdamı cümlesi (TÜİK); gösterecek değer yoksa null. */
|
||||
function istihdamMetni(veri: ProgramAyrintisi): string | null {
|
||||
const v = veri.istihdam;
|
||||
if (!v) return null;
|
||||
const parcalar = [
|
||||
@@ -493,15 +826,8 @@ function Istihdam({ veri }: { veri: ProgramAyrintisi }) {
|
||||
const birlesik = parcalar.join(", ");
|
||||
const cumle = birlesik.charAt(0).toLocaleUpperCase("tr-TR") + birlesik.slice(1);
|
||||
const ort = v.ortalama.istihdam != null ? ` Türkiye ortalaması %${ondalik(v.ortalama.istihdam)}.` : "";
|
||||
return (
|
||||
<div data-sonra>
|
||||
<h3 className={BOLUM_BASLIK}>Mezun istihdamı</h3>
|
||||
<p className={NOT}>
|
||||
{cumle} (TÜİK {v.yil}, ülke geneli, {v.duzey === "lisans" ? "lisans" : "ön lisans"}{" "}
|
||||
mezunları; üniversiteye göre değişir, kırılım yok).{ort}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
const duzey = v.duzey === "lisans" ? "lisans" : "ön lisans";
|
||||
return `${cumle} (TÜİK ${v.yil}, ülke geneli, ${duzey} mezunları; üniversiteye göre değişir, kırılım yok).${ort}`;
|
||||
}
|
||||
|
||||
function UniversiteOzeti({ veri }: { veri: ProgramAyrintisi }) {
|
||||
|
||||
Reference in New Issue
Block a user