import React, { useEffect, useRef } from 'react'; interface ModalProps { title: string; subtitle?: string; onClose: () => void; children: React.ReactNode; footer?: React.ReactNode; size?: 'md' | 'lg' | 'xl'; } const SIZES: Record, string> = { md: 'max-w-md', lg: 'max-w-2xl', xl: 'max-w-4xl', }; /** * Offene Dialoge in der Reihenfolge ihres Oeffnens. Escape darf nur den * obersten schliessen, sonst raeumt ein Tastendruck verschachtelte Dialoge * (z. B. "Neuer Kontakt" in einer Opportunity) gleich alle mit weg. */ const openModals: symbol[] = []; /** * Schliessen per Escape und per Klick in den Hintergrund. Beides greift in jedem * Dialog, weil alle Dialoge im CRM ueber diese Komponente laufen. */ function useDismiss(onClose: () => void) { // Ueber die Ref bleibt der Escape-Handler an den Dialog gebunden, auch wenn // die Elternkomponente bei jedem Rendern ein neues onClose durchgibt - sonst // wuerde sich der Dialog beim Neuaufbau des Handlers wieder nach oben // sortieren und dem verschachtelten Dialog das Escape wegnehmen. const closeRef = useRef(onClose); closeRef.current = onClose; useEffect(() => { const token = Symbol('modal'); openModals.push(token); const onKeyDown = (event: KeyboardEvent) => { if (event.key !== 'Escape') return; if (openModals[openModals.length - 1] !== token) return; closeRef.current(); }; window.addEventListener('keydown', onKeyDown); return () => { window.removeEventListener('keydown', onKeyDown); const index = openModals.indexOf(token); if (index !== -1) openModals.splice(index, 1); }; }, []); // Nur ein Klick, der im Hintergrund beginnt *und* endet, schliesst. So // ueberlebt eine Textmarkierung, die im Dialog anfaengt und daneben aufhoert. const pressedBackdrop = useRef(false); return { onMouseDown: (event: React.MouseEvent) => { pressedBackdrop.current = event.target === event.currentTarget; }, onClick: (event: React.MouseEvent) => { if (pressedBackdrop.current && event.target === event.currentTarget) closeRef.current(); }, }; } export const Modal: React.FC = ({ title, subtitle, onClose, children, footer, size = 'lg', }) => { const dismiss = useDismiss(onClose); return (

{title}

{subtitle &&

{subtitle}

}
{children}
{footer && (
{footer}
)}
); }; interface ConfirmDialogProps { title: string; message: string; confirmLabel?: string; onConfirm: () => void; onCancel: () => void; } export const ConfirmDialog: React.FC = ({ title, message, confirmLabel = 'Löschen', onConfirm, onCancel, }) => { const dismiss = useDismiss(onCancel); return (

{title}

{message}

); };