From 4b87a3e71ed555a061d0e8b8c92cf4c3cf3cdaf0 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Mon, 21 Sep 2026 05:23:40 +0300 Subject: [PATCH 1/4] =?UTF-8?q?fix(analitik):=20program=5Feklendi=20yaln?= =?UTF-8?q?=C4=B1z=20ger=C3=A7ek=20eklemede=20at=C4=B1ls=C4=B1n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Olay, profilsiz ziyaretçide program listeye girmeyip profil kapısı açıldığında da atılıyordu (kapiAcildi dalı else'e düşüyordu); seri "ekleme"yi değil "+ tıklaması"nı ölçüyordu (09-analitik-tur2, S6). - Buton: yalnız sonuc.eklendi iken {kaynak, yol: "dogrudan"}. - profilTamamlandi: bekleyen program listeye girdiyse (ekle() true) {kaynak, yol: "kapi"}. - OlayAdi yorumuna seri kırılması notu. Davranış değişmez: profilsiz + hâlâ kapıyı açar. Co-Authored-By: Claude Fable 5.1 --- src/features/liste/components/program-ekle-butonu.tsx | 7 +++++-- src/features/liste/hooks/use-tercih-profili.ts | 10 +++++++++- src/lib/analitik.ts | 3 +++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/features/liste/components/program-ekle-butonu.tsx b/src/features/liste/components/program-ekle-butonu.tsx index 46a077e..6a1a30d 100644 --- a/src/features/liste/components/program-ekle-butonu.tsx +++ b/src/features/liste/components/program-ekle-butonu.tsx @@ -52,8 +52,11 @@ export function ProgramEkleButonu({ toast.info(`Listen ${MANUEL_LISTE_MAX} tercihe ulaştı.`); } else if (sonuc.zatenVar) { toast.info("Bu program zaten seçtiklerinde."); - } else { - olay("program_eklendi", { kaynak }); + } else if (sonuc.eklendi) { + // Yalnız gerçek eklemede: profilsiz + kapıyı açar (kapiAcildi) ve + // program o an listeye girmez — o yolun olayı kapı tamamlanınca + // profilTamamlandi içinden {yol: "kapi"} ile atılır. + olay("program_eklendi", { kaynak, yol: "dogrudan" }); } } diff --git a/src/features/liste/hooks/use-tercih-profili.ts b/src/features/liste/hooks/use-tercih-profili.ts index d3e5a3f..6b12716 100644 --- a/src/features/liste/hooks/use-tercih-profili.ts +++ b/src/features/liste/hooks/use-tercih-profili.ts @@ -5,6 +5,7 @@ import { useSyncExternalStore } from "react"; import type { Program } from "@/types/yokatlas"; +import { olay } from "@/lib/analitik"; import { PROFIL_DEGISTI_EVENT, sonucHref, type TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler"; import { tercihProfiliOku, tercihProfiliTamamlandiMi } from "@/features/sihirbaz/sihirbaz-profil"; import { @@ -250,7 +251,14 @@ export function profilTamamlandi( const bekleyen = durum.bekleyen; const hedef = durum.bekleyenHedef; if (bekleyen) { - ekle(programdanManuelTercih(bekleyen.program, profil.sira, bekleyen.kaynak)); + const eklendi = ekle( + programdanManuelTercih(bekleyen.program, profil.sira, bekleyen.kaynak), + ); + // Kapıdan geçen +: program ancak şimdi listeye girdi. Butondaki + // {yol: "dogrudan"} atışının eşi; liste doluysa/zaten varsa atılmaz. + if (eklendi) { + olay("program_eklendi", { kaynak: bekleyen.kaynak, yol: "kapi" }); + } } durum = { ...durum, diff --git a/src/lib/analitik.ts b/src/lib/analitik.ts index b7dedb5..604d74e 100644 --- a/src/lib/analitik.ts +++ b/src/lib/analitik.ts @@ -20,6 +20,9 @@ export type OlayAdi = | "liste_olusturuldu" | "liste_guncellendi" | "liste_revize" + // Seri kırılması: bu düzeltmenin canlıya çıktığı tarihten önce = + + // tıklaması (kapı açılsa da atılıyordu), sonrası = gerçek ekleme + // ({yol: "dogrudan" | "kapi"}); seriler karşılaştırılamaz. | "program_eklendi" | "netler_goruntulendi" | "danisman_sorusu" From 67be93c3943b370f68f1067dffc2d4027d288744 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Mon, 21 Sep 2026 05:25:23 +0300 Subject: [PATCH 2/4] =?UTF-8?q?feat(analitik):=20kapi=5Facildi=20+=20kapi?= =?UTF-8?q?=5Fkapatildi=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({