Merge branch 'feature/fix-report-endpoints' into feature/add-duties-enpoints
This commit is contained in:
commit
25a0df3dca
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
Report,
|
||||
CreateReportData,
|
||||
UpdateReportData,
|
||||
ApiError
|
||||
UpdateReportData
|
||||
} from '@/types/report-types';
|
||||
import {
|
||||
listarRelatorios,
|
||||
@ -16,6 +15,7 @@ import {
|
||||
listarRelatoriosPorPaciente,
|
||||
listarRelatoriosPorMedico
|
||||
} from '@/lib/reports';
|
||||
import { buscarPacientePorId, buscarMedicoPorId, buscarPacientesPorIds, buscarMedicosPorIds } from '@/lib/api';
|
||||
|
||||
interface UseReportsReturn {
|
||||
// Estados
|
||||
@ -42,6 +42,12 @@ export function useReports(): UseReportsReturn {
|
||||
const [selectedReport, setSelectedReport] = useState<Report | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// Caches em memória para evitar múltiplas buscas pelo mesmo ID durante a sessão
|
||||
const patientsCacheRef = (globalThis as any).__reportsPatientsCache__ || new Map<string, any>();
|
||||
const doctorsCacheRef = (globalThis as any).__reportsDoctorsCache__ || new Map<string, any>();
|
||||
// store back to globalThis so cache persiste entre hot reloads
|
||||
(globalThis as any).__reportsPatientsCache__ = patientsCacheRef;
|
||||
(globalThis as any).__reportsDoctorsCache__ = doctorsCacheRef;
|
||||
|
||||
// Função para tratar erros
|
||||
const handleError = useCallback((error: any) => {
|
||||
@ -63,7 +69,134 @@ export function useReports(): UseReportsReturn {
|
||||
|
||||
try {
|
||||
const data = await listarRelatorios();
|
||||
setReports(data);
|
||||
|
||||
// Enriquecer relatórios: quando o backend retorna apenas IDs para paciente/executante,
|
||||
// buscamos os detalhes (nome, cpf, etc) em batch e anexamos como `paciente` e `executante`.
|
||||
const reportsWithRelations = await (async (arr: any[]) => {
|
||||
if (!arr || !arr.length) return arr;
|
||||
|
||||
const patientIds: string[] = [];
|
||||
const doctorIds: string[] = [];
|
||||
|
||||
for (const r of arr) {
|
||||
const pid = r.patient_id ?? r.patient ?? r.paciente;
|
||||
if (pid && typeof pid === 'string' && !patientIds.includes(pid) && !patientsCacheRef.has(String(pid))) patientIds.push(pid);
|
||||
|
||||
const did = r.requested_by ?? r.created_by ?? r.executante;
|
||||
if (did && typeof did === 'string' && !doctorIds.includes(did) && !doctorsCacheRef.has(String(did))) doctorIds.push(did);
|
||||
}
|
||||
|
||||
const patientsById = new Map<string, any>();
|
||||
if (patientIds.length) {
|
||||
try {
|
||||
const patients = await buscarPacientesPorIds(patientIds);
|
||||
for (const p of patients) {
|
||||
patientsById.set(String(p.id), p);
|
||||
patientsCacheRef.set(String(p.id), p);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore batch failure
|
||||
}
|
||||
}
|
||||
|
||||
// fallback individual para quaisquer IDs que não foram resolvidos no batch
|
||||
const unresolvedPatientIds = patientIds.filter(id => !patientsById.has(String(id)) && !patientsCacheRef.has(String(id)));
|
||||
if (unresolvedPatientIds.length) {
|
||||
await Promise.all(unresolvedPatientIds.map(async (id) => {
|
||||
try {
|
||||
const p = await buscarPacientePorId(id);
|
||||
if (p) {
|
||||
patientsById.set(String(id), p);
|
||||
patientsCacheRef.set(String(id), p);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore individual failure
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const doctorsById = new Map<string, any>();
|
||||
if (doctorIds.length) {
|
||||
try {
|
||||
const doctors = await buscarMedicosPorIds(doctorIds);
|
||||
for (const d of doctors) {
|
||||
doctorsById.set(String(d.id), d);
|
||||
doctorsCacheRef.set(String(d.id), d);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const unresolvedDoctorIds = doctorIds.filter(id => !doctorsById.has(String(id)) && !doctorsCacheRef.has(String(id)));
|
||||
if (unresolvedDoctorIds.length) {
|
||||
await Promise.all(unresolvedDoctorIds.map(async (id) => {
|
||||
try {
|
||||
const d = await buscarMedicoPorId(id);
|
||||
if (d) {
|
||||
doctorsById.set(String(id), d);
|
||||
doctorsCacheRef.set(String(id), d);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const mapped = arr.map((r) => {
|
||||
const copy = { ...r } as any;
|
||||
|
||||
// Heurísticas: prefira nomes já presentes no payload do relatório
|
||||
const possiblePatientName = r.patient_name ?? r.patient_full_name ?? r.patientFullName ?? r.paciente?.full_name ?? r.paciente?.nome ?? r.patient?.full_name ?? r.patient?.nome;
|
||||
if (possiblePatientName) {
|
||||
copy.paciente = copy.paciente || {};
|
||||
copy.paciente.full_name = possiblePatientName;
|
||||
}
|
||||
|
||||
const pid = r.patient_id ?? r.patient ?? r.paciente;
|
||||
if (!copy.paciente && pid) {
|
||||
if (patientsById.has(String(pid))) copy.paciente = patientsById.get(String(pid));
|
||||
else if (patientsCacheRef.has(String(pid))) copy.paciente = patientsCacheRef.get(String(pid));
|
||||
}
|
||||
|
||||
// Executante: prefira campos de nome já fornecidos
|
||||
const possibleExecutorName = r.requested_by_name ?? r.requester_name ?? r.requestedByName ?? r.executante_name ?? r.executante?.nome ?? r.executante;
|
||||
if (possibleExecutorName) {
|
||||
copy.executante = possibleExecutorName;
|
||||
} else {
|
||||
const did = r.requested_by ?? r.created_by ?? r.executante;
|
||||
if (did) {
|
||||
if (doctorsById.has(String(did))) copy.executante = doctorsById.get(String(did))?.full_name ?? doctorsById.get(String(did))?.nome ?? copy.executante;
|
||||
else if (doctorsCacheRef.has(String(did))) copy.executante = doctorsCacheRef.get(String(did))?.full_name ?? doctorsCacheRef.get(String(did))?.nome ?? copy.executante;
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
});
|
||||
|
||||
// Debug: identificar relatórios que ainda não tiveram paciente/doctor resolvido
|
||||
try {
|
||||
const unresolvedPatients: string[] = [];
|
||||
const unresolvedDoctors: string[] = [];
|
||||
for (const r of mapped) {
|
||||
const pid = r.patient_id ?? r.patient ?? r.paciente;
|
||||
if (pid && !r.paciente) unresolvedPatients.push(String(pid));
|
||||
const did = r.requested_by ?? r.created_by ?? r.executante;
|
||||
// note: if executante was resolved to a name, r.executante will be string name; if still ID, it may be ID
|
||||
if (did && (typeof r.executante === 'undefined' || (typeof r.executante === 'string' && r.executante.length > 30 && r.executante.includes('-')))) {
|
||||
unresolvedDoctors.push(String(did));
|
||||
}
|
||||
}
|
||||
if (unresolvedPatients.length) console.warn('[useReports] Pacientes não resolvidos (após batch+fallback):', Array.from(new Set(unresolvedPatients)).slice(0,50));
|
||||
if (unresolvedDoctors.length) console.warn('[useReports] Executantes não resolvidos (após batch+fallback):', Array.from(new Set(unresolvedDoctors)).slice(0,50));
|
||||
} catch (e) {
|
||||
// ignore logging errors
|
||||
}
|
||||
|
||||
return mapped;
|
||||
})(data as any[]);
|
||||
|
||||
setReports(reportsWithRelations || data);
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
} finally {
|
||||
|
||||
@ -14,11 +14,8 @@ export type ApiOk<T = any> = {
|
||||
total?: number;
|
||||
};
|
||||
};
|
||||
|
||||
// ===== TIPOS COMUNS =====
|
||||
export type Endereco = {
|
||||
cep?: string;
|
||||
logradouro?: string;
|
||||
numero?: string;
|
||||
complemento?: string;
|
||||
bairro?: string;
|
||||
@ -182,17 +179,62 @@ function withPrefer(h: Record<string, string>, prefer: string) {
|
||||
return { ...h, Prefer: prefer };
|
||||
}
|
||||
|
||||
// Helper: fetch seguro que tenta urls alternativas caso a requisição primária falhe
|
||||
async function fetchWithFallback<T = any>(url: string, headers: Record<string, string>, altUrls?: string[]): Promise<T | null> {
|
||||
try {
|
||||
console.debug('[fetchWithFallback] tentando URL:', url);
|
||||
const res = await fetch(url, { method: 'GET', headers });
|
||||
if (res.ok) {
|
||||
return await parse<T>(res);
|
||||
}
|
||||
const raw = await res.clone().text().catch(() => '');
|
||||
console.warn('[fetchWithFallback] falha na URL primária:', url, 'status:', res.status, 'raw:', raw);
|
||||
if (!altUrls || !altUrls.length) return null;
|
||||
for (const alt of altUrls) {
|
||||
try {
|
||||
console.debug('[fetchWithFallback] tentando fallback URL:', alt);
|
||||
const r2 = await fetch(alt, { method: 'GET', headers });
|
||||
if (r2.ok) return await parse<T>(r2);
|
||||
const raw2 = await r2.clone().text().catch(() => '');
|
||||
console.warn('[fetchWithFallback] fallback falhou:', alt, 'status:', r2.status, 'raw:', raw2);
|
||||
} catch (e) {
|
||||
console.warn('[fetchWithFallback] erro no fallback:', alt, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.warn('[fetchWithFallback] erro fetch primario:', url, e);
|
||||
if (!altUrls || !altUrls.length) return null;
|
||||
for (const alt of altUrls) {
|
||||
try {
|
||||
const r2 = await fetch(alt, { method: 'GET', headers });
|
||||
if (r2.ok) return await parse<T>(r2);
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse genérico
|
||||
async function parse<T>(res: Response): Promise<T> {
|
||||
let json: any = null;
|
||||
try {
|
||||
json = await res.json();
|
||||
} catch (err) {
|
||||
console.error("Erro ao parsear a resposta:", err);
|
||||
console.error("Erro ao parsear a resposta como JSON:", err);
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("[API ERROR]", res.url, res.status, json);
|
||||
// Tenta também ler o body como texto cru para obter mensagens detalhadas
|
||||
let rawText = '';
|
||||
try {
|
||||
rawText = await res.clone().text();
|
||||
} catch (tErr) {
|
||||
// ignore
|
||||
}
|
||||
console.error("[API ERROR]", res.url, res.status, json, "raw:", rawText);
|
||||
const code = (json && (json.error?.code || json.code)) ?? res.status;
|
||||
const msg = (json && (json.error?.message || json.message || json.error)) ?? res.statusText;
|
||||
|
||||
@ -270,14 +312,19 @@ export async function listarPacientes(params?: {
|
||||
}): Promise<Paciente[]> {
|
||||
const qs = new URLSearchParams();
|
||||
if (params?.q) qs.set("q", params.q);
|
||||
let url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients`;
|
||||
const qsString = qs.toString();
|
||||
if (qsString) url += `?${qsString}`;
|
||||
|
||||
// Use baseHeaders() so the current user token (from local/session storage) and apikey are sent
|
||||
const headers: HeadersInit = {
|
||||
...baseHeaders(),
|
||||
...rangeHeaders(params?.page, params?.limit),
|
||||
};
|
||||
|
||||
const url = `${REST}/patients${qs.toString() ? `?${qs.toString()}` : ""}`;
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
...baseHeaders(),
|
||||
...rangeHeaders(params?.page, params?.limit),
|
||||
},
|
||||
headers,
|
||||
});
|
||||
return await parse<Paciente[]>(res);
|
||||
}
|
||||
@ -324,8 +371,12 @@ export async function buscarPacientes(termo: string): Promise<Paciente[]> {
|
||||
// Executa as buscas e combina resultados únicos
|
||||
for (const query of queries) {
|
||||
try {
|
||||
const url = `${REST}/patients?${query}&limit=10`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?${query}&limit=10`;
|
||||
const headers = baseHeaders();
|
||||
const masked = (headers['Authorization'] as string | undefined) ? `${String(headers['Authorization']).slice(0,6)}...${String(headers['Authorization']).slice(-6)}` : null;
|
||||
console.debug('[buscarPacientes] URL:', url);
|
||||
console.debug('[buscarPacientes] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
|
||||
const res = await fetch(url, { method: "GET", headers });
|
||||
const arr = await parse<Paciente[]>(res);
|
||||
|
||||
if (arr?.length > 0) {
|
||||
@ -345,20 +396,159 @@ export async function buscarPacientes(termo: string): Promise<Paciente[]> {
|
||||
}
|
||||
|
||||
export async function buscarPacientePorId(id: string | number): Promise<Paciente> {
|
||||
// Se for string e não for só número, coloca aspas duplas (para UUID/texto)
|
||||
let idParam: string | number = id;
|
||||
if (typeof id === 'string' && isNaN(Number(id))) {
|
||||
idParam = `\"${id}\"`;
|
||||
const idParam = String(id);
|
||||
const headers = baseHeaders();
|
||||
|
||||
// Tenta buscar por id (UUID ou string) primeiro
|
||||
try {
|
||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?id=eq.${encodeURIComponent(idParam)}`;
|
||||
console.debug('[buscarPacientePorId] tentando por id URL:', url);
|
||||
const arr = await fetchWithFallback<Paciente[]>(url, headers);
|
||||
if (arr && arr.length) return arr[0];
|
||||
} catch (e) {
|
||||
// continue to next strategies
|
||||
}
|
||||
const url = `${REST}/patients?id=eq.${idParam}`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const arr = await parse<Paciente[]>(res);
|
||||
if (!arr?.length) throw new Error("404: Paciente não encontrado");
|
||||
return arr[0];
|
||||
|
||||
// Se for string e não numérico, talvez foi passado um nome — tentar por full_name / social_name
|
||||
if (typeof id === 'string' && isNaN(Number(id))) {
|
||||
const q = encodeURIComponent(String(id));
|
||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?full_name=ilike.*${q}*&limit=5`;
|
||||
const alt = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?social_name=ilike.*${q}*&limit=5`;
|
||||
console.debug('[buscarPacientePorId] tentando por nome URL:', url);
|
||||
const arr2 = await fetchWithFallback<Paciente[]>(url, headers, [alt]);
|
||||
if (arr2 && arr2.length) return arr2[0];
|
||||
}
|
||||
|
||||
throw new Error('404: Paciente não encontrado');
|
||||
}
|
||||
|
||||
// ===== RELATÓRIOS =====
|
||||
export type Report = {
|
||||
id: string;
|
||||
patient_id?: string;
|
||||
order_number?: string;
|
||||
exam?: string;
|
||||
diagnosis?: string;
|
||||
conclusion?: string;
|
||||
cid_code?: string;
|
||||
content_html?: string;
|
||||
content_json?: any;
|
||||
status?: string;
|
||||
requested_by?: string;
|
||||
due_at?: string;
|
||||
hide_date?: boolean;
|
||||
hide_signature?: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
created_by?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Buscar relatório por ID (tenta múltiplas estratégias: id, order_number, patient_id)
|
||||
* Retorna o primeiro relatório encontrado ou lança erro 404 quando não achar.
|
||||
*/
|
||||
export async function buscarRelatorioPorId(id: string | number): Promise<Report> {
|
||||
const sId = String(id);
|
||||
const headers = baseHeaders();
|
||||
|
||||
// 1) tenta por id (UUID ou campo id)
|
||||
try {
|
||||
const urlById = `${REST}/reports?id=eq.${encodeURIComponent(sId)}`;
|
||||
console.debug('[buscarRelatorioPorId] tentando por id URL:', urlById);
|
||||
const arr = await fetchWithFallback<Report[]>(urlById, headers);
|
||||
if (arr && arr.length) return arr[0];
|
||||
} catch (e) {
|
||||
console.warn('[buscarRelatorioPorId] falha ao buscar por id:', e);
|
||||
}
|
||||
|
||||
// 2) tenta por order_number (caso o usuário cole um código legível)
|
||||
try {
|
||||
const urlByOrder = `${REST}/reports?order_number=eq.${encodeURIComponent(sId)}`;
|
||||
console.debug('[buscarRelatorioPorId] tentando por order_number URL:', urlByOrder);
|
||||
const arr2 = await fetchWithFallback<Report[]>(urlByOrder, headers);
|
||||
if (arr2 && arr2.length) return arr2[0];
|
||||
} catch (e) {
|
||||
console.warn('[buscarRelatorioPorId] falha ao buscar por order_number:', e);
|
||||
}
|
||||
|
||||
// 3) tenta por patient_id (caso o usuário passe um patient_id em vez do report id)
|
||||
try {
|
||||
const urlByPatient = `${REST}/reports?patient_id=eq.${encodeURIComponent(sId)}`;
|
||||
console.debug('[buscarRelatorioPorId] tentando por patient_id URL:', urlByPatient);
|
||||
const arr3 = await fetchWithFallback<Report[]>(urlByPatient, headers);
|
||||
if (arr3 && arr3.length) return arr3[0];
|
||||
} catch (e) {
|
||||
console.warn('[buscarRelatorioPorId] falha ao buscar por patient_id:', e);
|
||||
}
|
||||
|
||||
// Não encontrado
|
||||
throw new Error('404: Relatório não encontrado');
|
||||
}
|
||||
|
||||
|
||||
// Buscar vários pacientes por uma lista de IDs (usa query in.(...))
|
||||
export async function buscarPacientesPorIds(ids: Array<string | number>): Promise<Paciente[]> {
|
||||
if (!ids || !ids.length) return [];
|
||||
// Separe valores que parecem UUIDs daqueles que são nomes/texto
|
||||
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
||||
const uuids: string[] = [];
|
||||
const names: string[] = [];
|
||||
for (const id of ids) {
|
||||
const s = String(id).trim();
|
||||
if (!s) continue;
|
||||
if (uuidRegex.test(s)) uuids.push(s);
|
||||
else names.push(s);
|
||||
}
|
||||
|
||||
const results: Paciente[] = [];
|
||||
|
||||
// Buscar por UUIDs (coluna id)
|
||||
if (uuids.length) {
|
||||
const uuidsParam = uuids.join(',');
|
||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?id=in.(${uuidsParam})&limit=100`;
|
||||
try {
|
||||
const res = await fetch(url, { method: 'GET', headers: baseHeaders() });
|
||||
const arr = await parse<Paciente[]>(res);
|
||||
if (arr && arr.length) results.push(...arr);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar por nomes (coluna full_name)
|
||||
if (names.length) {
|
||||
// Em vez de usar in.(...) (que exige aspas e quebra com encoding),
|
||||
// fazemos uma requisição por nome usando ilike para cada nome.
|
||||
for (const name of names) {
|
||||
try {
|
||||
const q = encodeURIComponent(name);
|
||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?full_name=ilike.*${q}*&limit=100`;
|
||||
const alt = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?social_name=ilike.*${q}*&limit=100`;
|
||||
const headers = baseHeaders();
|
||||
console.debug('[buscarPacientesPorIds] URL (patient by name):', url);
|
||||
const arr = await fetchWithFallback<Paciente[]>(url, headers, [alt]);
|
||||
if (arr && arr.length) results.push(...arr);
|
||||
} catch (e) {
|
||||
// ignore individual name failures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remover duplicados pelo id
|
||||
const seen = new Set<string>();
|
||||
const unique: Paciente[] = [];
|
||||
for (const p of results) {
|
||||
if (!p || !p.id) continue;
|
||||
if (!seen.has(p.id)) {
|
||||
seen.add(p.id);
|
||||
unique.push(p);
|
||||
}
|
||||
}
|
||||
return unique;
|
||||
}
|
||||
|
||||
export async function criarPaciente(input: PacienteInput): Promise<Paciente> {
|
||||
const url = `${REST}/patients`;
|
||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients`;
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
|
||||
@ -369,7 +559,7 @@ export async function criarPaciente(input: PacienteInput): Promise<Paciente> {
|
||||
}
|
||||
|
||||
export async function atualizarPaciente(id: string | number, input: PacienteInput): Promise<Paciente> {
|
||||
const url = `${REST}/patients?id=eq.${id}`;
|
||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?id=eq.${id}`;
|
||||
const res = await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
|
||||
@ -497,7 +687,11 @@ export async function buscarMedicos(termo: string): Promise<Medico[]> {
|
||||
for (const query of queries) {
|
||||
try {
|
||||
const url = `${REST}/doctors?${query}&limit=10`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const headers = baseHeaders();
|
||||
const masked = (headers['Authorization'] as string | undefined) ? `${String(headers['Authorization']).slice(0,6)}...${String(headers['Authorization']).slice(-6)}` : null;
|
||||
console.debug('[buscarMedicos] URL:', url);
|
||||
console.debug('[buscarMedicos] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
|
||||
const res = await fetch(url, { method: 'GET', headers });
|
||||
const arr = await parse<Medico[]>(res);
|
||||
|
||||
if (arr?.length > 0) {
|
||||
@ -516,52 +710,134 @@ export async function buscarMedicos(termo: string): Promise<Medico[]> {
|
||||
return results.slice(0, 20); // Limita a 20 resultados
|
||||
}
|
||||
|
||||
export async function buscarMedicoPorId(id: string | number): Promise<Medico> {
|
||||
export async function buscarMedicoPorId(id: string | number): Promise<Medico | null> {
|
||||
// Primeiro tenta buscar no Supabase (dados reais)
|
||||
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
||||
const isString = typeof id === 'string';
|
||||
const sId = String(id);
|
||||
|
||||
// Helper para escape de aspas
|
||||
const escapeQuotes = (v: string) => v.replace(/"/g, '\\"');
|
||||
|
||||
try {
|
||||
const url = `${REST}/doctors?id=eq.${id}`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const arr = await parse<Medico[]>(res);
|
||||
if (arr && arr.length > 0) {
|
||||
console.log('Médico encontrado no Supabase:', arr[0]);
|
||||
console.log('Campo especialidade no médico:', {
|
||||
especialidade: arr[0].especialidade,
|
||||
specialty: (arr[0] as any).specialty,
|
||||
hasEspecialidade: !!arr[0].especialidade,
|
||||
hasSpecialty: !!((arr[0] as any).specialty)
|
||||
});
|
||||
return arr[0];
|
||||
// 1) Se parece UUID, busca por id direto
|
||||
if (isString && uuidRegex.test(sId)) {
|
||||
const url = `${REST}/doctors?id=eq.${encodeURIComponent(sId)}`;
|
||||
console.debug('[buscarMedicoPorId] tentando por id URL:', url);
|
||||
const arr = await fetchWithFallback<Medico[]>(url, baseHeaders());
|
||||
if (arr && arr.length > 0) return arr[0];
|
||||
}
|
||||
|
||||
// 2) Se for string não numérica (um nome), tente buscar por full_name e nome_social
|
||||
if (isString && isNaN(Number(sId))) {
|
||||
const quoted = `"${escapeQuotes(sId)}"`;
|
||||
// tentar por full_name usando ilike para evitar 400 com espaços/caracteres
|
||||
try {
|
||||
const q = encodeURIComponent(sId);
|
||||
const url = `${REST}/doctors?full_name=ilike.*${q}*&limit=5`;
|
||||
const alt = `${REST}/doctors?nome_social=ilike.*${q}*&limit=5`;
|
||||
const arr = await fetchWithFallback<Medico[]>(url, baseHeaders(), [alt, `${REST}/doctors?social_name=ilike.*${q}*&limit=5`]);
|
||||
if (arr && arr.length > 0) return arr[0];
|
||||
} catch (e) {
|
||||
// ignore and try next
|
||||
}
|
||||
|
||||
// tentar nome_social também com ilike
|
||||
// (já tratado acima via fetchWithFallback)
|
||||
}
|
||||
|
||||
// 3) Por fim, tentar buscar por id (como último recurso)
|
||||
try {
|
||||
const idParam = encodeURIComponent(sId);
|
||||
const url3 = `${REST}/doctors?id=eq.${idParam}`;
|
||||
const arr3 = await fetchWithFallback<Medico[]>(url3, baseHeaders());
|
||||
if (arr3 && arr3.length > 0) return arr3[0];
|
||||
} catch (e) {
|
||||
// continue to mock fallback
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar no Supabase, tentando mock API:', error);
|
||||
}
|
||||
|
||||
|
||||
// Se não encontrar no Supabase, tenta o mock API
|
||||
try {
|
||||
const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctors/${id}`;
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
const mockUrl = `https://yuanqog.com/m1/1053378-0-default/rest/v1/doctors/${encodeURIComponent(String(id))}`;
|
||||
console.debug('[buscarMedicoPorId] tentando mock API URL:', mockUrl);
|
||||
try {
|
||||
const medico = await fetchWithFallback<any>(mockUrl, { Accept: 'application/json' });
|
||||
if (medico) {
|
||||
console.log('✅ Médico encontrado no Mock API:', medico);
|
||||
return medico as Medico;
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
throw new Error("404: Médico não encontrado");
|
||||
}
|
||||
throw new Error(`Erro ao buscar médico: ${res.status} ${res.statusText}`);
|
||||
// fetchWithFallback returned null -> not found
|
||||
console.warn('[buscarMedicoPorId] mock API returned no result for id:', id);
|
||||
return null;
|
||||
} catch (fetchErr) {
|
||||
console.warn('[buscarMedicoPorId] mock API fetch failed or returned no result:', fetchErr);
|
||||
return null;
|
||||
}
|
||||
|
||||
const medico = await res.json();
|
||||
console.log('Médico encontrado no Mock API:', medico);
|
||||
return medico as Medico;
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar médico em ambas as APIs:', error);
|
||||
throw new Error("404: Médico não encontrado");
|
||||
console.error('❌ Erro ao buscar médico em ambas as APIs:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar vários médicos por IDs
|
||||
export async function buscarMedicosPorIds(ids: Array<string | number>): Promise<Medico[]> {
|
||||
if (!ids || !ids.length) return [];
|
||||
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
||||
const uuids: string[] = [];
|
||||
const names: string[] = [];
|
||||
for (const id of ids) {
|
||||
const s = String(id).trim();
|
||||
if (!s) continue;
|
||||
if (uuidRegex.test(s)) uuids.push(s);
|
||||
else names.push(s);
|
||||
}
|
||||
|
||||
const results: Medico[] = [];
|
||||
|
||||
if (uuids.length) {
|
||||
const uuidsParam = uuids.join(',');
|
||||
const url = `${REST}/doctors?id=in.(${uuidsParam})&limit=200`;
|
||||
try {
|
||||
const res = await fetch(url, { method: 'GET', headers: baseHeaders() });
|
||||
const arr = await parse<Medico[]>(res);
|
||||
if (arr && arr.length) results.push(...arr);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (names.length) {
|
||||
// Evitar in.(...) com aspas — fazer uma requisição por nome usando ilike
|
||||
for (const name of names) {
|
||||
try {
|
||||
const q = encodeURIComponent(name);
|
||||
const url = `${REST}/doctors?full_name=ilike.*${q}*&limit=200`;
|
||||
const alt = `${REST}/doctors?nome_social=ilike.*${q}*&limit=200`;
|
||||
const headers = baseHeaders();
|
||||
console.debug('[buscarMedicosPorIds] URL (doctor by name):', url);
|
||||
const arr = await fetchWithFallback<Medico[]>(url, headers, [alt, `${REST}/doctors?social_name=ilike.*${q}*&limit=200`]);
|
||||
if (arr && arr.length) results.push(...arr);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
const unique: Medico[] = [];
|
||||
for (const d of results) {
|
||||
if (!d || !d.id) continue;
|
||||
if (!seen.has(d.id)) {
|
||||
seen.add(d.id);
|
||||
unique.push(d);
|
||||
}
|
||||
}
|
||||
return unique;
|
||||
}
|
||||
|
||||
// Dentro de lib/api.ts
|
||||
export async function criarMedico(input: MedicoInput): Promise<Medico> {
|
||||
console.log("Enviando os dados para a API:", input); // Log para depuração
|
||||
@ -1206,7 +1482,7 @@ export async function removerFotoMedico(_id: string | number): Promise<void> {}
|
||||
|
||||
// ===== PERFIS DE USUÁRIOS =====
|
||||
export async function listarPerfis(): Promise<Profile[]> {
|
||||
const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/profiles`;
|
||||
const url = `https://yuanq1/1053378-0-default/rest/v1/profiles`;
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: baseHeaders(),
|
||||
|
||||
@ -254,7 +254,7 @@ class HttpClient {
|
||||
}
|
||||
|
||||
// Instância única do cliente HTTP
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://mock.apidog.com/m1/1053378-0-default'
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://yuanqog.com/m1/1053378-0-default'
|
||||
export const httpClient = new HttpClient(API_BASE_URL)
|
||||
|
||||
export default httpClient
|
||||
@ -45,29 +45,39 @@ import {
|
||||
CreateReportData,
|
||||
UpdateReportData,
|
||||
ReportsResponse,
|
||||
ReportResponse,
|
||||
ApiError
|
||||
ReportResponse
|
||||
} from '@/types/report-types';
|
||||
|
||||
// URL base da API Mock
|
||||
const BASE_API_RELATORIOS = 'https://mock.apidog.com/m1/1053378-0-default/rest/v1/reports';
|
||||
// Definição local para ApiError
|
||||
type ApiError = {
|
||||
message: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
// URL base da API Supabase
|
||||
const BASE_API_RELATORIOS = 'https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports';
|
||||
|
||||
// Cabeçalhos base para as requisições Supabase
|
||||
function obterCabecalhos(token?: string): HeadersInit {
|
||||
// If token not passed explicitly, try the same fallbacks as lib/api.ts
|
||||
if (!token && typeof window !== 'undefined') {
|
||||
token =
|
||||
localStorage.getItem('auth_token') ||
|
||||
localStorage.getItem('token') ||
|
||||
sessionStorage.getItem('auth_token') ||
|
||||
sessionStorage.getItem('token') ||
|
||||
undefined;
|
||||
}
|
||||
|
||||
// Cabeçalhos base para as requisições
|
||||
function obterCabecalhos(): HeadersInit {
|
||||
const cabecalhos: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ',
|
||||
'Prefer': 'return=representation',
|
||||
};
|
||||
|
||||
// Adiciona token de autenticação do localStorage se disponível
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
cabecalhos['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
if (token) {
|
||||
cabecalhos['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return cabecalhos;
|
||||
}
|
||||
|
||||
@ -75,12 +85,15 @@ function obterCabecalhos(): HeadersInit {
|
||||
async function tratarRespostaApi<T>(resposta: Response): Promise<T> {
|
||||
if (!resposta.ok) {
|
||||
let mensagemErro = `HTTP ${resposta.status}: ${resposta.statusText}`;
|
||||
let rawText = '';
|
||||
try {
|
||||
const dadosErro = await resposta.json();
|
||||
rawText = await resposta.clone().text();
|
||||
const dadosErro = JSON.parse(rawText || '{}');
|
||||
mensagemErro = dadosErro.message || dadosErro.error || mensagemErro;
|
||||
} catch (e) {
|
||||
// Se não conseguir parsear como JSON, usa a mensagem de status HTTP
|
||||
// Se não conseguir parsear como JSON, manter rawText para debug
|
||||
}
|
||||
console.error('[tratarRespostaApi] response raw:', rawText);
|
||||
const erro: ApiError = {
|
||||
message: mensagemErro,
|
||||
code: resposta.status.toString(),
|
||||
@ -106,25 +119,44 @@ export async function listarRelatorios(filtros?: { patient_id?: string; status?:
|
||||
url += `?${params.toString()}`;
|
||||
}
|
||||
|
||||
// Busca o token do usuário (compatível com lib/api.ts keys)
|
||||
let token: string | undefined = undefined;
|
||||
if (typeof window !== 'undefined') {
|
||||
token =
|
||||
localStorage.getItem('auth_token') ||
|
||||
localStorage.getItem('token') ||
|
||||
sessionStorage.getItem('auth_token') ||
|
||||
sessionStorage.getItem('token') ||
|
||||
undefined;
|
||||
}
|
||||
|
||||
// Monta cabeçalhos conforme cURL
|
||||
const cabecalhos: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ',
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
cabecalhos['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
if (token) {
|
||||
cabecalhos['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// Logs de depuração (mask token)
|
||||
const masked = token ? `${token.slice(0, 6)}...${token.slice(-6)}` : null;
|
||||
console.log('[listarRelatorios] URL:', url);
|
||||
console.log('[listarRelatorios] Authorization (masked):', masked);
|
||||
console.log('[listarRelatorios] Headers (masked):', {
|
||||
...cabecalhos,
|
||||
Authorization: cabecalhos['Authorization'] ? '<<masked>>' : undefined,
|
||||
});
|
||||
|
||||
const resposta = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: cabecalhos,
|
||||
});
|
||||
console.log('[listarRelatorios] Status:', resposta.status, resposta.statusText);
|
||||
const dados = await resposta.json().catch(() => null);
|
||||
console.log('[listarRelatorios] Payload:', dados);
|
||||
if (!resposta.ok) throw new Error('Erro ao buscar relatórios');
|
||||
const dados = await resposta.json();
|
||||
if (Array.isArray(dados)) return dados;
|
||||
if (dados && Array.isArray(dados.data)) return dados.data;
|
||||
for (const chave in dados) {
|
||||
@ -139,13 +171,15 @@ export async function listarRelatorios(filtros?: { patient_id?: string; status?:
|
||||
export async function buscarRelatorioPorId(id: string): Promise<Report> {
|
||||
try {
|
||||
console.log('🔍 [API RELATÓRIOS] Buscando relatório ID:', id);
|
||||
const resposta = await fetch(`${BASE_API_RELATORIOS}/${id}`, {
|
||||
const resposta = await fetch(`${BASE_API_RELATORIOS}?id=eq.${id}`, {
|
||||
method: 'GET',
|
||||
headers: obterCabecalhos(),
|
||||
});
|
||||
const resultado = await tratarRespostaApi<ReportResponse>(resposta);
|
||||
console.log('✅ [API RELATÓRIOS] Relatório encontrado:', resultado.data);
|
||||
return resultado.data;
|
||||
const resultado = await tratarRespostaApi<Report[]>(resposta);
|
||||
const relatorio = Array.isArray(resultado) && resultado.length > 0 ? resultado[0] : null;
|
||||
console.log('✅ [API RELATÓRIOS] Relatório encontrado:', relatorio);
|
||||
if (!relatorio) throw new Error('Relatório não encontrado');
|
||||
return relatorio;
|
||||
} catch (erro) {
|
||||
console.error('❌ [API RELATÓRIOS] Erro ao buscar relatório:', erro);
|
||||
throw erro;
|
||||
@ -155,60 +189,39 @@ export async function buscarRelatorioPorId(id: string): Promise<Report> {
|
||||
/**
|
||||
* Cria um novo relatório médico
|
||||
*/
|
||||
export async function criarRelatorio(dadosRelatorio: CreateReportData): Promise<Report> {
|
||||
try {
|
||||
console.log('📝 [API RELATÓRIOS] Criando novo relatório...');
|
||||
console.log('📤 [API RELATÓRIOS] Dados enviados:', dadosRelatorio);
|
||||
const resposta = await fetch(BASE_API_RELATORIOS, {
|
||||
method: 'POST',
|
||||
headers: obterCabecalhos(),
|
||||
body: JSON.stringify(dadosRelatorio),
|
||||
});
|
||||
console.log('📝 [API RELATÓRIOS] Status da criação:', resposta.status);
|
||||
console.log('📝 [API RELATÓRIOS] Response OK:', resposta.ok);
|
||||
console.log('📝 [API RELATÓRIOS] Response URL:', resposta.url);
|
||||
if (!resposta.ok) {
|
||||
let mensagemErro = `HTTP ${resposta.status}: ${resposta.statusText}`;
|
||||
try {
|
||||
const dadosErro = await resposta.json();
|
||||
mensagemErro = dadosErro.message || dadosErro.error || mensagemErro;
|
||||
console.log('📝 [API RELATÓRIOS] Erro da API:', dadosErro);
|
||||
} catch (e) {
|
||||
console.log('📝 [API RELATÓRIOS] Não foi possível parsear erro como JSON');
|
||||
}
|
||||
const erro: ApiError = {
|
||||
message: mensagemErro,
|
||||
code: resposta.status.toString(),
|
||||
};
|
||||
throw erro;
|
||||
export async function criarRelatorio(dadosRelatorio: CreateReportData, token?: string): Promise<Report> {
|
||||
const headers = obterCabecalhos(token);
|
||||
const masked = (headers as any)['Authorization'] ? String((headers as any)['Authorization']).replace(/Bearer\s+(.+)/, 'Bearer <token_masked>') : null;
|
||||
console.log('[criarRelatorio] POST', BASE_API_RELATORIOS);
|
||||
console.log('[criarRelatorio] Headers (masked):', { ...headers, Authorization: masked });
|
||||
|
||||
const resposta = await fetch(BASE_API_RELATORIOS, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(dadosRelatorio),
|
||||
});
|
||||
console.log('[criarRelatorio] Status:', resposta.status, resposta.statusText);
|
||||
if (!resposta.ok) {
|
||||
let mensagemErro = `HTTP ${resposta.status}: ${resposta.statusText}`;
|
||||
try {
|
||||
const dadosErro = await resposta.json();
|
||||
mensagemErro = dadosErro.message || dadosErro.error || mensagemErro;
|
||||
console.error('[criarRelatorio] error body:', dadosErro);
|
||||
} catch (e) {
|
||||
console.error('[criarRelatorio] erro ao parsear body de erro');
|
||||
}
|
||||
const resultadoBruto = await resposta.json();
|
||||
console.log('📝 [API RELATÓRIOS] Resposta bruta da criação:', resultadoBruto);
|
||||
console.log('📝 [API RELATÓRIOS] Tipo da resposta:', typeof resultadoBruto);
|
||||
console.log('📝 [API RELATÓRIOS] Chaves da resposta:', Object.keys(resultadoBruto || {}));
|
||||
let relatorioCriado: Report;
|
||||
// Verifica formato da resposta similar ao listarRelatorios
|
||||
if (resultadoBruto && resultadoBruto.data) {
|
||||
relatorioCriado = resultadoBruto.data;
|
||||
} else if (resultadoBruto && resultadoBruto.id) {
|
||||
relatorioCriado = resultadoBruto;
|
||||
} else if (Array.isArray(resultadoBruto) && resultadoBruto.length > 0) {
|
||||
relatorioCriado = resultadoBruto[0];
|
||||
} else {
|
||||
console.warn('📝 [API RELATÓRIOS] Formato de resposta inesperado, criando relatório local');
|
||||
relatorioCriado = {
|
||||
id: 'local-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...dadosRelatorio
|
||||
};
|
||||
}
|
||||
console.log('✅ [API RELATÓRIOS] Relatório processado:', relatorioCriado);
|
||||
return relatorioCriado;
|
||||
} catch (erro) {
|
||||
console.error('❌ [API RELATÓRIOS] Erro ao criar relatório:', erro);
|
||||
const erro: any = {
|
||||
message: mensagemErro,
|
||||
code: resposta.status.toString(),
|
||||
};
|
||||
throw erro;
|
||||
}
|
||||
const resultado = await resposta.json();
|
||||
// Supabase retorna array
|
||||
if (Array.isArray(resultado) && resultado.length > 0) {
|
||||
return resultado[0];
|
||||
}
|
||||
throw new Error('Resposta inesperada da API Supabase');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -218,14 +231,16 @@ export async function atualizarRelatorio(id: string, dadosRelatorio: UpdateRepor
|
||||
try {
|
||||
console.log('📝 [API RELATÓRIOS] Atualizando relatório ID:', id);
|
||||
console.log('📤 [API RELATÓRIOS] Dados:', dadosRelatorio);
|
||||
const resposta = await fetch(`${BASE_API_RELATORIOS}/${id}`, {
|
||||
const resposta = await fetch(`${BASE_API_RELATORIOS}?id=eq.${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: obterCabecalhos(),
|
||||
body: JSON.stringify(dadosRelatorio),
|
||||
});
|
||||
const resultado = await tratarRespostaApi<ReportResponse>(resposta);
|
||||
console.log('✅ [API RELATÓRIOS] Relatório atualizado:', resultado.data);
|
||||
return resultado.data;
|
||||
const resultado = await tratarRespostaApi<Report[]>(resposta);
|
||||
const relatorio = Array.isArray(resultado) && resultado.length > 0 ? resultado[0] : null;
|
||||
console.log('✅ [API RELATÓRIOS] Relatório atualizado:', relatorio);
|
||||
if (!relatorio) throw new Error('Relatório não encontrado');
|
||||
return relatorio;
|
||||
} catch (erro) {
|
||||
console.error('❌ [API RELATÓRIOS] Erro ao atualizar relatório:', erro);
|
||||
throw erro;
|
||||
@ -256,13 +271,18 @@ export async function deletarRelatorio(id: string): Promise<void> {
|
||||
export async function listarRelatoriosPorPaciente(idPaciente: string): Promise<Report[]> {
|
||||
try {
|
||||
console.log('👤 [API RELATÓRIOS] Buscando relatórios do paciente:', idPaciente);
|
||||
const resposta = await fetch(`${BASE_API_RELATORIOS}?patient_id=${idPaciente}`, {
|
||||
const url = `${BASE_API_RELATORIOS}?patient_id=eq.${idPaciente}`;
|
||||
const headers = obterCabecalhos();
|
||||
const masked = (headers as any)['Authorization'] ? `${String((headers as any)['Authorization']).slice(0,6)}...${String((headers as any)['Authorization']).slice(-6)}` : null;
|
||||
console.debug('[listarRelatoriosPorPaciente] URL:', url);
|
||||
console.debug('[listarRelatoriosPorPaciente] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
|
||||
const resposta = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: obterCabecalhos(),
|
||||
headers,
|
||||
});
|
||||
const resultado = await tratarRespostaApi<ReportsResponse>(resposta);
|
||||
console.log('✅ [API RELATÓRIOS] Relatórios do paciente encontrados:', resultado.data?.length || 0);
|
||||
return resultado.data || [];
|
||||
const resultado = await tratarRespostaApi<Report[]>(resposta);
|
||||
console.log('✅ [API RELATÓRIOS] Relatórios do paciente encontrados:', resultado.length);
|
||||
return resultado;
|
||||
} catch (erro) {
|
||||
console.error('❌ [API RELATÓRIOS] Erro ao buscar relatórios do paciente:', erro);
|
||||
throw erro;
|
||||
@ -275,13 +295,18 @@ export async function listarRelatoriosPorPaciente(idPaciente: string): Promise<R
|
||||
export async function listarRelatoriosPorMedico(idMedico: string): Promise<Report[]> {
|
||||
try {
|
||||
console.log('👨⚕️ [API RELATÓRIOS] Buscando relatórios do médico:', idMedico);
|
||||
const resposta = await fetch(`${BASE_API_RELATORIOS}?doctor_id=${idMedico}`, {
|
||||
const url = `${BASE_API_RELATORIOS}?requested_by=eq.${idMedico}`;
|
||||
const headers = obterCabecalhos();
|
||||
const masked = (headers as any)['Authorization'] ? `${String((headers as any)['Authorization']).slice(0,6)}...${String((headers as any)['Authorization']).slice(-6)}` : null;
|
||||
console.debug('[listarRelatoriosPorMedico] URL:', url);
|
||||
console.debug('[listarRelatoriosPorMedico] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
|
||||
const resposta = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: obterCabecalhos(),
|
||||
});
|
||||
const resultado = await tratarRespostaApi<ReportsResponse>(resposta);
|
||||
console.log('✅ [API RELATÓRIOS] Relatórios do médico encontrados:', resultado.data?.length || 0);
|
||||
return resultado.data || [];
|
||||
const resultado = await tratarRespostaApi<Report[]>(resposta);
|
||||
console.log('✅ [API RELATÓRIOS] Relatórios do médico encontrados:', resultado.length);
|
||||
return resultado;
|
||||
} catch (erro) {
|
||||
console.error('❌ [API RELATÓRIOS] Erro ao buscar relatórios do médico:', erro);
|
||||
throw erro;
|
||||
|
||||
16
susconecta/package-lock.json
generated
16
susconecta/package-lock.json
generated
@ -68,6 +68,7 @@
|
||||
"zod": "3.25.67"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
"@types/node": "^22",
|
||||
@ -9402,21 +9403,6 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||
"version": "14.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.16.tgz",
|
||||
"integrity": "sha512-jhPl3nN0oKEshJBNDAo0etGMzv0j3q3VYorTSFqH1o3rwv1MQRdor27u1zhkgsHPNeY1jxcgyx1ZsCkDD1IHgg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,60 +4,66 @@ export interface ApiError {
|
||||
code?: string;
|
||||
}
|
||||
// Este arquivo foi renomeado de report.ts para report-types.ts para evitar confusão com outros arquivos de lógica.
|
||||
// Tipos para o endpoint de Relatórios Médicos
|
||||
// Tipos para o endpoint Supabase de Relatórios Médicos
|
||||
export interface Report {
|
||||
id: string;
|
||||
patient_id: string;
|
||||
doctor_id: string;
|
||||
report_type: string;
|
||||
chief_complaint: string;
|
||||
clinical_history: string;
|
||||
symptoms_and_signs: string;
|
||||
physical_examination: string;
|
||||
complementary_exams: string;
|
||||
exam_results: string;
|
||||
order_number: string;
|
||||
exam: string;
|
||||
diagnosis: string;
|
||||
prognosis?: string;
|
||||
treatment_performed: string;
|
||||
objective_recommendations: string;
|
||||
icd_code?: string;
|
||||
report_date: string;
|
||||
conclusion: string;
|
||||
cid_code: string;
|
||||
content_html: string;
|
||||
content_json: any;
|
||||
status: string;
|
||||
requested_by: string;
|
||||
due_at: string;
|
||||
hide_date: boolean;
|
||||
hide_signature: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
||||
// Dados expandidos (quando incluir dados relacionados)
|
||||
patient?: {
|
||||
id: string;
|
||||
full_name: string;
|
||||
cpf?: string;
|
||||
birth_date?: string;
|
||||
};
|
||||
|
||||
doctor?: {
|
||||
id: string;
|
||||
full_name: string;
|
||||
crm?: string;
|
||||
specialty?: string;
|
||||
};
|
||||
created_by: string;
|
||||
}
|
||||
|
||||
export interface CreateReportData {
|
||||
patient_id: string;
|
||||
order_number: string;
|
||||
exam: string;
|
||||
diagnosis: string;
|
||||
conclusion: string;
|
||||
cid_code: string;
|
||||
content_html: string;
|
||||
content_json: any;
|
||||
status: string;
|
||||
requested_by: string;
|
||||
due_at: string;
|
||||
hide_date: boolean;
|
||||
hide_signature: boolean;
|
||||
created_by: string;
|
||||
}
|
||||
|
||||
export interface UpdateReportData extends Partial<CreateReportData> {
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export type ReportsResponse = Report[];
|
||||
export type ReportResponse = Report;
|
||||
|
||||
// Dados para criar um novo relatório
|
||||
export interface CreateReportData {
|
||||
patient_id: string;
|
||||
doctor_id: string;
|
||||
report_type: string;
|
||||
chief_complaint: string;
|
||||
clinical_history: string;
|
||||
symptoms_and_signs: string;
|
||||
physical_examination: string;
|
||||
complementary_exams: string;
|
||||
exam_results: string;
|
||||
diagnosis: string;
|
||||
prognosis?: string;
|
||||
treatment_performed: string;
|
||||
objective_recommendations: string;
|
||||
icd_code?: string;
|
||||
report_date: string;
|
||||
patient_id: string;
|
||||
order_number: string;
|
||||
exam: string;
|
||||
diagnosis: string;
|
||||
conclusion: string;
|
||||
cid_code: string;
|
||||
content_html: string;
|
||||
content_json: any;
|
||||
status: string;
|
||||
requested_by: string;
|
||||
due_at: string;
|
||||
hide_date: boolean;
|
||||
hide_signature: boolean;
|
||||
}
|
||||
|
||||
// Dados para atualizar um relatório existente
|
||||
@ -66,18 +72,6 @@ export interface UpdateReportData extends Partial<CreateReportData> {
|
||||
}
|
||||
|
||||
// Resposta da API ao listar relatórios
|
||||
export interface ReportsResponse {
|
||||
data: Report[];
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Resposta da API ao criar/atualizar um relatório
|
||||
export interface ReportResponse {
|
||||
data: Report;
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Dados do formulário (adaptado para a estrutura do front-end existente)
|
||||
export interface ReportFormData {
|
||||
@ -86,22 +80,19 @@ export interface ReportFormData {
|
||||
profissionalCrm: string;
|
||||
|
||||
// Identificação do Paciente
|
||||
pacienteId: string;
|
||||
pacienteNome: string;
|
||||
pacienteCpf: string;
|
||||
pacienteIdade: string;
|
||||
patient_id: string;
|
||||
report_type: string;
|
||||
symptoms_and_signs: string;
|
||||
diagnosis: string;
|
||||
prognosis?: string;
|
||||
treatment_performed: string;
|
||||
icd_code?: string;
|
||||
report_date: string;
|
||||
hipotesesDiagnosticas: string;
|
||||
condutaMedica: string;
|
||||
prescricoes: string;
|
||||
retornoAgendado: string;
|
||||
// cid10: string; // Removed, not present in schema
|
||||
|
||||
// Informações do Relatório
|
||||
motivoRelatorio: string;
|
||||
cid?: string;
|
||||
dataRelatorio: string;
|
||||
|
||||
// Histórico Clínico
|
||||
historicoClinico: string;
|
||||
|
||||
// Sinais, Sintomas e Exames
|
||||
sinaisSintomas: string;
|
||||
examesRealizados: string;
|
||||
resultadosExames: string;
|
||||
// ...restante do código...
|
||||
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user