From 67be93c3943b370f68f1067dffc2d4027d288744 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Mon, 21 Sep 2026 05:25:23 +0300 Subject: [PATCH] =?UTF-8?q?feat(analitik):=20kapi=5Facildi=20+=20kapi=5Fka?= =?UTF-8?q?patildi=20olaylar=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Profil kapısının açılışı ve tamamlanmadan kapanışı doğrudan ölçülmüyordu; "82 oturum kapıyı gördü" kod davranışından çıkarımdı (09-analitik-tur2). İki yeni olay (adlar kalıcı sözleşme): - kapi_acildi {tetik: arti|sonuc|sihirbaz|duzenle|diger}: depoda tek açılış noktası (kapiyiAc). Her açıcı kendi tetik'ini yazar: programEkleIste → arti, profilDuzenlemeyiAc → duzenle, profilToplamaAc → isteğe bağlı 3. parametre, verilmezse hedeften (sonuc/sihirbaz), o da yoksa diger. Depoya `tetik` alanı eklendi. - kapi_kapatildi {adim: giris|sira|sihirbaz, tetik}: yalnız TAMAMLANMADAN kapanışta; kapı bileşeninde tek noktadan (vazgec) — X, dışarı tıklama, Esc ve "Vazgeç" butonları. Tamamlanan kapı profilTamamlandi ile depodan kapanır, bu noktaya uğramaz. Ham sıra/e-posta olaylara girmez. Davranış değişmez. Co-Authored-By: Claude Fable 5.1 --- .../components/tercih-profili-kapisi.tsx | 55 ++++++++++-- .../liste/hooks/use-tercih-profili.ts | 84 +++++++++++++------ src/lib/analitik.ts | 7 +- 3 files changed, 109 insertions(+), 37 deletions(-) diff --git a/src/features/liste/components/tercih-profili-kapisi.tsx b/src/features/liste/components/tercih-profili-kapisi.tsx index 7085430..7ab9ce9 100644 --- a/src/features/liste/components/tercih-profili-kapisi.tsx +++ b/src/features/liste/components/tercih-profili-kapisi.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { ArrowRight, Trophy } from "lucide-react"; import { useReducedMotion } from "motion/react"; @@ -53,17 +53,38 @@ type Adim = "giris" | "sira" | "sihirbaz"; const ADIM_SIRASI: Record = { giris: 0, sira: 1, sihirbaz: 2 }; export function TercihProfiliKapisi() { - const { acik, duzenle, dogrudanSira, bekleyen, bekleyenHedef, profil } = - useProfilKapisi(); + const { + acik, + duzenle, + dogrudanSira, + tetik, + bekleyen, + bekleyenHedef, + profil, + } = useProfilKapisi(); + // Kapanış olayı için o an görünen adım; KapıIcerik her adım değişiminde + // (ve mount'ta) bildirir. Render'ı etkilemediği için state değil ref. + const adimRef = useRef("giris"); const oturumAnahtari = acik ? `${duzenle ? "d" : "n"}-${bekleyen?.program.id ?? "yok"}-${profil?.sira ?? 0}-${profil?.tur ?? ""}` : "kapali"; + // Tamamlanmadan kapanışın TEK noktası: X, dışarı tıklama, Esc (Dialog + // onOpenChange) ve içerideki "Vazgeç" butonları buradan geçer. + // Tamamlanan kapı buraya uğramaz: secimleriTamamla → profilTamamlandi + // depoda `acik`'ı doğrudan kapatır; Radix kontrollü `open` prop'u + // değişince onOpenChange çağırmaz → `kapi_kapatildi` atılmaz. + function vazgec() { + if (!acik) return; + olay("kapi_kapatildi", { adim: adimRef.current, tetik }); + profilKapisiniKapat(); + } + return ( { - if (!v) profilKapisiniKapat(); + if (!v) vazgec(); }} > { + adimRef.current = yeniAdim; + }} + onVazgec={vazgec} /> ) : null} @@ -98,21 +123,32 @@ function KapıIcerik({ bekleyen, hedef, profil, + onAdimDegisti, + onVazgec, }: { duzenle: boolean; dogrudanSira: boolean; bekleyen: BekleyenProgram | null; hedef: BekleyenHedef | null; profil: TercihProfili | null; + /** Görünen adımı üst bileşene bildirir (yalnız `kapi_kapatildi` ölçümü). */ + onAdimDegisti: (adim: Adim) => void; + /** Tamamlamadan kapat — olayı üst bileşen atar, sonra kapıyı kapatır. */ + onVazgec: () => void; }) { const router = useRouter(); const bilinenSira = bekleyen?.adaySira ?? profil?.sira ?? null; const bilinenTur = bekleyen?.adayTur ?? profil?.tur ?? "say"; // Düzenlemede ve navbar "Sıralamanı gir" girişinde doğrudan sıralama adımı - const [adim, setAdim] = useState( - duzenle || dogrudanSira ? "sira" : "giris", - ); + const ilkAdim: Adim = duzenle || dogrudanSira ? "sira" : "giris"; + const [adim, setAdim] = useState(ilkAdim); + // Her açılışta (key ile yeniden mount) üstteki adım kaydını başa sar; + // önceki açılıştan kalan "sihirbaz" değeri yeni kapanışa sızmasın. + useEffect(() => { + onAdimDegisti(ilkAdim); + // eslint-disable-next-line react-hooks/exhaustive-deps -- yalnız mount + }, []); // Adım geçişleri sihirbazın kendi içindeki TransitionPanel kaymasıyla oynar const [yon, setYon] = useState(1); const [gecisBasladi, setGecisBasladi] = useState(false); @@ -140,6 +176,7 @@ function KapıIcerik({ setYon(ADIM_SIRASI[yeniAdim] > ADIM_SIRASI[adim] ? 1 : -1); setGecisBasladi(true); setAdim(yeniAdim); + onAdimDegisti(yeniAdim); } async function facetYukle(siraDeger: number, turDeger: string) { @@ -287,7 +324,7 @@ function KapıIcerik({