Files
scoreodonto.com/frontend/components/ChangePasswordModal.tsx
VPS 4 Builder 3e64514b08 feat(financeiro+salas): motor financeiro V1/V2, glosas, GTO→financeiro, contratos de convênio e redesenho de Salas
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>
2026-06-16 00:28:07 +02:00

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>
);
};