mesa-react / frontend /src /api.js
Guilherme Silberfarb Costa
correcao de trabalhos tecnicos, layer inundacao e bugs
9ca4757
Raw
History Blame Contribute Delete
20.4 kB
function normalizeApiBase(value) {
if (value === undefined || value === null) return null
const text = String(value).trim()
if (!text || text === '.' || text === './') return ''
return text.endsWith('/') ? text.slice(0, -1) : text
}
const API_BASE = normalizeApiBase(import.meta.env.VITE_API_BASE)
?? (import.meta.env.DEV ? 'http://localhost:8000' : '')
const AUTH_TOKEN_STORAGE_KEY = 'mesa_auth_token'
let AUTH_TOKEN = ''
if (typeof window !== 'undefined') {
AUTH_TOKEN = window.localStorage.getItem(AUTH_TOKEN_STORAGE_KEY) || ''
}
export function setAuthToken(token) {
const value = String(token || '').trim()
AUTH_TOKEN = value
if (typeof window === 'undefined') return
if (value) {
window.localStorage.setItem(AUTH_TOKEN_STORAGE_KEY, value)
} else {
window.localStorage.removeItem(AUTH_TOKEN_STORAGE_KEY)
}
}
export function getAuthToken() {
return AUTH_TOKEN
}
export function apiUrl(path) {
return `${API_BASE}${String(path || '')}`
}
function authHeaders(extraHeaders = {}) {
const headers = { ...extraHeaders }
if (AUTH_TOKEN) headers['X-Auth-Token'] = AUTH_TOKEN
return headers
}
function emitAuthRequired(detail = 'Login obrigatorio') {
if (typeof window === 'undefined') return
const event = new CustomEvent('mesa:auth-required', { detail: { message: detail } })
window.dispatchEvent(event)
}
function describeFetchFailure(cause) {
const name = String(cause?.name || '').trim()
const message = String(cause?.message || '').trim()
if (name === 'AbortError') {
return 'a requisicao foi interrompida antes de concluir'
}
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
return 'sem conexao com a internet'
}
if (/failed to fetch|networkerror|load failed/i.test(message)) {
return 'conexao interrompida durante a transferencia ou bloqueio de rede/CORS/proxy'
}
return message || 'falha de rede nao identificada'
}
function buildFetchError(path, stage, cause, status = null) {
const endpoint = String(path || '').trim() || '(endpoint desconhecido)'
const reason = describeFetchFailure(cause)
const statusNumber = Number(status)
const statusLabel = Number.isFinite(statusNumber) ? ` (HTTP ${statusNumber})` : ''
const action = stage === 'body'
? `Falha ao receber dados de ${endpoint}${statusLabel}`
: `Falha ao conectar com ${endpoint}`
const error = new Error(`${action}. Motivo: ${reason}.`)
error.path = endpoint
error.stage = stage
error.reason = reason
if (Number.isFinite(statusNumber)) {
error.status = statusNumber
}
error.cause = cause
return error
}
async function fetchWithDiagnostics(path, options = {}) {
try {
return await fetch(`${API_BASE}${path}`, options)
} catch (cause) {
throw buildFetchError(path, 'request', cause)
}
}
async function responseBlobWithDiagnostics(response, path = '') {
try {
return await response.blob()
} catch (cause) {
throw buildFetchError(path, 'body', cause, response?.status)
}
}
async function handleResponse(response, path = '') {
if (!response.ok) {
let detail = 'Erro inesperado'
try {
const data = await response.json()
detail = data.detail || detail
} catch {
detail = response.statusText || detail
}
const detailMessage = typeof detail === 'string'
? detail
: Array.isArray(detail)
? detail
.map((item) => {
const loc = Array.isArray(item?.loc)
? item.loc.filter((part) => part !== 'body').join(' > ')
: ''
const msg = String(item?.msg || '').trim()
if (loc && msg) return `${loc}: ${msg}`
return msg || loc || ''
})
.filter(Boolean)
.join(' | ')
: String(detail?.message || detail?.detail || response.statusText || 'Erro inesperado')
if (response.status === 401 && path !== '/api/auth/login') {
setAuthToken('')
emitAuthRequired(detailMessage)
}
const error = new Error(detailMessage)
error.status = response.status
error.path = path
error.detail = detail
throw error
}
return response
}
async function postJson(path, body) {
const response = await fetchWithDiagnostics(path, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify(body),
})
await handleResponse(response, path)
return response.json()
}
async function postForm(path, formData) {
const response = await fetchWithDiagnostics(path, {
method: 'POST',
headers: authHeaders(),
body: formData,
})
await handleResponse(response, path)
return response.json()
}
async function getJson(path) {
const response = await fetchWithDiagnostics(path, { headers: authHeaders() })
await handleResponse(response, path)
return response.json()
}
async function getBlob(path) {
const response = await fetchWithDiagnostics(path, { headers: authHeaders() })
await handleResponse(response, path)
return responseBlobWithDiagnostics(response, path)
}
export function downloadBlob(blob, fileName) {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = fileName
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(url)
}
export const api = {
authLogin: (usuario, matricula) => postJson('/api/auth/login', { usuario, matricula }),
authMe: () => getJson('/api/auth/me'),
authLogout: () => postJson('/api/auth/logout', {}),
createSession: () => postJson('/api/sessions', {}),
pesquisaAdminConfig: () => getJson('/api/pesquisa/admin-config'),
pesquisaAdminConfigSalvar: (campos = {}) => postJson('/api/pesquisa/admin-config', { campos }),
pesquisarLogradourosEixos: (limite = 20000) => getJson(`/api/pesquisa/logradouros-eixos?limite=${encodeURIComponent(String(limite))}`),
pesquisarModelos(filtros = {}) {
const params = new URLSearchParams()
Object.entries(filtros).forEach(([key, value]) => {
if (value === null || value === undefined) return
const text = String(value).trim()
if (!text) return
params.append(key, text)
})
const query = params.toString()
return getJson(query ? `/api/pesquisa/modelos?${query}` : '/api/pesquisa/modelos')
},
pesquisarLocalizacaoAvaliando(payload = {}) {
return postJson('/api/pesquisa/localizar-avaliando', payload)
},
pesquisarMapaModelos(
modelosIds = [],
avaliando = null,
modoExibicao = 'pontos',
criterioEspacial = 'maior_distancia',
trabalhosTecnicosModelosModo = 'selecionados',
trabalhosTecnicosProximidadeModo = 'sem_proximidade',
trabalhosTecnicosRaioM = 1000,
agruparPontosMercado = false,
) {
const avaliandos = Array.isArray(avaliando) ? avaliando : []
const avaliandoUnico = !avaliandos.length && avaliando && typeof avaliando === 'object' ? avaliando : null
return postJson('/api/pesquisa/mapa-modelos', {
modelos_ids: modelosIds,
avaliando_lat: avaliandoUnico?.lat ?? null,
avaliando_lon: avaliandoUnico?.lon ?? null,
avaliandos: avaliandos.length ? avaliandos : null,
modo_exibicao: modoExibicao,
criterio_espacial: criterioEspacial,
trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo,
trabalhos_tecnicos_proximidade_modo: trabalhosTecnicosProximidadeModo,
trabalhos_tecnicos_raio_m: trabalhosTecnicosRaioM,
agrupar_pontos_mercado: Boolean(agruparPontosMercado),
})
},
uploadElaboracaoFile(sessionId, file) {
const form = new FormData()
form.append('session_id', sessionId)
form.append('file', file)
return postForm('/api/elaboracao/upload', form)
},
elaboracaoRepositorioModelos: () => getJson('/api/elaboracao/repositorio-modelos'),
elaboracaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/elaboracao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }),
pasteElaboracaoTable: (sessionId, pastedText) => postJson('/api/elaboracao/paste-table', { session_id: sessionId, pasted_text: pastedText }),
confirmSheet: (sessionId, sheetName) => postJson('/api/elaboracao/confirm-sheet', { session_id: sessionId, sheet_name: sheetName }),
applyImportColumns: (sessionId, colunas) => postJson('/api/elaboracao/apply-import-columns', { session_id: sessionId, colunas }),
mapCoords: (sessionId, colLat, colLon) => postJson('/api/elaboracao/map-coords', { session_id: sessionId, col_lat: colLat, col_lon: colLon }),
geocodificar: (sessionId, colCdlog, colNum, auto200) => postJson('/api/elaboracao/geocodificar', { session_id: sessionId, col_cdlog: colCdlog, col_num: colNum, auto_200: auto200 }),
geocodificarCorrecoes: (sessionId, correcoes, auto200) => postJson('/api/elaboracao/geocodificar-correcoes', { session_id: sessionId, correcoes, auto_200: auto200 }),
geocodificarReiniciar: (sessionId) => postJson('/api/elaboracao/geocodificar-reiniciar', { session_id: sessionId }),
geocodificarExcluirCoords: (sessionId) => postJson('/api/elaboracao/geocodificar-excluir-coords', { session_id: sessionId }),
applySelection(payload) {
return postJson('/api/elaboracao/apply-selection', payload)
},
classifyElaboracaoX: (sessionId, colunasX) => postJson('/api/elaboracao/classify-x', { session_id: sessionId, colunas_x: colunasX }),
searchTransformations: (sessionId, grauCoef, grauF, transformacoesFixas = {}, transformacaoYFixa = 'Livre') => postJson('/api/elaboracao/search-transformations', {
session_id: sessionId,
grau_min_coef: grauCoef,
grau_min_f: grauF,
transformacoes_fixas: transformacoesFixas,
transformacao_y_fixa: transformacaoYFixa,
}),
adoptSuggestion: (sessionId, indice) => postJson('/api/elaboracao/adopt-suggestion', { session_id: sessionId, indice }),
fitModel(payload) {
return postJson('/api/elaboracao/fit-model', payload)
},
updateModelDispersao: (sessionId, payload) => postJson('/api/elaboracao/model-dispersao', {
session_id: sessionId,
...(payload || {}),
}),
getDispersaoInterativo: (sessionId, alvo, options = {}) => postJson('/api/elaboracao/dispersao-interativo', {
session_id: sessionId,
alvo,
...(options || {}),
}),
getDiagnosticoInterativo: (sessionId, grafico) => postJson('/api/elaboracao/diagnostico-interativo', {
session_id: sessionId,
grafico,
}),
previewTransformElab: (sessionId, transformacaoY, transformacoesX) => postJson('/api/elaboracao/transform-preview', {
session_id: sessionId,
transformacao_y: transformacaoY,
transformacoes_x: transformacoesX,
}),
applyOutlierFilters: (sessionId, filtros) => postJson('/api/elaboracao/outliers/apply-filters', { session_id: sessionId, filtros }),
applyOutlierFiltersRecursive: (sessionId, filtros) => postJson('/api/elaboracao/outliers/apply-filters-recursive', { session_id: sessionId, filtros }),
restartOutlierIteration: (sessionId, outliersTexto, reincluirTexto, grauCoef, grauF) => postJson('/api/elaboracao/outliers/restart', {
session_id: sessionId,
outliers_texto: outliersTexto,
reincluir_texto: reincluirTexto,
grau_min_coef: grauCoef,
grau_min_f: grauF,
}),
outlierSummary: (sessionId, outliersTexto, reincluirTexto) => postJson('/api/elaboracao/outliers/summary', {
session_id: sessionId,
outliers_texto: outliersTexto,
reincluir_texto: reincluirTexto,
}),
clearOutlierHistory: (sessionId) => postJson('/api/elaboracao/outliers/clear-history', { session_id: sessionId }),
evaluationFieldsElab: (sessionId) => postJson('/api/elaboracao/evaluation/fields', { session_id: sessionId }),
evaluationCalculateElab: (sessionId, valoresX, indiceBase) => postJson('/api/elaboracao/evaluation/calculate', {
session_id: sessionId,
valores_x: valoresX,
indice_base: indiceBase,
}),
evaluationKnnDetailsElab: (sessionId, valoresX = null, indiceAvaliacao = null) => postJson('/api/elaboracao/evaluation/knn-details', {
session_id: sessionId,
valores_x: valoresX,
indice_avaliacao: indiceAvaliacao,
}),
evaluationClearElab: (sessionId) => postJson('/api/elaboracao/evaluation/clear', { session_id: sessionId }),
evaluationDeleteElab: (sessionId, indice, indiceBase) => postJson('/api/elaboracao/evaluation/delete', {
session_id: sessionId,
indice,
indice_base: indiceBase,
}),
getAvaliadores: () => getJson('/api/elaboracao/avaliadores'),
evaluationBaseElab: (sessionId, indiceBase) => postJson('/api/elaboracao/evaluation/base', {
session_id: sessionId,
indice_base: indiceBase,
}),
exportEvaluationElab: async (sessionId) => getBlob(`/api/elaboracao/evaluation/export?session_id=${encodeURIComponent(sessionId)}`),
exportEquationElab: async (sessionId, mode = 'excel') => getBlob(`/api/elaboracao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`),
exportModel: async (sessionId, nomeArquivo, elaborador, observacaoModelo = '') => {
const path = '/api/elaboracao/export-model'
const response = await fetchWithDiagnostics(path, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({
session_id: sessionId,
nome_arquivo: nomeArquivo,
elaborador,
observacao_modelo: observacaoModelo,
}),
})
await handleResponse(response, path)
return responseBlobWithDiagnostics(response, path)
},
saveModelRepository: (sessionId, nomeArquivo, elaborador, observacaoModelo = '', confirmarSubstituicao = false) => postJson('/api/elaboracao/save-model-repository', {
session_id: sessionId,
nome_arquivo: nomeArquivo,
elaborador,
observacao_modelo: observacaoModelo,
confirmar_substituicao: confirmarSubstituicao,
}),
exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`),
updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos', pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/elaboracao/map/update', {
session_id: sessionId,
variavel_mapa: variavelMapa,
modo_mapa: modoMapa,
ponto_raio_min: pontoRaioMin,
ponto_raio_max: pontoRaioMax,
}),
updateElaboracaoResiduosMap: (sessionId, variavelMapa, modoMapa = 'pontos', escalaExtremoAbs = null, pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/elaboracao/residuos/map/update', {
session_id: sessionId,
variavel_mapa: variavelMapa,
modo_mapa: modoMapa,
escala_extremo_abs: escalaExtremoAbs,
ponto_raio_min: pontoRaioMin,
ponto_raio_max: pontoRaioMax,
}),
previewMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/preview', { session_id: sessionId, coluna_data: colunaData }),
applyMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/apply', { session_id: sessionId, coluna_data: colunaData }),
getContext: (sessionId) => getJson(`/api/elaboracao/context?session_id=${encodeURIComponent(sessionId)}`),
uploadVisualizacaoFile(sessionId, file) {
const form = new FormData()
form.append('session_id', sessionId)
form.append('file', file)
return postForm('/api/visualizacao/upload', form)
},
visualizacaoRepositorioModelos: () => getJson('/api/visualizacao/repositorio-modelos'),
visualizacaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/visualizacao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }),
visualizacaoSection: (sessionId, secao, trabalhosTecnicosModelosModo = 'selecionados_e_outras_versoes') => postJson('/api/visualizacao/section', {
session_id: sessionId,
secao,
trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo,
}),
evaluationContextViz: (sessionId) => postJson('/api/visualizacao/evaluation/context', { session_id: sessionId }),
updateVisualizacaoMap: (sessionId, variavelMapa, trabalhosTecnicosModelosModo = 'selecionados_e_outras_versoes', pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/visualizacao/map/update', {
session_id: sessionId,
variavel_mapa: variavelMapa,
trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo,
ponto_raio_min: pontoRaioMin,
ponto_raio_max: pontoRaioMax,
}),
evaluationCalculateViz: (sessionId, valoresX, indiceBase, avaliando = null) => postJson('/api/visualizacao/evaluation/calculate', {
session_id: sessionId,
valores_x: valoresX,
indice_base: indiceBase,
avaliando_lat: avaliando?.lat ?? null,
avaliando_lon: avaliando?.lon ?? null,
}),
evaluationKnnDetailsViz: (sessionId, valoresX, avaliando = null) => postJson('/api/visualizacao/evaluation/knn-details', {
session_id: sessionId,
valores_x: valoresX,
avaliando_lat: avaliando?.lat ?? null,
avaliando_lon: avaliando?.lon ?? null,
}),
evaluationLocationMapViz: (sessionId, avaliando = null) => postJson('/api/visualizacao/evaluation/location-map', {
session_id: sessionId,
avaliando_lat: avaliando?.lat ?? null,
avaliando_lon: avaliando?.lon ?? null,
}),
exportEquationViz: (sessionId, mode = 'excel') => getBlob(`/api/visualizacao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`),
anexosModelos: () => getJson('/api/anexos/modelos'),
anexosModeloColunas: (modeloId) => getJson(`/api/anexos/modelos/${encodeURIComponent(String(modeloId || ''))}/columns`),
anexosGerar: (modeloId, bancoDadosColunas, avaliacao = null) => postJson('/api/anexos/generate', {
modelo_id: modeloId,
banco_dados_colunas: bancoDadosColunas,
avaliacao,
}),
anexosDownload: (filename) => getBlob(`/api/anexos/download/${encodeURIComponent(String(filename || ''))}`),
repositorioListar: () => getJson('/api/repositorio/modelos'),
repositorioUpload(files = [], { confirmarSubstituicao = false } = {}) {
const form = new FormData()
files.forEach((file) => {
form.append('files', file)
})
form.append('confirmar_substituicao', confirmarSubstituicao ? 'true' : 'false')
return postForm('/api/repositorio/upload', form)
},
repositorioDelete: (modelosIds = []) => postJson('/api/repositorio/delete', { modelos_ids: modelosIds }),
trabalhosTecnicosListar: ({ pagina = 1, tamanhoPagina = 15, texto = '', tipo = '', ano = '', incluirIds = false } = {}) => {
const params = new URLSearchParams({ pagina: String(pagina), tamanho_pagina: String(tamanhoPagina) })
if (texto) params.set('texto', texto)
if (tipo) params.set('tipo', tipo)
if (ano) params.set('ano', ano)
if (incluirIds) params.set('incluir_ids', 'true')
return getJson(`/api/trabalhos-tecnicos?${params.toString()}`)
},
trabalhosTecnicosMapa: (trabalhosIds = null, modoExibicao = 'clusterizado', avaliando = null) => postJson('/api/trabalhos-tecnicos/mapa', {
trabalhos_ids: trabalhosIds,
modo_exibicao: modoExibicao,
avaliando_lat: avaliando?.lat ?? null,
avaliando_lon: avaliando?.lon ?? null,
}),
trabalhosTecnicosDetalhe: (trabalhoId) => postJson('/api/trabalhos-tecnicos/detalhe', { trabalho_id: trabalhoId }),
trabalhosTecnicosCadastrar: (payload = {}) => postJson('/api/trabalhos-tecnicos/cadastrar', payload),
trabalhosTecnicosSalvar: (payload = {}) => postJson('/api/trabalhos-tecnicos/salvar', payload),
trabalhosTecnicosExcluir: (trabalhoId, confirmacao) => postJson('/api/trabalhos-tecnicos/excluir', { trabalho_id: trabalhoId, confirmacao }),
trabalhosTecnicosModelosMl: () => getJson('/api/trabalhos-tecnicos/modelos-ml'),
trabalhosTecnicosCadastrarModeloMl: (nome) => postJson('/api/trabalhos-tecnicos/modelos-ml', { nome }),
logsStatus: () => getJson('/api/logs/status'),
logsEvents({ scope = '', usuario = '', limit = 200 } = {}) {
const params = new URLSearchParams()
const scopeText = String(scope || '').trim()
const usuarioText = String(usuario || '').trim()
const limitNumber = Number(limit)
if (scopeText) params.set('scope', scopeText)
if (usuarioText) params.set('usuario', usuarioText)
if (Number.isFinite(limitNumber) && limitNumber > 0) params.set('limit', String(Math.floor(limitNumber)))
const query = params.toString()
return getJson(query ? `/api/logs/events?${query}` : '/api/logs/events')
},
}