import React, { useEffect, useMemo, useRef, useState } from "react"; type ProfileForm = { name: string; email: string; organismName?: string; // "Empresa" / "Organismo" (CESPT, etc.) }; export default function ProfileModal({ open, loading = false, initial, avatarUrl = null, onClose, onSave, onUploadAvatar, }: { open: boolean; loading?: boolean; initial: ProfileForm; avatarUrl?: string | null; onClose: () => void; onSave: (next: ProfileForm) => void | Promise; onUploadAvatar?: (file: File) => void | Promise; }) { const [name, setName] = useState(initial?.name ?? ""); const [email, setEmail] = useState(initial?.email ?? ""); const [organismName, setOrganismName] = useState(initial?.organismName ?? ""); // Avatar preview local (si el usuario selecciona imagen) const [localAvatar, setLocalAvatar] = useState(null); const lastPreviewUrlRef = useRef(null); const fileInputRef = useRef(null); // Mantener el form sincronizado cuando se abre o cambia initial useEffect(() => { if (!open) return; setName(initial?.name ?? ""); setEmail(initial?.email ?? ""); setOrganismName(initial?.organismName ?? ""); setLocalAvatar(null); }, [open, initial?.name, initial?.email, initial?.organismName]); // Cerrar con ESC useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [open, onClose]); // Limpieza de object URLs useEffect(() => { return () => { if (lastPreviewUrlRef.current) { URL.revokeObjectURL(lastPreviewUrlRef.current); } }; }, []); const initials = useMemo(() => { const parts = (name || "") .trim() .split(/\s+/) .filter(Boolean); const a = parts[0]?.[0] ?? "U"; const b = parts[1]?.[0] ?? ""; return (a + b).toUpperCase(); }, [name]); const computedAvatarSrc = useMemo(() => { const src = localAvatar ?? avatarUrl; if (!src) return null; if (src.startsWith("blob:")) return src; // cache-bust por si el backend mantiene la misma URL const sep = src.includes("?") ? "&" : "?"; return `${src}${sep}t=${Date.now()}`; }, [localAvatar, avatarUrl]); const triggerFilePicker = () => { if (!onUploadAvatar) return; fileInputRef.current?.click(); }; const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const isImage = file.type.startsWith("image/"); const maxMb = 5; const sizeOk = file.size <= maxMb * 1024 * 1024; if (!isImage) { alert("Selecciona un archivo de imagen."); e.target.value = ""; return; } if (!sizeOk) { alert(`La imagen debe pesar máximo ${maxMb}MB.`); e.target.value = ""; return; } // Preview inmediato const previewUrl = URL.createObjectURL(file); if (lastPreviewUrlRef.current) URL.revokeObjectURL(lastPreviewUrlRef.current); lastPreviewUrlRef.current = previewUrl; setLocalAvatar(previewUrl); try { await onUploadAvatar?.(file); } catch (err) { console.error(err); alert("No se pudo subir la imagen. Intenta de nuevo."); } finally { e.target.value = ""; } }; const handleSubmit = async () => { if (!name.trim()) { alert("El nombre es obligatorio."); return; } if (!email.trim()) { alert("El correo es obligatorio."); return; } await onSave({ name: name.trim(), email: email.trim(), organismName: organismName.trim() || undefined, }); }; if (!open) return null; return (
{/* Backdrop */}
{/* RIGHT: Form */}
correo electrónico
setName(e.target.value)} className="w-full rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm text-slate-900 outline-none focus:ring-2 focus:ring-slate-200" placeholder="Nombre del usuario" /> setEmail(e.target.value)} className="w-full rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm text-slate-900 outline-none focus:ring-2 focus:ring-slate-200" placeholder="correo@organismo.gob.mx" /> setOrganismName(e.target.value)} className="w-full rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm text-slate-900 outline-none focus:ring-2 focus:ring-slate-200" placeholder="Organismo operador" />
{/* Footer */}
); } function Field({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); }