3e64514b08
Financeiro V1 (Épicos 1–4): - FKs de origem no lançamento (paciente/profissional/procedimento/agendamento/contrato/realizado), soft delete e cron "Atrasado" - prontuário de procedimentos realizados (fotos antes/depois) + regra sem-comissão (garantia/reabertura do mesmo profissional) - normalizeFinanceiro: corrige CHECK capitalizado vs enforceUppercase (lançamentos não salvavam pela UI) - baixa de pagamento (pago_em), despesas (fornecedor/centro de custo/recorrente), competência por data de conclusão - relatório financeiro (período, paciente, profissional, forma, convênio, glosa) - orçamento Assinado -> contas a receber; contrato vigente -> cobrança recorrente; renovação automática de contrato Financeiro V2 (Comissão/Repasse): - regras de % (padrão/procedimento/override por profissional) + custo de laboratório - base selecionável (recebido/produção), fechamento persistido, pagar (gera despesa) + comprovante + estorno Glosas de convênio: por item de GTO, recurso/protocolo/prazo, recuperar/estornar, impacto no relatório GTO -> Financeiro: finalizar gera RECEITA (convênio a receber); LancarGTO passa a persistir; convênios reais (planos) Contratos: modelo "Atendimento a Convênios / Repasse (Dentista Credenciado)" + variáveis de convênio Salas (redesenho): perfil "Dono de Sala"; locação sempre ativa; menus MINHAS/ALUGAR SALAS; sala como workspace isolado (seletor agrupado Clínicas x Salas, tenantGuard por dono); financeiro de locação (reserva confirmada -> recebível); Painel da Sala (KPIs, agenda visual, recebíveis, relatório por sala) Docs: BACKLOG-FINANCEIRO-V1, AUDITORIA-FUNCIONAL-SCOREODONTO, CHECKLIST-DEPLOY-PRODUCAO 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-teal-500 focus:ring-2 focus:ring-teal-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-teal-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-teal-600 hover:bg-teal-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>
|
|
);
|
|
};
|