f084656712
Agenda (multi-secretária): - audit_log genérico + autoria (created/updated/canceled_by) + soft-delete + version (lock otimista) - status novo vocabulário + índice único PARCIAL (libera slot ao cancelar/faltar) - endpoints: falta, remarcar, restaurar, historico, pendencias (a reagendar), faltas, familia - presença: heartbeat (DB última visita + Dragonfly ao vivo, janela 45s) - frontend: StackModal, AgendaDetailModal, AgendaPresence; lista a-reagendar; pacienteId no fluxo de criação; chips de família + badge de faltas no modal RBAC por cargo: funcoes.permissoes/nivel; gate de menu por cargo (array vazio herda role); hierarquia só-gerencia-abaixo no backend Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61 lines
3.5 KiB
TypeScript
61 lines
3.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { KeyRound, Loader2, X } from 'lucide-react';
|
|
import { HybridBackend } from '../services/backend.ts';
|
|
import { useToast } from '../contexts/ToastContext.tsx';
|
|
|
|
interface Props {
|
|
forced?: boolean; // troca obrigatória (1º login com senha temporária) — não pede senha atual nem deixa cancelar
|
|
onClose?: () => void;
|
|
onDone: () => void;
|
|
}
|
|
|
|
const inp = 'w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium text-gray-800 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all';
|
|
|
|
export const ChangePasswordModal: React.FC<Props> = ({ forced, onClose, onDone }) => {
|
|
const toast = useToast();
|
|
const [atual, setAtual] = useState('');
|
|
const [nova, setNova] = useState('');
|
|
const [conf, setConf] = useState('');
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const submit = async () => {
|
|
if (nova.length < 6) { toast.error('A NOVA SENHA DEVE TER NO MÍNIMO 6 CARACTERES.'); return; }
|
|
if (nova !== conf) { toast.error('AS SENHAS NÃO COINCIDEM.'); return; }
|
|
setSaving(true);
|
|
try {
|
|
const r = await HybridBackend.changeMyPassword(forced ? undefined : atual, nova);
|
|
if (!r.success) { toast.error(r.message || 'ERRO AO TROCAR SENHA.'); return; }
|
|
toast.success('SENHA ALTERADA COM SUCESSO!');
|
|
onDone();
|
|
} catch { toast.error('ERRO AO TROCAR SENHA.'); }
|
|
finally { setSaving(false); }
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black/60 z-[85] flex items-center justify-center p-4 backdrop-blur-sm">
|
|
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm">
|
|
<div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
|
|
<h3 className="font-black text-gray-800 uppercase text-sm flex items-center gap-2"><KeyRound size={16} className="text-blue-600" /> {forced ? 'Defina uma nova senha' : 'Trocar senha'}</h3>
|
|
{!forced && onClose && <button onClick={onClose} className="p-1.5 hover:bg-gray-100 rounded-lg"><X size={18} /></button>}
|
|
</div>
|
|
<div className="p-6 space-y-3">
|
|
{forced && (
|
|
<p className="text-[11px] text-amber-700 bg-amber-50 border border-amber-100 rounded-xl p-3 font-bold uppercase leading-relaxed">
|
|
Sua senha é temporária. Defina uma nova para continuar usando o sistema.
|
|
</p>
|
|
)}
|
|
{!forced && <input type="password" placeholder="SENHA ATUAL" value={atual} onChange={e => setAtual(e.target.value)} className={inp} autoComplete="current-password" />}
|
|
<input type="password" placeholder="NOVA SENHA" value={nova} onChange={e => setNova(e.target.value)} className={inp} autoComplete="new-password" />
|
|
<input type="password" placeholder="CONFIRMAR NOVA SENHA" value={conf} onChange={e => setConf(e.target.value)} className={inp} autoComplete="new-password" />
|
|
<div className="flex gap-2 pt-1">
|
|
{!forced && onClose && <button onClick={onClose} className="px-4 py-2.5 border border-gray-200 rounded-xl text-[11px] font-black uppercase hover:bg-gray-50">Cancelar</button>}
|
|
<button onClick={submit} disabled={saving} className="flex-1 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-[11px] font-black uppercase disabled:opacity-50 flex items-center justify-center gap-2">
|
|
{saving ? <Loader2 size={13} className="animate-spin" /> : <KeyRound size={13} />} Salvar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|