From 1b8af52d5bfd4cc27a95d7a4fa986393c48f1b4c Mon Sep 17 00:00:00 2001 From: Yuri Baza Date: Wed, 10 Dec 2025 19:13:09 -0300 Subject: [PATCH 1/2] Fix/295 ajustes relatorio local coleta (#307) --- src/controllers/relatorios-controller.js | 11 +++- src/helpers/formata-dados-relatorio.js | 67 +++++++++++++++++++----- src/reports/assets/styles/root.css | 12 +++-- src/reports/templates/LocaisColeta.tsx | 58 ++++++++++++++------ 4 files changed, 112 insertions(+), 36 deletions(-) diff --git a/src/controllers/relatorios-controller.js b/src/controllers/relatorios-controller.js index 6ef76422..059eb964 100644 --- a/src/controllers/relatorios-controller.js +++ b/src/controllers/relatorios-controller.js @@ -472,7 +472,7 @@ export const obtemDadosDoRelatorioDeColetaPorColetorEIntervaloDeData = async (re export const obtemDadosDoRelatorioDeLocalDeColeta = async (req, res, next) => { const { paginacao } = req; const { limite, pagina, offset } = paginacao; - const { local, dataInicio, dataFim } = req.query; + const { local, dataInicio, dataFim, showCoord } = req.query; let whereLocal = {}; let whereData = {}; @@ -533,7 +533,13 @@ export const obtemDadosDoRelatorioDeLocalDeColeta = async (req, res, next) => { { model: Especie, attributes: ['id', 'nome'], - // required: true, + include: [ + { + model: Autor, + attributes: ['id', 'nome'], + as: 'autor', + }, + ], }, { model: LocalColeta, @@ -582,6 +588,7 @@ export const obtemDadosDoRelatorioDeLocalDeColeta = async (req, res, next) => { dados: dadosFormatados.locais, total: dadosFormatados?.quantidadeTotal || 0, textoFiltro: formataTextFilter(local, dataInicio, dataFim || new Date()), + showCoord: showCoord === 'true', }); const readable = new Readable(); diff --git a/src/helpers/formata-dados-relatorio.js b/src/helpers/formata-dados-relatorio.js index 22e06c13..73b69b4d 100644 --- a/src/helpers/formata-dados-relatorio.js +++ b/src/helpers/formata-dados-relatorio.js @@ -218,17 +218,19 @@ export function agruparPorFamiliaGeneroEspecie(dados) { return resultado; } -function formatarCoordenadas(lat, lon) { - const latDir = lat >= 0 ? 'N' : 'S'; - const lonDir = lon >= 0 ? 'E' : 'W'; - return `${Math.abs(lat).toFixed(4)}° ${latDir}, ${Math.abs(lon).toFixed(4)}° ${lonDir}`; -} - export function agruparPorLocal(dados) { const agrupado = {}; let quantidadeTotal = 0; - dados.sort((a, b) => a?.familia?.nome.localeCompare(b?.familia?.nome)).forEach(entradaOriginal => { + dados.sort((a, b) => { + const familia = a?.familia?.nome.localeCompare(b?.familia?.nome); + if (familia !== 0) return familia; + + const genero = a?.genero?.nome.localeCompare(b?.genero?.nome); + if (genero !== 0) return genero; + + return a?.especy?.nome.localeCompare(b?.especy?.nome); + }).forEach(entradaOriginal => { const locaisColetum = entradaOriginal.locais_coletum; const cidade = locaisColetum?.cidade; const estado = cidade?.estado?.nome || 'Desconhecido'; @@ -236,15 +238,13 @@ export function agruparPorLocal(dados) { const municipio = cidade?.nome || 'Desconhecido'; const local = locaisColetum?.descricao?.trim() || 'Local não informado'; - const coordenadasFormatadas = cidade - ? formatarCoordenadas(cidade.latitude, cidade.longitude) - : 'Coordenadas indisponíveis'; - const chave = `${estado} > ${municipio} > ${local}`; const entrada = { ...entradaOriginal, - coordenadasFormatadas, + latitude: cidade?.latitude || null, + longitude: cidade?.longitude || null, + autor: entradaOriginal.especy?.autor?.nome || 'Não Informado', }; if (!agrupado[chave]) { @@ -253,7 +253,8 @@ export function agruparPorLocal(dados) { estadoSigla, municipio, local, - coordenadas: coordenadasFormatadas, + latitude: cidade?.latitude || null, + longitude: cidade?.longitude || null, quantidadeRegistros: 0, registros: [], }; @@ -262,17 +263,55 @@ export function agruparPorLocal(dados) { agrupado[chave].registros.push(entrada); agrupado[chave].quantidadeRegistros += 1; quantidadeTotal += 1; + }); // Transforma o objeto em um array const locais = Object.values(agrupado); + const locaisComResumo = adicionarResumoTaxonomicoPorLocal(locais); + return { - locais, // agora é um array + locais: locaisComResumo, quantidadeTotal, }; } +export function adicionarResumoTaxonomicoPorLocal(locaisAgrupados) { + return locaisAgrupados.map(local => { + const familias = new Set(); + const generos = new Set(); + const especies = new Set(); + + for (const reg of local.registros) { + // família + const familiaId = reg.familia_id ?? reg.familia?.id; + if (familiaId != null) { + familias.add(familiaId); + } + + // gênero + const generoId = reg.genero_id ?? reg.genero?.id; + if (generoId != null) { + generos.add(generoId); + } + + // espécie + const especieId = reg.especie_id ?? reg.especy?.id; + if (especieId != null) { + especies.add(especieId); + } + } + + return { + ...local, + quantidadeFamilias: familias.size, + quantidadeGeneros: generos.size, + quantidadeEspecies: especies.size, + }; + }); +} + export function agruparPorGenero(dados) { return dados.map(familia => { const generosMap = new Map(); diff --git a/src/reports/assets/styles/root.css b/src/reports/assets/styles/root.css index 6b2cb12c..6cdb7611 100644 --- a/src/reports/assets/styles/root.css +++ b/src/reports/assets/styles/root.css @@ -110,11 +110,17 @@ tfoot { .grupoLocalColeta { display: flex; align-items: center; - justify-content: space-between; + gap: 1rem; } -.grupoLocalColeta div { - width: calc(33%); /* 33% menos 2.5rem de espaçamento das margens */ +.grupoLocalColeta div:first-child { + display: flex; + gap: 0.5rem; + width: calc(40); +} + +.grupoLocalColeta div:last-child { + width: calc(60); } .grupoLocalColeta div h1 { diff --git a/src/reports/templates/LocaisColeta.tsx b/src/reports/templates/LocaisColeta.tsx index c5d7ba2d..4e9d5e18 100644 --- a/src/reports/templates/LocaisColeta.tsx +++ b/src/reports/templates/LocaisColeta.tsx @@ -21,7 +21,9 @@ interface Registro { genero: { nome: string; } - coordenadasFormatadas: string; + latitude: number | null; + longitude: number | null; + autor?: string; } interface LocaisColeta { @@ -31,19 +33,23 @@ interface LocaisColeta { local: string; registros: Registro[]; quantidadeRegistros: number; + quantidadeEspecies?: number; + quantidadeGeneros?: number; + quantidadeFamilias?: number; } interface RelacaoLocaisColetaProps { dados: LocaisColeta[]; total?: number; textoFiltro?: string; + showCoord?: boolean; } -function RelacaoLocaisColeta({ dados, total, textoFiltro }: RelacaoLocaisColetaProps) { - const renderTotalizador = (geral: boolean, qtd?: number) => { +function RelacaoLocaisColeta({ dados, total, textoFiltro, showCoord = false }: RelacaoLocaisColetaProps) { + const renderTotalizador = (geral: boolean, qtd?: number, qtdEspecies?: number, qtdGeneros?: number, qtdFamilias?: number) => { return (
- Total {geral ? 'geral' : 'do local'}: {geral ? total : qtd} + Total {geral ? 'geral' : 'do local'}: {geral ? total : qtd} {!geral ? `(Famílias: ${qtdFamilias || 0}, Gêneros: ${qtdGeneros || 0}, Espécies: ${qtdEspecies || 0})`: ''}
) } @@ -57,26 +63,45 @@ function RelacaoLocaisColeta({ dados, total, textoFiltro }: RelacaoLocaisColetaP return `${data_coleta_dia}/${romanoMeses[data_coleta_mes - 1]}/${data_coleta_ano}`; } + const converteDecimalParaDMS = (decimal: number | null, isLatitude = true) => { + if (decimal === null || decimal === undefined) { + return ''; + } + + const abs = Math.abs(decimal); + const graus = Math.floor(abs); + const minutosDecimal = (abs - graus) * 60; + const minutos = Math.floor(minutosDecimal); + const segundos = ((minutosDecimal - minutos) * 60).toFixed(2); + + let hemisferio; + if (isLatitude) { + hemisferio = decimal >= 0 ? 'N' : 'S'; + } else { + hemisferio = decimal >= 0 ? 'E' : 'W'; + } + + return `${graus}° ${minutos}' ${segundos}" ${hemisferio}`; + }; + const obtemCordenadas = (registro: Registro) => { - const { coordenadasFormatadas } = registro; - if (!coordenadasFormatadas) return { latitude: '', longitude: '' }; - const [latitude, longitude] = coordenadasFormatadas.split(','); return { - latitude: latitude ? latitude.trim() : '', - longitude: longitude ? longitude.trim() : '' + latitude: registro.latitude ? converteDecimalParaDMS(registro.latitude, true) : '', + longitude: registro.longitude ? converteDecimalParaDMS(registro.longitude, false) : '' }; } const renderTable = (registros: Registro[]) => { return ( - +
- - + + {showCoord && } + {showCoord && } @@ -89,8 +114,9 @@ function RelacaoLocaisColeta({ dados, total, textoFiltro }: RelacaoLocaisColetaP - - + + {showCoord && } + {showCoord && } ) @@ -106,8 +132,6 @@ function RelacaoLocaisColeta({ dados, total, textoFiltro }: RelacaoLocaisColetaP

UF.: {item.estadoSigla}

-
-

Município: {item.municipio}

@@ -115,7 +139,7 @@ function RelacaoLocaisColeta({ dados, total, textoFiltro }: RelacaoLocaisColetaP
{renderTable(item.registros)} - {renderTotalizador(false, item.quantidadeRegistros)} + {renderTotalizador(false, item.quantidadeRegistros, item.quantidadeEspecies, item.quantidadeGeneros, item.quantidadeFamilias)} ) } From d08dd4fb50d6c24ebdc8f675c80ad00a667fe2df Mon Sep 17 00:00:00 2001 From: Matheus Coitinho <142349293+MatCoitinho@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:16:32 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Corre=C3=A7=C3=A3o=20do=20coletor=20n=C3=A3?= =?UTF-8?q?o=20aparecer=20na=20edi=C3=A7=C3=A3o=20e=20nos=20detalhes=20do?= =?UTF-8?q?=20tombo=20(#313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/tombos-controller.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/controllers/tombos-controller.js b/src/controllers/tombos-controller.js index f89350c4..a9ed0db5 100644 --- a/src/controllers/tombos-controller.js +++ b/src/controllers/tombos-controller.js @@ -1164,6 +1164,12 @@ export const obterTombo = async (request, response, next) => { colecaoInicial: tombo.colecoes_anexa !== null ? tombo.colecoes_anexa?.tipo : '', complementoInicial: tombo.localizacao !== null && tombo.localizacao !== undefined ? tombo.localizacao?.complemento : '', hcf: tombo.hcf, + coletor: tombo.coletore + ? { + id: tombo.coletore?.id, + nome: tombo.coletore?.nome, + } + : null, situacao: tombo.situacao, data_tombo: tombo.data_tombo, observacao: tombo.observacao !== null ? tombo.observacao : '',
Data Coleta Família EspécieLatitudeLongitudeAutorLatitudeLongitudeNº do Tombo
{criaData(item)} {familia?.nome} {genero?.nome} {especy?.nome}{cordenadas.latitude}{cordenadas.longitude}{item.autor}{cordenadas.latitude}{cordenadas.longitude}{item.hcf}