fix(principal): integra auth, agenda e laudos com a api
This commit is contained in:
@@ -1,8 +1,50 @@
|
||||
import { appointments as mockAppointments } from '../data/mockData.js'
|
||||
import { apiConfig, apiEndpoint, getAuthenticatedHeaders } from '../config/api.js'
|
||||
import { appointmentMapper } from '../mappers/appointmentMapper.js'
|
||||
import { fetchJsonWithFallback, normalizeCollection, normalizeItem } from './repositoryUtils.js'
|
||||
|
||||
export const appointmentRepository = {
|
||||
getAll() {
|
||||
return mockAppointments
|
||||
async getAll() {
|
||||
const data = await fetchJsonWithFallback(
|
||||
[
|
||||
{
|
||||
url: apiEndpoint('/agendamentos'),
|
||||
options: { headers: getAuthenticatedHeaders() },
|
||||
},
|
||||
{
|
||||
url: `${apiConfig.restUrl}/appointments?select=*,patients(full_name),doctors(name)`,
|
||||
options: { headers: getAuthenticatedHeaders() },
|
||||
},
|
||||
],
|
||||
'Erro ao buscar agendamentos.',
|
||||
)
|
||||
|
||||
return normalizeCollection(data, ['agendamentos', 'appointments', 'data']).map(appointmentMapper.toUi)
|
||||
},
|
||||
|
||||
async create(uiData) {
|
||||
const data = await fetchJsonWithFallback(
|
||||
[
|
||||
{
|
||||
url: apiEndpoint('/agendamentos'),
|
||||
options: {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
body: JSON.stringify(appointmentMapper.toApi(uiData)),
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${apiConfig.restUrl}/appointments`,
|
||||
options: {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders({ Prefer: 'return=representation' }),
|
||||
body: JSON.stringify(appointmentMapper.toApi(uiData, 'supabase')),
|
||||
},
|
||||
},
|
||||
],
|
||||
'Falha ao criar o agendamento.',
|
||||
)
|
||||
|
||||
return appointmentMapper.toUi(normalizeItem(data, ['agendamento', 'appointment', 'data']))
|
||||
},
|
||||
|
||||
getTodayTimeline() {
|
||||
|
||||
124
src/repositories/authRepository.js
Normal file
124
src/repositories/authRepository.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
apiConfig,
|
||||
apiEndpoint,
|
||||
clearAuthSession,
|
||||
getAnonHeaders,
|
||||
getAuthenticatedHeaders,
|
||||
getAuthSession,
|
||||
hasAuthenticatedSession,
|
||||
saveAuthSession,
|
||||
} from '../config/api.js'
|
||||
|
||||
export const authRepository = {
|
||||
async login({ email, password }) {
|
||||
const response = await fetch(`${apiConfig.supabaseUrl}/auth/v1/token?grant_type=password`, {
|
||||
method: 'POST',
|
||||
headers: getAnonHeaders(),
|
||||
body: JSON.stringify({ email: email?.trim(), password }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await getResponseError(response, 'Erro de autenticacao.'))
|
||||
}
|
||||
|
||||
const session = await response.json()
|
||||
if (!session?.access_token) {
|
||||
throw new Error('Falha no login. Token nao recebido.')
|
||||
}
|
||||
|
||||
saveAuthSession(session)
|
||||
return session
|
||||
},
|
||||
|
||||
async requestPasswordReset(email) {
|
||||
const payload = { email: email?.trim() }
|
||||
const apiResponse = await fetch(apiEndpoint('/solicitar-reset-de-senha'), {
|
||||
method: 'POST',
|
||||
headers: getAnonHeaders(),
|
||||
body: JSON.stringify(payload),
|
||||
}).catch(() => null)
|
||||
|
||||
if (apiResponse?.ok) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (apiResponse && !shouldFallback(apiResponse)) {
|
||||
throw new Error(await getResponseError(apiResponse, 'Erro ao solicitar reset de senha.'))
|
||||
}
|
||||
|
||||
const supabaseResponse = await fetch(`${apiConfig.supabaseUrl}/auth/v1/recover`, {
|
||||
method: 'POST',
|
||||
headers: getAnonHeaders(),
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
if (!supabaseResponse.ok) {
|
||||
throw new Error(await getResponseError(supabaseResponse, 'Erro ao enviar link de recuperacao.'))
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
async getUser() {
|
||||
const apiResponse = await fetch(apiEndpoint('/informacoes-do-usuario-autenticado'), {
|
||||
method: 'GET',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
}).catch(() => null)
|
||||
|
||||
if (apiResponse?.ok) {
|
||||
return apiResponse.json()
|
||||
}
|
||||
|
||||
if (apiResponse && !shouldFallback(apiResponse)) {
|
||||
throw new Error(await getResponseError(apiResponse, 'Erro ao resgatar perfil de usuario.'))
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiConfig.supabaseUrl}/auth/v1/user`, {
|
||||
method: 'GET',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await getResponseError(response, 'Erro ao resgatar perfil de usuario.'))
|
||||
}
|
||||
|
||||
return response.json()
|
||||
},
|
||||
|
||||
getSession() {
|
||||
return getAuthSession()
|
||||
},
|
||||
|
||||
isAuthenticated() {
|
||||
return hasAuthenticatedSession()
|
||||
},
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
const apiResponse = await fetch(apiEndpoint('/logout'), {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
}).catch(() => null)
|
||||
|
||||
if (apiResponse?.ok || (apiResponse && !shouldFallback(apiResponse))) return
|
||||
|
||||
await fetch(`${apiConfig.supabaseUrl}/auth/v1/logout`, {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
})
|
||||
} catch {
|
||||
// A sessao local precisa ser removida mesmo quando o backend nao responde.
|
||||
} finally {
|
||||
clearAuthSession()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function shouldFallback(response) {
|
||||
return [404, 405].includes(response.status)
|
||||
}
|
||||
|
||||
async function getResponseError(response, fallbackMessage) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return error.error_description || error.msg || error.message || error.error || fallbackMessage
|
||||
}
|
||||
@@ -1,4 +1,42 @@
|
||||
import { apiConfig, apiEndpoint, getAuthenticatedHeaders } from '../config/api.js'
|
||||
import { fetchJsonWithFallback } from './repositoryUtils.js'
|
||||
|
||||
export const communicationRepository = {
|
||||
async sendSms({ patientName, phone, content }) {
|
||||
const message = `[MediConnect] Ola ${patientName}, ${content}`
|
||||
const payload = {
|
||||
telefone: normalizePhone(phone),
|
||||
phone: normalizePhone(phone),
|
||||
mensagem: message,
|
||||
message,
|
||||
paciente: patientName,
|
||||
}
|
||||
|
||||
await fetchJsonWithFallback(
|
||||
[
|
||||
{
|
||||
url: apiEndpoint('/enviar-sms-via-twilio'),
|
||||
options: {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${apiConfig.functionsUrl.replace(/\/+$/, '')}/send-sms`,
|
||||
options: {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
},
|
||||
],
|
||||
'Falha no envio de SMS via Twilio.',
|
||||
)
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
getCampaigns() {
|
||||
return [
|
||||
{ title: 'Lembretes Anti-Falta', desc: 'Envio automatico 48h e 4h antes', count: '324 pacientes elegiveis' },
|
||||
@@ -31,3 +69,9 @@ export const communicationRepository = {
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
function normalizePhone(phone) {
|
||||
const digits = String(phone || '').replace(/\D/g, '')
|
||||
if (!digits) return ''
|
||||
return digits.startsWith('55') ? `+${digits}` : `+55${digits}`
|
||||
}
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import { apiConfig, apiHeaders } from '../config/api.js'
|
||||
import { apiConfig, getAuthenticatedHeaders } from '../config/api.js'
|
||||
|
||||
export const patientRepository = {
|
||||
// 1. Listar pacientes
|
||||
async getAll() {
|
||||
const response = await fetch(`${apiConfig.restUrl}/patients?select=*`, { headers: apiHeaders })
|
||||
const response = await fetch(`${apiConfig.restUrl}/patients?select=*`, { headers: getAuthenticatedHeaders() })
|
||||
if (!response.ok) throw new Error('Erro ao buscar pacientes')
|
||||
return response.json()
|
||||
},
|
||||
|
||||
async getById(patientId) {
|
||||
const patients = await this.getAll()
|
||||
return patients.find((p) => String(p.id) === String(patientId)) || null
|
||||
const patient = patients.find((p) => String(p.id) === String(patientId)) || null
|
||||
return patient ? mapPatientToDetail(patient) : null
|
||||
},
|
||||
|
||||
async getDirectoryRows() {
|
||||
const patients = await this.getAll()
|
||||
return patients.map((patient) => ({
|
||||
...patient,
|
||||
name: patient.full_name,
|
||||
phone: patient.phone_mobile,
|
||||
detailId: patient.id,
|
||||
insurance: 'Particular',
|
||||
city: 'Recife',
|
||||
state: 'PE',
|
||||
vip: false,
|
||||
lastVisitIso: null,
|
||||
lastVisit: 'Ainda nao houve atendimento',
|
||||
nextVisit: 'Nenhum atendimento agendado',
|
||||
}))
|
||||
return patients.map(mapPatientToDirectory)
|
||||
},
|
||||
|
||||
// 2. Criar paciente (direto)
|
||||
@@ -43,7 +32,7 @@ export const patientRepository = {
|
||||
|
||||
const response = await fetch(`${apiConfig.restUrl}/patients`, {
|
||||
method: 'POST',
|
||||
headers: { ...apiHeaders, Prefer: 'return=representation' },
|
||||
headers: getAuthenticatedHeaders({ Prefer: 'return=representation' }),
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
@@ -69,7 +58,7 @@ export const patientRepository = {
|
||||
|
||||
const response = await fetch(`${apiConfig.functionsUrl}/create-patient`, {
|
||||
method: 'POST',
|
||||
headers: apiHeaders,
|
||||
headers: getAuthenticatedHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
@@ -93,7 +82,7 @@ export const patientRepository = {
|
||||
|
||||
const response = await fetch(`${apiConfig.restUrl}/patients?id=eq.${patientId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { ...apiHeaders, Prefer: 'return=representation' },
|
||||
headers: getAuthenticatedHeaders({ Prefer: 'return=representation' }),
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
@@ -105,10 +94,62 @@ export const patientRepository = {
|
||||
async remove(patientId) {
|
||||
const response = await fetch(`${apiConfig.restUrl}/patients?id=eq.${patientId}`, {
|
||||
method: 'DELETE',
|
||||
headers: apiHeaders,
|
||||
headers: getAuthenticatedHeaders(),
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('Erro ao deletar paciente')
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
function mapPatientToDirectory(patient) {
|
||||
return {
|
||||
...patient,
|
||||
name: patient.name || patient.full_name || patient.nome || 'Paciente',
|
||||
phone: patient.phone || patient.phone_mobile || patient.telefone || '',
|
||||
detailId: patient.id,
|
||||
insurance: patient.insurance || patient.convenio || 'Particular',
|
||||
city: patient.city || patient.cidade || 'Recife',
|
||||
state: patient.state || patient.uf || 'PE',
|
||||
vip: Boolean(patient.vip),
|
||||
lastVisitIso: patient.lastVisitIso || patient.last_visit_iso || null,
|
||||
lastVisit: patient.lastVisit || patient.last_visit || 'Ainda nao houve atendimento',
|
||||
nextVisit: patient.nextVisit || patient.next_visit || 'Nenhum atendimento agendado',
|
||||
}
|
||||
}
|
||||
|
||||
function mapPatientToDetail(patient) {
|
||||
const directory = mapPatientToDirectory(patient)
|
||||
|
||||
return {
|
||||
...directory,
|
||||
age: patient.age || patient.idade || calculateAge(patient.birth_date),
|
||||
document: patient.document || patient.cpf || 'CPF nao informado',
|
||||
plan: directory.insurance,
|
||||
condition: patient.condition || patient.condicao || 'Sem condicao principal',
|
||||
status: patient.status || 'Acompanhamento',
|
||||
risk: patient.risk || patient.risco || 'Baixo',
|
||||
email: patient.email || '',
|
||||
address: patient.address || patient.endereco || 'Endereco nao informado',
|
||||
team: patient.team || patient.equipe || [],
|
||||
notes: patient.notes || patient.observacoes || [],
|
||||
exams: patient.exams || patient.exames || [],
|
||||
}
|
||||
}
|
||||
|
||||
function calculateAge(birthDate) {
|
||||
if (!birthDate) return 0
|
||||
|
||||
const birth = new Date(birthDate)
|
||||
if (Number.isNaN(birth.getTime())) return 0
|
||||
|
||||
const today = new Date()
|
||||
let age = today.getFullYear() - birth.getFullYear()
|
||||
const monthDiff = today.getMonth() - birth.getMonth()
|
||||
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
||||
age -= 1
|
||||
}
|
||||
|
||||
return age
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
import { professionals as mockProfessionals } from '../data/mockData.js'
|
||||
import { apiConfig, apiEndpoint, getAuthenticatedHeaders } from '../config/api.js'
|
||||
import { fetchJsonWithFallback, normalizeCollection } from './repositoryUtils.js'
|
||||
|
||||
export const professionalRepository = {
|
||||
getAll() {
|
||||
return mockProfessionals
|
||||
async getAll() {
|
||||
const data = await fetchJsonWithFallback(
|
||||
[
|
||||
{
|
||||
url: apiEndpoint('/listar-medicos'),
|
||||
options: { headers: getAuthenticatedHeaders() },
|
||||
},
|
||||
{
|
||||
url: `${apiConfig.restUrl}/doctors`,
|
||||
options: { headers: getAuthenticatedHeaders() },
|
||||
},
|
||||
],
|
||||
'Erro ao buscar medicos.',
|
||||
)
|
||||
|
||||
return normalizeCollection(data, ['medicos', 'doctors', 'professionals', 'data']).map(mapProfessional)
|
||||
},
|
||||
|
||||
getCoverageMap() {
|
||||
@@ -12,3 +27,15 @@ export const professionalRepository = {
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function mapProfessional(doctor) {
|
||||
return {
|
||||
id: String(doctor.id || doctor.medico_id || doctor.user_id || doctor.name || doctor.nome),
|
||||
name: doctor.name || doctor.nome || doctor.full_name || 'Medico(a)',
|
||||
role: doctor.specialty || doctor.speciality || doctor.especialidade || doctor.role || 'Medico(a)',
|
||||
schedule: doctor.schedule || doctor.agenda || doctor.disponibilidade || 'Seg a Sex, 08h as 18h',
|
||||
nextSlot: doctor.nextSlot || doctor.proximo_horario || doctor.next_slot || 'Consulta pendente',
|
||||
patients: doctor.patients || doctor.pacientes_ativos || doctor.active_patients || 0,
|
||||
status: doctor.status || doctor.situacao || 'Disponivel',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,74 @@
|
||||
import { authRepository } from './authRepository.js'
|
||||
import { apiConfig, apiEndpoint, getAuthenticatedHeaders } from '../config/api.js'
|
||||
import { getResponseError } from './repositoryUtils.js'
|
||||
|
||||
export const profileRepository = {
|
||||
getCurrentUserProfile() {
|
||||
async getCurrentUserProfile() {
|
||||
const data = await authRepository.getUser()
|
||||
const user = data?.user || data?.usuario || data?.profile || data
|
||||
const meta = user?.user_metadata || user?.metadata || user?.app_metadata || {}
|
||||
const avatarUrl = user?.avatarUrl || user?.avatar_url || meta.avatar_url || meta.picture || ''
|
||||
|
||||
return {
|
||||
email: 'henrique.cardoso@mediconnect.com.br',
|
||||
name: 'Dr. Henrique Cardoso',
|
||||
phone: '(81) 98888-0101',
|
||||
role: 'Medico Clinico Geral',
|
||||
unit: 'Clinica Boa Vista',
|
||||
id: user?.id || user?.user_id || user?.uid || '',
|
||||
email: user?.email || meta.email || '',
|
||||
name: user?.name || user?.nome || user?.full_name || meta.full_name || meta.name || 'Usuario',
|
||||
phone: user?.phone || user?.telefone || meta.phone || meta.telefone || '',
|
||||
role: user?.role || user?.cargo || meta.role || meta.cargo || 'Usuario do Sistema',
|
||||
unit: user?.unit || user?.unidade || meta.unit || meta.unidade || 'Clinica Boa Vista',
|
||||
avatarUrl,
|
||||
}
|
||||
},
|
||||
|
||||
async updateAvatar(file) {
|
||||
const profile = await this.getCurrentUserProfile()
|
||||
const formData = new FormData()
|
||||
formData.append('avatar', file)
|
||||
formData.append('file', file)
|
||||
|
||||
const apiResponse = await fetch(apiEndpoint('/upload-avatar'), {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders({ 'Content-Type': undefined }),
|
||||
body: formData,
|
||||
}).catch(() => null)
|
||||
|
||||
if (apiResponse?.ok) {
|
||||
return normalizeAvatarResponse(await apiResponse.json().catch(() => ({})))
|
||||
}
|
||||
|
||||
if (apiResponse && ![404, 405].includes(apiResponse.status)) {
|
||||
throw new Error(await getResponseError(apiResponse, 'Falha ao enviar avatar.'))
|
||||
}
|
||||
|
||||
if (!profile.id) {
|
||||
throw new Error('Nao foi possivel identificar o usuario para enviar o avatar.')
|
||||
}
|
||||
|
||||
const extension = file.name?.split('.').pop() || 'jpg'
|
||||
const objectPath = `${profile.id}/avatar.${extension}`
|
||||
const response = await fetch(`${apiConfig.storageUrl}/object/avatars/${objectPath}`, {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders({
|
||||
'Content-Type': file.type || 'application/octet-stream',
|
||||
'x-upsert': 'true',
|
||||
}),
|
||||
body: file,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await getResponseError(response, 'Falha ao enviar avatar.'))
|
||||
}
|
||||
|
||||
return {
|
||||
avatarUrl: `${apiConfig.storageUrl}/object/public/avatars/${objectPath}`,
|
||||
path: objectPath,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function normalizeAvatarResponse(data) {
|
||||
return {
|
||||
avatarUrl: data.avatarUrl || data.avatar_url || data.publicUrl || data.public_url || data.url || '',
|
||||
path: data.path || data.key || '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +1,142 @@
|
||||
const reportTypes = [
|
||||
'Atestado Medico',
|
||||
'Laudo de Exame',
|
||||
'Laudo de Imagem',
|
||||
'Relatorio Cirurgico',
|
||||
'Declaracao de Acompanhante',
|
||||
'Encaminhamento',
|
||||
]
|
||||
|
||||
const doctors = ['Dra. Ana Silva', 'Dr. Carlos Mendes', 'Dr. Roberto Nunes']
|
||||
const currentUser = 'Dra. Ana Silva'
|
||||
const adminUsers = ['Dr. Roberto Nunes']
|
||||
import { apiConfig, apiEndpoint, getAuthenticatedHeaders } from '../config/api.js'
|
||||
import { reportMapper } from '../mappers/reportMapper.js'
|
||||
import { fetchJsonWithFallback, normalizeCollection, normalizeItem } from './repositoryUtils.js'
|
||||
|
||||
export const reportRepository = {
|
||||
getAdminUsers() {
|
||||
return adminUsers
|
||||
async getInitialReports() {
|
||||
const data = await fetchJsonWithFallback(
|
||||
[
|
||||
{
|
||||
url: apiEndpoint('/reports'),
|
||||
options: { headers: getAuthenticatedHeaders() },
|
||||
},
|
||||
{
|
||||
url: `${apiConfig.restUrl}/reports?select=*,patients(full_name),doctors(name)`,
|
||||
options: { headers: getAuthenticatedHeaders() },
|
||||
},
|
||||
],
|
||||
'Falha ao buscar laudos da API.',
|
||||
)
|
||||
|
||||
return normalizeCollection(data, ['reports', 'relatorios', 'laudos', 'data']).map(reportMapper.toUi)
|
||||
},
|
||||
|
||||
getCurrentUser() {
|
||||
return currentUser
|
||||
async create(uiData) {
|
||||
const data = await fetchJsonWithFallback(
|
||||
[
|
||||
{
|
||||
url: apiEndpoint('/reports'),
|
||||
options: {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
body: JSON.stringify(reportMapper.toApi(uiData)),
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${apiConfig.restUrl}/reports`,
|
||||
options: {
|
||||
method: 'POST',
|
||||
headers: getAuthenticatedHeaders({ Prefer: 'return=representation' }),
|
||||
body: JSON.stringify(reportMapper.toApi(uiData, 'supabase')),
|
||||
},
|
||||
},
|
||||
],
|
||||
'Falha ao salvar laudo.',
|
||||
)
|
||||
|
||||
return reportMapper.toUi(normalizeItem(data, ['report', 'relatorio', 'laudo', 'data']))
|
||||
},
|
||||
|
||||
getDoctors() {
|
||||
return doctors
|
||||
},
|
||||
async update(id, uiData) {
|
||||
const data = await fetchJsonWithFallback(
|
||||
[
|
||||
{
|
||||
url: apiEndpoint(`/reports/${id}`),
|
||||
options: {
|
||||
method: 'PATCH',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
body: JSON.stringify(reportMapper.toApi({ ...uiData, id })),
|
||||
},
|
||||
},
|
||||
{
|
||||
url: apiEndpoint('/reports'),
|
||||
options: {
|
||||
method: 'PATCH',
|
||||
headers: getAuthenticatedHeaders(),
|
||||
body: JSON.stringify({ id, ...reportMapper.toApi(uiData) }),
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${apiConfig.restUrl}/reports?id=eq.${id}`,
|
||||
options: {
|
||||
method: 'PATCH',
|
||||
headers: getAuthenticatedHeaders({ Prefer: 'return=representation' }),
|
||||
body: JSON.stringify(reportMapper.toApi(uiData, 'supabase')),
|
||||
},
|
||||
},
|
||||
],
|
||||
'Falha ao atualizar o laudo.',
|
||||
)
|
||||
|
||||
getInitialReports() {
|
||||
return [
|
||||
{
|
||||
id: 'report-1',
|
||||
type: 'Atestado Medico',
|
||||
patient: 'Carlos Eduardo Santos',
|
||||
doctor: 'Dra. Ana Silva',
|
||||
date: '27/03/2026',
|
||||
status: 'finalizado',
|
||||
content: 'Atesto que o paciente esteve em consulta medica nesta data, necessitando de repouso por 2 dias.',
|
||||
showDate: true,
|
||||
signDigital: true,
|
||||
versions: [
|
||||
{ version: 1, action: 'Criado', user: 'Dra. Ana Silva', summary: 'Laudo criado' },
|
||||
{ version: 2, action: 'Editado', user: 'Dra. Ana Silva', summary: 'Ajuste no periodo de repouso' },
|
||||
{ version: 3, action: 'Liberado', user: 'Dra. Ana Silva', summary: 'Laudo liberado e finalizado' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'report-2',
|
||||
type: 'Laudo de Exame',
|
||||
patient: 'Mariana Costa',
|
||||
doctor: 'Dra. Ana Silva',
|
||||
date: '26/03/2026',
|
||||
status: 'enviado',
|
||||
content: 'Laudo referente ao exame de ecocardiograma. Resultado dentro dos parametros normais.',
|
||||
showDate: true,
|
||||
signDigital: true,
|
||||
versions: [
|
||||
{ version: 1, action: 'Criado', user: 'Dr. Carlos Mendes', summary: 'Laudo criado' },
|
||||
{ version: 2, action: 'Editado', user: 'Dra. Ana Silva', summary: 'Adicao da data do exame' },
|
||||
{ version: 3, action: 'Liberado', user: 'Dra. Ana Silva', summary: 'Conclusao incluida' },
|
||||
{ version: 4, action: 'Enviado', user: 'Dr. Roberto Nunes', summary: 'Laudo enviado ao paciente' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'report-3',
|
||||
type: 'Relatorio Cirurgico',
|
||||
patient: 'Fernanda Lima',
|
||||
doctor: 'Dr. Carlos Mendes',
|
||||
date: '25/03/2026',
|
||||
status: 'rascunho',
|
||||
content: 'Relatorio do procedimento de colecistectomia laparoscopica realizado sob anestesia geral.',
|
||||
showDate: false,
|
||||
signDigital: true,
|
||||
versions: [
|
||||
{ version: 1, action: 'Criado', user: 'Dr. Carlos Mendes', summary: 'Relatorio criado' },
|
||||
{ version: 2, action: 'Rascunho', user: 'Dr. Carlos Mendes', summary: 'Detalhamento do procedimento' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'report-4',
|
||||
type: 'Declaracao de Acompanhante',
|
||||
patient: 'Joao Pedro Alves',
|
||||
doctor: 'Dr. Roberto Nunes',
|
||||
date: '24/03/2026',
|
||||
status: 'finalizado',
|
||||
content: 'Declaro que o acompanhante esteve presente durante todo o periodo de internacao.',
|
||||
showDate: true,
|
||||
signDigital: false,
|
||||
versions: [
|
||||
{ version: 1, action: 'Criado', user: 'Dr. Roberto Nunes', summary: 'Declaracao criada e liberada' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'report-5',
|
||||
type: 'Laudo de Imagem',
|
||||
patient: 'Roberto Campos',
|
||||
doctor: 'Dra. Ana Silva',
|
||||
date: '22/03/2026',
|
||||
status: 'enviado',
|
||||
content: 'Ultrassonografia de abdomen total sem achados patologicos relevantes.',
|
||||
showDate: true,
|
||||
signDigital: true,
|
||||
versions: [
|
||||
{ version: 1, action: 'Criado', user: 'Dra. Ana Silva', summary: 'Laudo criado' },
|
||||
{ version: 2, action: 'Liberado', user: 'Dra. Ana Silva', summary: 'Conclusao adicionada' },
|
||||
{ version: 3, action: 'Enviado', user: 'Dr. Roberto Nunes', summary: 'Laudo enviado ao paciente' },
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
getReportTypes() {
|
||||
return reportTypes
|
||||
return reportMapper.toUi(normalizeItem(data, ['report', 'relatorio', 'laudo', 'data']))
|
||||
},
|
||||
|
||||
getTemplates() {
|
||||
return [
|
||||
{ id: 'template-1', name: 'Atestado de Repouso Simples', type: 'Atestado Medico', description: 'Atestado padrao concedendo dias de repouso ao paciente.', content: 'Atesto, para os devidos fins, que o(a) paciente necessita de repouso pelo periodo indicado.' },
|
||||
{ id: 'template-2', name: 'Laudo de Hemograma', type: 'Laudo de Exame', description: 'Resultado de hemograma completo com interpretacao clinica.', content: 'Laudo de hemograma completo com parametros avaliados e interpretacao clinica.' },
|
||||
{ id: 'template-3', name: 'Relatorio Cirurgico', type: 'Relatorio Cirurgico', description: 'Relatorio padronizado para procedimento cirurgico.', content: 'Relatorio do procedimento cirurgico, achados, conduta e evolucao imediata.' },
|
||||
{
|
||||
id: 't1',
|
||||
name: 'Atestado Medico Padrao',
|
||||
type: 'Atestado Medico',
|
||||
description: 'Atestado simples para repouso, consulta e CID.',
|
||||
content:
|
||||
'Atesto para os devidos fins que o(a) paciente [NOME DO PACIENTE] esteve em consulta medica nesta data, necessitando de [DIAS] dias de repouso por motivo de saude (CID: [CODIGO]).',
|
||||
},
|
||||
{
|
||||
id: 't2',
|
||||
name: 'Encaminhamento Especializado',
|
||||
type: 'Encaminhamento',
|
||||
description: 'Encaminhamento para avaliacao de especialidade.',
|
||||
content:
|
||||
'Encaminho o(a) paciente [NOME DO PACIENTE] para avaliacao da especialidade de [ESPECIALIDADE] devido ao quadro clinico de [SINTOMAS/DIAGNOSTICO PREVIO].\n\nConduta mantida ate o momento: [MEDICACOES]',
|
||||
},
|
||||
{
|
||||
id: 't3',
|
||||
name: 'Laudo de Evolucao Diaria',
|
||||
type: 'Evolucao Clinica',
|
||||
description: 'Modelo para evolucao clinica diaria.',
|
||||
content:
|
||||
'Paciente evolui [BEM/MAL], [COM/SEM] queixas no momento.\nSinais vitais: PA [VALOR], FC [VALOR] bpm, SatO2 [VALOR]%.\nExame fisico: [DESCRICAO].\nConduta: [MANTER/ALTERAR TRATAMENTO OPCOES].',
|
||||
},
|
||||
{
|
||||
id: 't4',
|
||||
name: 'Receituario de Uso Continuo',
|
||||
type: 'Receituario Fixado',
|
||||
description: 'Lista de medicamentos de uso continuo.',
|
||||
content:
|
||||
'Uso continuo:\n1. [MEDICAMENTO] - [DOSE] - Tomar [POSOLOGIA]\n2. [MEDICAMENTO] - [DOSE] - Tomar [POSOLOGIA]',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
getAdminUsers() {
|
||||
return ['Dr. Henrique Cardoso', 'Dra. Marina Lopes', 'Dra. Ana Silva']
|
||||
},
|
||||
|
||||
getCurrentUser() {
|
||||
return 'Dr. Henrique Cardoso'
|
||||
},
|
||||
|
||||
getDoctors() {
|
||||
return ['Dr. Henrique Cardoso', 'Dra. Marina Lopes', 'Dra. Ana Silva', 'Dr. Roberto Santos']
|
||||
},
|
||||
|
||||
getReportTypes() {
|
||||
return [
|
||||
'Atestado Medico',
|
||||
'Encaminhamento',
|
||||
'Evolucao Clinica',
|
||||
'Receituario Fixado',
|
||||
'Laudo de Procedimento',
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
74
src/repositories/repositoryUtils.js
Normal file
74
src/repositories/repositoryUtils.js
Normal file
@@ -0,0 +1,74 @@
|
||||
export async function fetchJsonWithFallback(requests, fallbackMessage) {
|
||||
let lastResponse = null
|
||||
let lastError = null
|
||||
|
||||
for (const request of requests) {
|
||||
let response
|
||||
|
||||
try {
|
||||
response = await fetch(request.url, request.options)
|
||||
lastResponse = response
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
continue
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return parseJsonResponse(response)
|
||||
}
|
||||
|
||||
if (!shouldFallback(response)) {
|
||||
throw new Error(await getResponseError(response, fallbackMessage))
|
||||
}
|
||||
}
|
||||
|
||||
if (lastError && !lastResponse) {
|
||||
throw new Error(lastError.message || fallbackMessage)
|
||||
}
|
||||
|
||||
throw new Error(await getResponseError(lastResponse, fallbackMessage))
|
||||
}
|
||||
|
||||
export function normalizeCollection(data, keys = []) {
|
||||
if (Array.isArray(data)) return data
|
||||
|
||||
for (const key of keys) {
|
||||
if (Array.isArray(data?.[key])) return data[key]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export function normalizeItem(data, keys = []) {
|
||||
if (Array.isArray(data)) return data[0] || null
|
||||
|
||||
for (const key of keys) {
|
||||
if (data?.[key]) return data[key]
|
||||
}
|
||||
|
||||
return data || null
|
||||
}
|
||||
|
||||
export async function getResponseError(response, fallbackMessage) {
|
||||
if (!response) return fallbackMessage
|
||||
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return error.error_description || error.msg || error.message || error.error || fallbackMessage
|
||||
}
|
||||
|
||||
function shouldFallback(response) {
|
||||
return [404, 405].includes(response.status)
|
||||
}
|
||||
|
||||
async function parseJsonResponse(response) {
|
||||
if (response.status === 204) return null
|
||||
|
||||
const text = await response.text()
|
||||
if (!text) return null
|
||||
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { message: text }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user