İçeriğe geç

Detail Drawer Pattern — Kanon

Detay

Admin paneldeki “detay drawer” component’lari (EmailDetailDrawer, SmsDetailDrawer vb.) bu pattern’i takip eder. Yeni drawer eklerken once buraya bak.

Kapsam: components/admin/communications/*DetailDrawer.tsx. Genel-amac components/admin/design-system/Drawer.tsx primitive ayri konu — bu doc detay verilerini gosteren drawer’lar icindir.

LogTable component’inde drawer mount’i id’ye bagli key ile yapilir:

{openId !== null && (
<DetailDrawer key={openId} id={openId} onClose={() => setOpenId(null)} />
)}

Neden: React key degisince component’i otomatik unmount + remount yapar. Tum useState’ler default’a doner. Drawer icinde useEffect+ setState ile manuel reset yazmaya gerek yoktur — cascading render riski ortadan kalkar.

Anti-pattern (eskiden boyleydi):

// ❌ kaldirildi
useEffect(() => {
setData(null);
setTab('default');
setPayload(null);
fetch(...).then(setData);
}, [id]);

Sync setState reset’leri React Compiler’in set-state-in-effect kuralina takiliyordu. Parent key pattern bu reset’leri gereksizlestirir.

useEffect’te sadece async fetch + cancel guard kalir:

useEffect(() => {
let cancelled = false;
void fetch(`/api/admin/communications/emails/${id}`)
.then(r => r.json())
.then(j => { if (!cancelled) setData(j.log); });
return () => { cancelled = true; };
}, [id]);

Neden cancel guard: key={id} remount’undan ayri olarak, ayni drawer icinde id parent state’inden A→B cabuk degisirse, A’nin response’u B’nin uzerine yazabilir. cancelled flag bunu onler.

Async setData (.then(setData)) cascading render degil — React Compiler kurali sync setState’i hedefler.

Encrypted payload reveal + auto-mask + clipboard mantigi tek hook’ta toplanmistir:

import { usePayloadReveal } from '@/hooks/use-payload-reveal';
const { payload, secondsLeft, onReveal, onCopy } = usePayloadReveal(
`/api/admin/communications/emails/${id}/payload`,
);

Endpoint kontrati:

  • POST {endpoint} body { source: 'view' | 'copy' }
  • Response { payload: unknown }

Hook 30s auto-mask timer’ini barindirir; component cleanup’ta timer clear edilir. Sabit: REVEAL_TIMEOUT_MS (drawer-shared.ts’de).

backdropStyle + drawerStyle components/admin/communications/drawer-shared.ts icindedir. Yeni drawer ayni stili import eder. Variant gerekiyorsa shared’a yeni export ekle (ornek: drawerStyleNarrow), inline yazma.

Mevcut iki drawer’da footer:

  • ↻ Resend butonu → ResendDialog acar
  • Delete butonu → native confirm() → DELETE endpoint → onClose()

Yeni drawer farkli aksiyon barindirabilir; ama Resend + Delete varsa ayni butonlari kullan.

Pure-function (REVEAL_TIMEOUT_MS, sabit) testleri node:test ile calistirilabilir. Hook’un timing davranisi component-level test gerektirir — su an codebase’de jsdom + RTL kurulu degil, manuel browser smoke yeterli.

Drawer/Modal layer’larin tek satirda anlasilir hiyerarsisi. Yeni overlay eklerken bu skaladan secim yap, kafadan rakam kullanma.

zIndexKatmanOrnek
20Sticky header, icerik dropdownAdminPanelContent, LeadStatusSelect
40Mobile sidebar overlayAdminSidebar, CustomerSidebar
50Drawer + Modal (standart)primitive Drawer/Modal, EmailDetailDrawer, SmsDetailDrawer, CommandPalette
60Drawer’dan acilan dialogResendDialog (Email/Sms drawer ustunde durur)
200Fullscreen editorImageEditor, sidebar logout confirm

Yanlis pattern (eskiden boyleydi):

  • Email/Sms drawer zIndex: 900/901 — primitive Drawer (Tailwind z-50) ile farkli katmanda, ResendDialog zIndex: 1000 patch ile bunu asmaya calisiyordu. Cozum: hepsini ayni skalaya getir.

Yeni drawer yazarken:

  • Drawer’in kendi backdrop+panel: zIndex: 50 (drawer-shared.ts’den import)
  • Drawer’dan acilan dialog: zIndex: 60 (ResendDialog ornegi)
  • Daha ustte gostermek istiyorsan → ladder’a yeni satir ekle, dokumante et
YapmaYap
Sync setState reset useEffect icindeParent key={id} ile fresh mount
Drawer icinde inline backdropStyleimport { backdropStyle } from './drawer-shared'
Payload reveal duplicateusePayloadReveal(endpoint) hook
Cancel guard’siz fetchlet cancelled = false; return () => { cancelled = true; }
Timer cleanup eksikHook icinde clearInterval + clearTimeout cleanup
  • components/admin/communications/EmailDetailDrawer.tsx — kanon implementasyon
  • components/admin/communications/SmsDetailDrawer.tsx — kanon implementasyon
  • components/admin/communications/drawer-shared.ts — sabit + style
  • hooks/use-payload-reveal.ts — payload reveal hook
  • docs/admin-design-system/STYLE.md — genel design system