fix: fix TypeScript errors in profissional page

This commit is contained in:
M-Gabrielly 2025-11-06 20:16:12 -03:00
parent b265b72f75
commit e85e8e9736
2 changed files with 123 additions and 21 deletions

View File

@ -520,24 +520,25 @@ export default function RelatoriosPage() {
{/* Performance por médico */}
<div className="bg-card border border-border rounded-lg shadow p-6">
<div className="flex items-center justify-between mb-2">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-2 md:gap-0 mb-4">
<h2 className="font-semibold text-lg text-foreground flex items-center gap-2"><TrendingUp className="w-5 h-5" /> Performance por Médico</h2>
<Button size="sm" variant="outline" className="hover:bg-primary! hover:text-white! transition-colors" onClick={() => exportPDF("Performance por Médico", "Resumo da performance por médico.")}> <FileDown className="w-4 h-4 mr-1" /> Exportar PDF</Button>
<Button size="sm" variant="outline" className="hover:bg-primary! hover:text-white! transition-colors w-full md:w-auto" onClick={() => exportPDF("Performance por Médico", "Resumo da performance por médico.")}> <FileDown className="w-4 h-4 mr-1" /> Exportar PDF</Button>
</div>
<table className="w-full text-sm mt-4">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-muted-foreground">
<th className="text-left font-medium">Médico</th>
<th className="text-left font-medium">Consultas</th>
<th className="text-left font-medium">Absenteísmo (%)</th>
<tr className="text-muted-foreground border-b border-border">
<th className="text-left font-medium py-3 px-2 md:px-0">Médico</th>
<th className="text-center font-medium py-3 px-2 md:px-0">Consultas</th>
<th className="text-center font-medium py-3 px-2 md:px-0">Absenteísmo (%)</th>
</tr>
</thead>
<tbody>
{(loading ? performancePorMedico : medicosPerformance).map((m) => (
<tr key={m.nome}>
<td className="py-1">{m.nome}</td>
<td className="py-1">{m.consultas}</td>
<td className="py-1">{m.absenteismo}</td>
<tr key={m.nome} className="border-b border-border/50 hover:bg-muted/30 transition-colors">
<td className="py-3 px-2 md:px-0">{m.nome}</td>
<td className="py-3 px-2 md:px-0 text-center font-medium">{m.consultas}</td>
<td className="py-3 px-2 md:px-0 text-center text-blue-500 font-medium">{m.absenteismo}</td>
</tr>
))}
</tbody>
@ -545,6 +546,7 @@ export default function RelatoriosPage() {
</div>
</div>
</div>
</div>
);
}

View File

