modified: src/hooks/useAgenda.js modified: src/index.css modified: src/main.jsx modified: src/mappers/reportMapper.js modified: src/pages/AgendaPage.jsx modified: src/pages/AuthPages.jsx modified: src/pages/MedicalRecordsPage.jsx modified: src/pages/PatientsPage.jsx modified: src/pages/ReportsPage.jsx modified: src/pages/SettingsPage.jsx modified: src/repositories/analyticsRepository.js modified: src/repositories/authRepository.js modified: src/repositories/patientRepository.js modified: src/repositories/reportRepository.js modified: src/repositories/repositoryUtils.js new file: src/utils/theme.js new file: vercel.json
135 lines
4.2 KiB
JavaScript
135 lines
4.2 KiB
JavaScript
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(translateErrorMessage(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 text = await response.text().catch(() => '')
|
|
const error = parseErrorBody(text)
|
|
const message = translateErrorMessage(
|
|
error.error_description ||
|
|
error.msg ||
|
|
error.message ||
|
|
error.error ||
|
|
error.details ||
|
|
error.hint ||
|
|
text ||
|
|
fallbackMessage,
|
|
)
|
|
|
|
return response.status ? `${fallbackMessage} (${response.status}): ${message}` : message
|
|
}
|
|
|
|
export function translateErrorMessage(message) {
|
|
const rawMessage = String(message || '').trim()
|
|
const normalized = rawMessage.toLowerCase()
|
|
|
|
if (!rawMessage) return 'Erro inesperado.'
|
|
if (isPortugueseMessage(rawMessage)) return rawMessage
|
|
|
|
const translations = [
|
|
[/failed to fetch|networkerror|load failed|network request failed/, 'Não foi possível conectar ao servidor. Verifique sua conexão e tente novamente.'],
|
|
[/invalid login credentials|invalid credentials/, 'E-mail ou senha inválidos.'],
|
|
[/email not confirmed/, 'E-mail ainda não confirmado. Verifique sua caixa de entrada.'],
|
|
[/user already registered|already registered/, 'Este e-mail já está cadastrado.'],
|
|
[/user not found/, 'Usuário não encontrado.'],
|
|
[/jwt expired|invalid jwt|jwt malformed|invalid token|token is expired/, 'Sessão expirada. Faça login novamente.'],
|
|
[/missing required parameters?/, 'Parâmetros obrigatórios não foram enviados.'],
|
|
[/duplicate key value violates unique constraint/, 'Já existe um registro com essas informações.'],
|
|
[/new row violates row-level security policy|row-level security policy|permission denied/, 'Você não tem permissão para realizar esta ação.'],
|
|
[/violates foreign key constraint/, 'Não foi possível salvar porque há um vínculo obrigatório ausente ou inválido.'],
|
|
[/null value in column "([^"]+)".*violates not-null constraint/, 'Campo obrigatório não preenchido.'],
|
|
[/invalid input value for enum ([^:]+): "([^"]+)"/, 'Valor inválido para uma opção do sistema.'],
|
|
[/invalid input syntax for type uuid/, 'Identificador inválido enviado para a API.'],
|
|
[/relation .* does not exist/, 'Recurso da API não encontrado.'],
|
|
[/function .* does not exist/, 'Endpoint da API não encontrado.'],
|
|
[/cors|preflight/, 'A API bloqueou a requisição por configuração de CORS.'],
|
|
]
|
|
|
|
for (const [pattern, translation] of translations) {
|
|
if (pattern.test(normalized)) return translation
|
|
}
|
|
|
|
return rawMessage
|
|
}
|
|
|
|
function isPortugueseMessage(message) {
|
|
return /[ãõáéíóúâêôç]/i.test(message) ||
|
|
/\b(erro|falha|não|nao|usuário|usuario|senha|campo|obrigatório|obrigatorio|sessão|sessao)\b/i.test(message)
|
|
}
|
|
|
|
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 }
|
|
}
|
|
}
|
|
|
|
function parseErrorBody(text) {
|
|
if (!text) return {}
|
|
|
|
try {
|
|
return JSON.parse(text)
|
|
} catch {
|
|
return { message: text }
|
|
}
|
|
}
|