@ -7,6 +7,7 @@ import ProtectedRoute from "@/components/shared/ProtectedRoute";
import { useAuth } from "@/hooks/useAuth";
import { useToast } from "@/hooks/use-toast";
import { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesPorIds, buscarMedicoPorId, buscarMedicosPorIds, buscarMedicos, listarAgendamentos, type Paciente, buscarRelatorioPorId, atualizarMedico } from "@/lib/api";
import { ENV_CONFIG } from '@/lib/env-config';
import { useReports } from "@/hooks/useReports";
import { CreateReportData } from "@/types/report-types";
import { Button } from "@/components/ui/button";
@ -36,7 +37,6 @@ import {
import dynamic from "next/dynamic";
import { ENV_CONFIG } from '@/lib/env-config';
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
@ -182,7 +182,7 @@ const ProfissionalPage = () => {
const q = `doctor_id=eq.${encodeURIComponent(String(resolvedDoctorId))}&select=patient_id&limit=200`;
const appts = await listarAgendamentos(q).catch(() => []);
for (const a of (appts || [])) {
const pid = a.patient_id ?? a.patient ?? a.patient_id_raw ?? null;
const pid = (a as any).patient_id ?? null;
if (pid) patientIdSet.add(String(pid));
}
} catch (e) {
@ -211,6 +211,7 @@ const ProfissionalPage = () => {
})();
return () => { mounted = false; };
// Re-run when user id becomes available so patients assigned to the logged-in doctor are loaded
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user?.id]);
// Carregar perfil do médico correspondente ao usuário logado
@ -429,6 +430,9 @@ const ProfissionalPage = () => {
const [commPhoneNumber, setCommPhoneNumber] = useState('');
const [commMessage, setCommMessage] = useState('');
const [commPatientId, setCommPatientId] = useState<string | null>(null);
const [commResponses, setCommResponses] = useState<any[]>([]);
const [commResponsesLoading, setCommResponsesLoading] = useState(false);
const [commResponsesError, setCommResponsesError] = useState<string | null>(null);
const [smsSending, setSmsSending] = useState(false);
const handleSave = async (event: React.MouseEvent<HTMLButtonElement>) => {
@ -520,6 +524,68 @@ const ProfissionalPage = () => {
}
};
const loadCommResponses = async (patientId?: string) => {
const pid = patientId ?? commPatientId;
if (!pid) {
setCommResponses([]);
setCommResponsesError('Selecione um paciente para ver respostas');
return;
}
setCommResponsesLoading(true);
setCommResponsesError(null);
try {
// 1) tentar buscar por patient_id (o comportamento ideal)
const qs = new URLSearchParams();
qs.set('patient_id', `eq.${String(pid)}`);
qs.set('order', 'created_at.desc');
const url = `${(ENV_CONFIG as any).REST}/messages?${qs.toString()}`;
const headers: Record<string,string> = { 'Accept': 'application/json' };
if (token) headers['Authorization'] = `Bearer ${token}`;
if ((ENV_CONFIG as any)?.SUPABASE_ANON_KEY) headers['apikey'] = (ENV_CONFIG as any).SUPABASE_ANON_KEY;
const r = await fetch(url, { method: 'GET', headers });
let data = await r.json().catch(() => []);
data = Array.isArray(data) ? data : [];
// 2) Se não houver mensagens por patient_id, tentar buscar por número (from/to)
if ((!data || data.length === 0) && commPhoneNumber) {
try {
const norm = normalizePhoneNumber(commPhoneNumber);
if (norm) {
// Primeiro tenta buscar mensagens onde `from` é o número
const qsFrom = new URLSearchParams();
qsFrom.set('from', `eq.${String(norm)}`);
qsFrom.set('order', 'created_at.desc');
const urlFrom = `${(ENV_CONFIG as any).REST}/messages?${qsFrom.toString()}`;
const rf = await fetch(urlFrom, { method: 'GET', headers });
const dataFrom = await rf.json().catch(() => []);
if (Array.isArray(dataFrom) && dataFrom.length) {
data = dataFrom;
} else {
// se nada, tenta `to` (caso o provedor grave a direção inversa)
const qsTo = new URLSearchParams();
qsTo.set('to', `eq.${String(norm)}`);
qsTo.set('order', 'created_at.desc');
const urlTo = `${(ENV_CONFIG as any).REST}/messages?${qsTo.toString()}`;
const rt = await fetch(urlTo, { method: 'GET', headers });
const dataTo = await rt.json().catch(() => []);
if (Array.isArray(dataTo) && dataTo.length) data = dataTo;
}
}
} catch (phoneErr) {
// não bloqueara o fluxo principal; apenas log
console.warn('[ProfissionalPage] fallback por telefone falhou', phoneErr);
}
}
setCommResponses(Array.isArray(data) ? data : []);
} catch (e: any) {
setCommResponsesError(String(e?.message || e || 'Falha ao buscar respostas'));
setCommResponses([]);
} finally {
setCommResponsesLoading(false);
}
};
const handleEditarLaudo = (paciente: any) => {
@ -2638,6 +2704,9 @@ const ProfissionalPage = () => {
// Use a sentinel value "__none" for the "-- nenhum --" choice and map it to null here.
const v = val === "__none" ? null : (val || null);
setCommPatientId(v);
// clear previous responses when changing selection
setCommResponses([]);
setCommResponsesError(null);
if (!v) {
setCommPhoneNumber('');
return;
@ -2655,6 +2724,8 @@ const ProfissionalPage = () => {
console.warn('[ProfissionalPage] erro ao preencher telefone do paciente selecionado', e);
setCommPhoneNumber('');
}
// carregar respostas do paciente selecionado
void loadCommResponses(String(v));
}}
>
<SelectTrigger className="w-full">
@ -2686,6 +2757,35 @@ const ProfissionalPage = () => {
{smsSending ? 'Enviando...' : 'Enviar SMS'}
</Button>
</div>
{/* Respostas do paciente */}
<div className="mt-6 border-t border-border pt-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold">Últimas respostas do paciente</h3>
<div>
<Button size="sm" variant="outline" onClick={() => void loadCommResponses()} disabled={!commPatientId || commResponsesLoading}>
{commResponsesLoading ? 'Atualizando...' : 'Atualizar respostas'}
</Button>
</div>
</div>
{commResponsesLoading ? (
<div className="text-sm text-muted-foreground">Carregando respostas...</div>
) : commResponsesError ? (
<div className="text-sm text-red-500">{commResponsesError}</div>
) : (commResponses && commResponses.length) ? (
<div className="space-y-2">
{commResponses.map((m:any) => (
<div key={m.id} className="p-3 rounded border border-border bg-muted/10">
<div className="text-xs text-muted-foreground">{m.created_at ? new Date(m.created_at).toLocaleString() : ''}</div>
<div className="mt-1 whitespace-pre-wrap">{m.body ?? m.content ?? m.message ?? '-'}</div>
</div>
))}
</div>
) : (
<div className="text-sm text-muted-foreground">Nenhuma resposta encontrada para o paciente selecionado.</div>
)}
</div>
</div>
</div>
</div>