diff --git a/backend/src/services/tendersMonthService.js b/backend/src/services/tendersMonthService.js index 185be54..1f25a3b 100644 --- a/backend/src/services/tendersMonthService.js +++ b/backend/src/services/tendersMonthService.js @@ -9,11 +9,12 @@ async function getTendersByMonth(start, end) { const { data, error } = await supabase .from('tendersmonth') .select('*') - .gte('year', startYear) - .lte('year', endYear) - .gte('month', startMonth) - .lte('month', endMonth); - + .or( + `and(year.eq.${startYear},month.gte.${startMonth}),` + + `and(year.gt.${startYear},year.lt.${endYear}),` + + `and(year.eq.${endYear},month.lte.${endMonth})` + ); + if (error) { throw new Error(error.message); } @@ -23,5 +24,4 @@ async function getTendersByMonth(start, end) { module.exports = { getTendersByMonth, -}; - +}; \ No newline at end of file diff --git "a/docs/Como executar/Front-end/Testes unit\303\241rios/Testes do Componente Filtro.md" "b/docs/Como executar/Front-end/Testes unit\303\241rios/Testes do Componente Filtro.md" index 3130ce3..972c368 100644 --- "a/docs/Como executar/Front-end/Testes unit\303\241rios/Testes do Componente Filtro.md" +++ "b/docs/Como executar/Front-end/Testes unit\303\241rios/Testes do Componente Filtro.md" @@ -2,43 +2,25 @@ ## Descrição Geral -Este conjunto de testes foi desenvolvido para verificar o comportamento e a renderização correta do componente `Filtro` no projeto. A abordagem adotada utiliza a biblioteca `@testing-library/react` e `Jest` para renderizar o componente e validar os elementos visíveis na interface de usuário, garantindo que todos os elementos essenciais estejam presentes e corretamente configurados. +Este conjunto de testes foi desenvolvido para verificar o comportamento e a renderização correta do componente `Filtro` no projeto. A abordagem adotada utiliza a biblioteca `@testing-library/react` e `Jest` para renderizar o componente e validar os elementos visíveis na interface do usuário, garantindo que todos os elementos essenciais estejam presentes e configurados corretamente. Além disso, o mock de funções de API e de gráficos foi implementado para isolar o componente e testar seu comportamento de forma independente. -### Casos de Teste +## Casos de Teste -#### Renderização do Componente Filtro +### 1. Renderização e Interação com o Componente Filtro -1. **Objetivo**: Verificar se o componente `Filtro` é renderizado corretamente com todos os seus elementos principais. -2. **Teste**: - - Renderizar o componente `Filtro`. - - Verificar se o título com o texto "Pesquise por cidade, período e tema" está presente no documento. - - Verificar se o seletor de cidades está presente com a opção "Selecione uma cidade". +**Objetivo:** Verificar se o componente `Filtro` é renderizado corretamente, com todos os seus elementos principais, e interage corretamente com os selecionadores de data. -#### Manipulação da Seleção de Cidade +**Teste:** -1. **Objetivo**: Garantir que a seleção de uma cidade no campo de entrada é manipulada corretamente. -2. **Teste**: - - Renderizar o componente `Filtro`. - - Simular a seleção de uma cidade do seletor de cidades. - - Verificar se a cidade selecionada é refletida no campo de entrada. - -#### Manipulação de Seleção de Datas Inicial e Final - -1. **Objetivo**: Testar a funcionalidade de seleção de datas e garantir que as datas inicial e final são manipuladas corretamente. -2. **Teste**: - - Renderizar o componente `Filtro`. - - Simular a seleção de uma data inicial com o valor "01 / 2023". - - Simular a seleção de uma data final com o valor "12 / 2023". - - Verificar se os campos de entrada para "Data Inicial" e "Data final" contêm os valores selecionados. - -#### Verificação dos Gráficos - -1. **Objetivo**: Garantir que os gráficos são renderizados corretamente após a seleção de dados. -2. **Teste**: - - Renderizar o componente `Filtro`. - - Verificar se os gráficos para "Valor Empenhado", "Valor Liquidado" e "Valor Pago" estão presentes no documento. - - Verificar se o número de gráficos renderizados corresponde ao esperado (três gráficos). +- Renderizar o componente `Filtro`. +- Verificar se o título com o texto "Pesquise por período" está presente no documento. +- Verificar se os campos de data inicial e final estão presentes. +- Simular a seleção de uma data inicial com o valor "01/2023". +- Simular a seleção de uma data final com o valor "12/2023". +- Verificar se a função `fettchYearAndMonthTender` foi chamada após a seleção das datas. +- Verificar se os valores totais ("Total Empenhado", "Total Liquidado", "Total Pago") são exibidos no documento. +- Verificar se os gráficos são renderizados corretamente, garantindo que o mock de gráficos está presente e o número de gráficos renderizados corresponde ao esperado (dois gráficos). ## Considerações Finais -Esses testes garantem que o componente `Filtro` está sendo renderizado corretamente e que os elementos principais estão presentes com o conteúdo esperado. Além disso, verificam se as funcionalidades básicas, como a manipulação de entradas e a seleção de datas, estão funcionando conforme o esperado. Estes testes são essenciais para assegurar que o componente se comporta conforme projetado em diversos cenários de uso. +Os testes garantem que o componente `Filtro` é renderizado corretamente, com os elementos essenciais presentes, e que as funcionalidades básicas, como a manipulação das seleções de data e a exibição de gráficos, estão funcionando conforme o esperado. A utilização de mocks para funções de API e gráficos assegura que o teste seja executado de maneira isolada, focando exclusivamente no comportamento do componente. diff --git a/front/package-lock.json b/front/package-lock.json index f8a3487..bc5159d 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -10542,9 +10542,10 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" diff --git a/front/src/app/globals.css b/front/src/app/globals.css index 745952f..cd891e5 100644 --- a/front/src/app/globals.css +++ b/front/src/app/globals.css @@ -59,6 +59,23 @@ color: #FFFFFF; /* Texto branco */ } +/* Estilo para linhas do gráfico */ +.high-contrast .chart-line-emp-enhanced, +.high-contrast .chart-line-liquidated, +.high-contrast .chart-line-paid { + stroke: #FFFFFF; /* Linha branca para manter contraste */ +} + +/* Estilo para a legenda do gráfico */ +.high-contrast .chart-legend-item { + color: #FFD700; /* Cor dourada para itens da legenda */ +} + +/* Estilo para explicações do gráfico */ +.high-contrast .chart-explanation { + color: #FFD700; /* Cor dourada para explicações */ +} + /* Estilo para aumentar o tamanho da fonte */ .font-lg { font-size: 1.25rem; /* Tamanho da fonte aumentado */ @@ -68,3 +85,22 @@ .font-original { font-size: 1rem; /* Tamanho da fonte original */ } + +/* Força a aplicação da cor vermelha */ +.swiper-button-next, +.swiper-button-prev { + color: #ED1C24 !important; +} +/* styles.css or a relevant CSS file */ +.legend-color-red { + background-color: #ED1C24 !important; + } + + .legend-color-orange { + background-color: #F19C28 !important; + } + + .legend-color-green { + background-color: #2FB551 !important; + } + \ No newline at end of file diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index 861a558..2eb0581 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -1,25 +1,23 @@ "use client"; -import { Search, MapPin, CalendarClock, MoveRight } from "lucide-react"; +import { Search, MapPin, CalendarClock, MoveRight } from "lucide-react"; import Busca from "@/components/Busca"; import dynamic from 'next/dynamic'; import Slider from "@/components/Slider"; import Pilares from "@/components/Pilares"; - -const Grafico = dynamic(() => import('@/components/Grafico'), { ssr: false }); +import Mensagem from "@/components/Mensagem"; +import Grafico from '@/components/Grafico'; export default function Home() { return (
-
- - -
-
- {/* Conteúdo adicional pode ir aqui */} -
+ + +
+ +
); } diff --git a/front/src/components/Busca.tsx b/front/src/components/Busca.tsx index 98450b4..0b09e79 100644 --- a/front/src/components/Busca.tsx +++ b/front/src/components/Busca.tsx @@ -5,21 +5,21 @@ import Link from 'next/link'; const Busca = () => { return ( -
+
Lupa1 -
-

- Faça sua busca
filtrada
: -

- Veja os dados para cada município do Estado de
Minas Gerais -

+
+

+ Faça sua busca
filtrada
:

+

+ Veja os dados para cada município do Estado de
Minas Gerais +

- diff --git a/front/src/components/Filtro.tsx b/front/src/components/Filtro.tsx index 77f8509..cff4e22 100644 --- a/front/src/components/Filtro.tsx +++ b/front/src/components/Filtro.tsx @@ -1,16 +1,15 @@ -'use client'; +"use client"; + import React, { useState, useEffect } from 'react'; -import Chart from 'react-apexcharts'; +import dynamic from 'next/dynamic'; import { ApexOptions } from 'apexcharts'; -import { fetchCities, fetchUnits, searchLicitacoes } from '../services/api'; -import { MapPin, CalendarClock, MoveLeft, MoveRight } from 'lucide-react'; +import { CalendarClock, MoveLeft, MoveRight, HelpCircle } from 'lucide-react'; // Importação do HelpCircle import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import { ptBR } from 'date-fns/locale'; +import { fettchYearAndMonthTender } from '../services/api'; -interface LicitacaoData { - administrative_unit_id: number; - city_id: number; +interface Dados { committed_value: number; liquidated_value: number; paid_value: number; @@ -18,160 +17,333 @@ interface LicitacaoData { month: number; } -const Filtro: React.FC = () => { - const [selectedCityId, setSelectedCityId] = useState(null); - const [selectedUnit, setSelectedUnit] = useState(''); - const [cities, setCities] = useState<{ id: number; name: string }[]>([]); - const [units, setUnits] = useState<{ id: number; name: string }[]>([]); - const [empenhadoSeries, setEmpenhadoSeries] = useState([]); - const [liquidadoSeries, setLiquidadoSeries] = useState([]); - const [pagoSeries, setPagoSeries] = useState([]); +const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +const Dashboard: React.FC = () => { + const [data, setData] = useState([]); + const [lineChartSeries, setLineChartSeries] = useState([]); + const [pieChartSeries, setPieChartSeries] = useState([]); + const [totalSales, setTotalSales] = useState(0); + const [totalRevenue, setTotalRevenue] = useState(0); + const [totalUsers, setTotalUsers] = useState(0); const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); - const [isClient, setIsClient] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const isHighContrastMode = document.documentElement.classList.contains('high-contrast'); - const [chartOptions, setChartOptions] = useState({ + + // Inicialização dos estados dos gráficos com opções padrão + const [lineChartOptions, setLineChartOptions] = useState({ chart: { - type: 'bar', + type: 'line', + height: 350, + toolbar: { + show: true, + }, + zoom: { + enabled: true, + }, + animations: { + enabled: true, + }, background: '#ffffff', + foreColor: '#000000', }, - plotOptions: { - bar: { - horizontal: false, - distributed: true, - barHeight: '100%', - colors: { - ranges: [{ from: 0, to: 5000000000, color: '#ED1C24' }], - }, - }, + stroke: { + curve: 'smooth', + width: 2, + }, + dataLabels: { + enabled: false, + }, + markers: { + size: 4, }, xaxis: { - categories: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], - type: 'category', + categories: [], + labels: { + style: { + fontSize: '12px', + }, + }, + tickAmount: 10, }, yaxis: { - labels: { - formatter: (value) => `R$ ${value.toLocaleString('pt-BR')}`, // Adiciona o símbolo R$ e formata os números + title: { + text: 'Valores', + style: { + fontSize: '14px', }, + }, + labels: { + formatter: (value: number) => `R$ ${value.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`, + style: { + fontSize: '12px', + }, + }, }, - dataLabels: { - enabled: false, - }, - legend: { - show: false, + position: 'top', + horizontalAlign: 'center', + fontSize: '14px', }, - responsive: [ - { breakpoint: 2500, options: { chart: { height: 200, width: 1200 } } }, - { breakpoint: 1025, options: { chart: { height: 424, width: 800 } } }, - { breakpoint: 640, options: { chart: { height: 200, width: 310 } } }, - { breakpoint: 769, options: { chart: { height: 424, width: 700 } } }, - - ], - theme: { - mode: 'light', // Default mode + tooltip: { + y: { + formatter: (value: number) => `R$ ${value.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`, + }, }, - }); - useEffect(() => { - setIsClient(true); - - const root = document.documentElement; - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.attributeName === "class") { - const isDarkMode = root.classList.contains('dark'); - const isHighContrastMode = root.classList.contains('high-contrast'); - - setChartOptions((prevOptions) => ({ - ...prevOptions, - chart: { - ...prevOptions.chart, - background: isHighContrastMode ? '#000000' : isDarkMode ? '#1f1f1f' : '#ffffff', - foreColor: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000', + colors: ['#ED1C24', '#F19C28', '#2FB551'], + responsive: [ + { + breakpoint: 1024, + options: { + xaxis: { + labels: { + style: { + fontSize: '10px', + }, }, - xaxis: { - ...prevOptions.xaxis, + }, + yaxis: [ + { labels: { style: { - colors: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000', - } - } + fontSize: '10px', + }, + }, + }, + ], + legend: { + fontSize: '12px', + }, + }, + }, + { + breakpoint: 768, + options: { + chart: { + height: 300, + }, + xaxis: { + labels: { + style: { + fontSize: '8px', + }, }, - yaxis: { - ...prevOptions.yaxis, + }, + yaxis: [ + { labels: { style: { - colors: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000', + fontSize: '8px', }, - formatter: (value) => `R$ ${value.toLocaleString('pt-BR')}`, // Adiciona o símbolo R$ e formata os números - } + }, }, - plotOptions: { - ...prevOptions.plotOptions, - bar: { - ...prevOptions.plotOptions?.bar ?? {}, - colors: { - ranges: [{ - from: 0, - to: 5000000000, - color: isHighContrastMode ? '#FFEA00' : isDarkMode ? '#ED1C24' : '#ED1C24', - }] - } - } - } - })); - } - }); - }); - - observer.observe(root, { - attributes: true, - attributeFilter: ['class'] - }); - - return () => observer.disconnect(); - }, []); - const loadCities = async () => { + ], + legend: { + fontSize: '10px', + }, + }, + }, + ], + }); + + const [pieChartOptions, setPieChartOptions] = useState({ + chart: { + type: 'pie', + height: 350, + background: '#ffffff', + foreColor: '#000000', + }, + labels: ['Valor Empenhado', 'Valor Liquidado', 'Valor Pago'], + legend: { + position: 'bottom', + fontSize: '14px', + }, + tooltip: { + y: { + formatter: (value: number) => `R$ ${value.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`, + }, + }, + colors: ['#ED1C24', '#F19C28', '#2FB551'], + responsive: [ + { + breakpoint: 1024, + options: { + legend: { + fontSize: '12px', + }, + }, + }, + { + breakpoint: 768, + options: { + chart: { + height: 300, + }, + legend: { + fontSize: '10px', + }, + }, + }, + ], + }); + + const fetchData = async (startDate: Date | null, endDate: Date | null) => { + if (!startDate || !endDate) return; + try { - const cities = await fetchCities(); - setCities(cities); + const startMonth = (startDate.getMonth() + 1).toString().padStart(2, '0'); + const startYear = startDate.getFullYear().toString(); + const endMonth = (endDate.getMonth() + 1).toString().padStart(2, '0'); + const endYear = endDate.getFullYear().toString(); + + const fetchedData = await fettchYearAndMonthTender({ startYear, startMonth, endYear, endMonth }); + + if (Array.isArray(fetchedData) && fetchedData.length > 0) { + setData(fetchedData); + + const categories = fetchedData.map(item => `${item.year}-${String(item.month).padStart(2, '0')}`); + const committedValues = fetchedData.map((item: Dados) => item.committed_value); + const liquidatedValues = fetchedData.map((item: Dados) => item.liquidated_value); + const paidValues = fetchedData.map((item: Dados) => item.paid_value); + + setLineChartSeries([ + { name: 'Valor Empenhado', data: committedValues }, + { name: 'Valor Liquidado', data: liquidatedValues }, + { name: 'Valor Pago', data: paidValues }, + ]); + + setLineChartOptions(prevOptions => ({ + ...prevOptions, + xaxis: { + ...prevOptions.xaxis, + categories, + }, + })); + + const totalCommitted = committedValues.reduce((acc, val) => acc + val, 0); + const totalLiquidated = liquidatedValues.reduce((acc, val) => acc + val, 0); + const totalPaid = paidValues.reduce((acc, val) => acc + val, 0); + + setPieChartSeries([totalCommitted, totalLiquidated, totalPaid]); + setTotalSales(totalCommitted); + setTotalRevenue(totalLiquidated); + setTotalUsers(totalPaid); + + setErrorMessage(null); + } else { + setErrorMessage('Nenhum dado encontrado para o período selecionado.'); + } } catch (error) { - console.error('Erro ao buscar cidades:', error); + console.error('Erro ao buscar dados:', error); + setErrorMessage('Erro ao buscar dados.'); } }; - const loadUnits = async () => { - try { - const units = await fetchUnits(); - setUnits(units); - } catch (error) { - console.error('Erro ao buscar unidades:', error); - } + useEffect(() => { + fetchData(startDate, endDate); + }, [startDate, endDate]); + + const updateChartOptionsForTheme = () => { + const isDarkMode = document.documentElement.classList.contains('dark'); + const isHighContrastMode = document.documentElement.classList.contains('high-contrast'); + + const backgroundColor = isHighContrastMode ? '#000000' : isDarkMode ? '#1f1f1f' : '#ffffff'; + const foreColor = isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000'; + + setLineChartOptions(prevOptions => ({ + ...prevOptions, + chart: { + ...prevOptions.chart, + background: backgroundColor, + foreColor: foreColor, + }, + xaxis: { + ...prevOptions.xaxis, + labels: { + ...prevOptions.xaxis?.labels, + style: { + ...prevOptions.xaxis?.labels?.style, + colors: foreColor, + }, + }, + }, + yaxis: { + ...prevOptions.yaxis, + labels: { + ...(Array.isArray(prevOptions.yaxis) ? {} : prevOptions.yaxis?.labels), + style: { + ...(Array.isArray(prevOptions.yaxis) ? {} : prevOptions.yaxis?.labels?.style), + colors: foreColor, + }, + }, + }, + legend: { + ...prevOptions.legend, + labels: { + colors: foreColor, + }, + }, + })); + + setPieChartOptions(prevOptions => ({ + ...prevOptions, + chart: { + ...prevOptions.chart, + background: backgroundColor, + foreColor: foreColor, + }, + legend: { + ...prevOptions.legend, + labels: { + colors: foreColor, + }, + }, + })); }; + useEffect(() => { + updateChartOptionsForTheme(); + + const observer = new MutationObserver(() => { + updateChartOptionsForTheme(); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'], + }); + + return () => { + observer.disconnect(); + }; + }, []); + const renderCustomHeader = ({ date, + decreaseMonth, + increaseMonth, decreaseYear, increaseYear, }: { date: Date; + decreaseMonth: () => void; + increaseMonth: () => void; decreaseYear: () => void; increaseYear: () => void; }) => { const months = [ 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', - 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro' + 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro', ]; return (
- {months[date.getMonth()]} de {date.getFullYear()} -
@@ -187,179 +359,133 @@ const Filtro: React.FC = () => { const handleEndDateChange = (date: Date | null) => { if (date && startDate && date < startDate) { - setEndDate(startDate); - } else { - setEndDate(date); + setStartDate(date); } + setEndDate(date); }; - const fetchAndProcessData = async () => { - if (!selectedCityId) return; - - try { - const startMonth = startDate ? (startDate.getMonth() + 1).toString().padStart(2, '0') : ''; - const startYear = startDate ? startDate.getFullYear().toString() : ''; - const endMonth = endDate ? (endDate.getMonth() + 1).toString().padStart(2, '0') : ''; - const endYear = endDate ? endDate.getFullYear().toString() : ''; - - const data: LicitacaoData[] = await searchLicitacoes({ - startYear, - startMonth, - endYear, - endMonth, - cityId: selectedCityId, - unitId: selectedUnit, - }); - - const empData: { [key: number]: number[] } = {}; - const liquidData: { [key: number]: number[] } = {}; - const pagoData: { [key: number]: number[] } = {}; - - const filteredData = selectedUnit - ? data.filter(item => item.administrative_unit_id === Number(selectedUnit)) - : data; - - filteredData.forEach((item: LicitacaoData) => { - const unitId = item.administrative_unit_id; - const month = item.month - 1; - - if (!empData[unitId]) empData[unitId] = new Array(12).fill(0); - if (!liquidData[unitId]) liquidData[unitId] = new Array(12).fill(0); - if (!pagoData[unitId]) pagoData[unitId] = new Array(12).fill(0); - - empData[unitId][month] += item.committed_value; - liquidData[unitId][month] += item.liquidated_value; - pagoData[unitId][month] += item.paid_value; - }); - - const empenhadoSeries: ApexAxisChartSeries = Object.keys(empData).map(key => ({ - name: `Unidade ${key}`, - data: empData[parseInt(key)] - })); - - const liquidadoSeries: ApexAxisChartSeries = Object.keys(liquidData).map(key => ({ - name: `Unidade ${key}`, - data: liquidData[parseInt(key)] - })); - - const pagoSeries: ApexAxisChartSeries = Object.keys(pagoData).map(key => ({ - name: `Unidade ${key}`, - data: pagoData[parseInt(key)] - })); - - setEmpenhadoSeries(empenhadoSeries); - setLiquidadoSeries(liquidadoSeries); - setPagoSeries(pagoSeries); - setErrorMessage(null); - - - } catch (error) { - console.error('Erro ao buscar dados de licitações:', error); - setErrorMessage('Erro ao buscar dados de licitações.'); - } - }; - - useEffect(() => { - loadCities(); - loadUnits(); - }, []); - - useEffect(() => { - if (selectedCityId && startDate && endDate) { - fetchAndProcessData(); - } - }, [selectedCityId, selectedUnit, startDate, endDate]); - - return ( - <> -
-

- Pesquise por cidade, período e tema +
+
+

+ Pesquise por período

-
    -
  • - - -
    -
  • - - {/*
  • - - -
    -
  • */} - -
  • - +
    +
    + - +
    + até +
    + -
    -
  • -
+
+
+ {errorMessage &&

{errorMessage}

}

+
+ {/* Total Empenhado */} +
+

+ Total Empenhado + {/* Tooltip */} +
+ +
+
+ Valor Empenhado: Valor do orçamento reservado para fazer face a compromisso formalmente assumido com fornecedor ou credor. +
+
+
+

+

+ R$ {totalSales.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} +

+
-
-
-
-

Valor Empenhado

- -
-
-

Valor Liquidado

- -
-
-

Valor Pago

- -
+ {/* Total Liquidado */} +
+

+ Total Liquidado + {/* Tooltip */} +
+ +
+
+ Valor Liquidado: Valor que o fornecedor ou credor tem direito a receber referente a produto ou serviço devidamente entregue. +
+
+
+

+

+ R$ {totalRevenue.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} +

+
+ + {/* Total Pago */} +
+

+ Total Pago + {/* Tooltip */} +
+ +
+
+ Valor Pago: Valor referente aos pagamentos efetuados através de movimentações bancárias, escriturais e apropriação contábil da despesa. +
+
+
+

+

+ R$ {totalUsers.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} +

+
+
+ +
+
+ +
+
+
- +
); }; -export default Filtro; \ No newline at end of file +export default Dashboard; diff --git a/front/src/components/Footer.tsx b/front/src/components/Footer.tsx index 55ff157..5b2ac72 100644 --- a/front/src/components/Footer.tsx +++ b/front/src/components/Footer.tsx @@ -27,10 +27,12 @@ const Footer = () => {
- unblogo + + unblogo + import('react-apexcharts'), { ssr: false }); +const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +interface Dados { + year: number; + committed_value: number; + liquidated_value: number; + paid_value: number; +} const Grafico: React.FC = () => { - const [isClient, setIsClient] = useState(false); - const isHighContrastMode = document.documentElement.classList.contains('high-contrast'); - const [chartOptions, setChartOptions] = useState({ - chart: { - type: 'bar', - height: 500, - background: '#ffffff', - foreColor: '#000000', + const [series, setSeries] = useState([]); + const [errorMessage, setErrorMessage] = useState(null); + const [chartOptions, setChartOptions] = useState({ + chart: { + type: 'line', + height: 600, + zoom: { + enabled: true, + type: 'x', + zoomedArea: { + fill: { + color: '#90CAF9', + opacity: 0.4 + } + } + }, + toolbar: { + tools: { + zoomin: true, + zoomout: true, + pan: true, + reset: true }, - plotOptions: { - bar: { - horizontal: false, - distributed: true, - barHeight: '100%', - colors: { - ranges: [{ - from: 0, - to: 500000, - color: '#ED1C24' - }], - } - } + autoSelected: 'pan' + }, + background: '#ffffff', + }, + colors: ['#ED1C24', '#F19C28', '#2FB551'], + dataLabels: { + enabled: false + }, + stroke: { + curve: 'smooth' + }, + fill: { + type: 'gradient', + gradient: { + opacityFrom: 0.6, + opacityTo: 0.8, + } + }, + legend: { + position: 'top', + horizontalAlign: 'center', + fontSize: '14px', + }, + xaxis: { + type: 'datetime', + labels: { + format: 'yyyy', + style: { + fontSize: '12px', }, - xaxis: { - categories: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], - type: 'category', - labels: { - style: { - colors: '#000000' - } - } + }, + tickAmount: 10, + min: new Date(2014, 0, 1).getTime(), + max: new Date(2023, 0, 1).getTime(), + }, + yaxis: { + labels: { + style: { + fontSize: '14px', }, - yaxis: { - title: { - text: 'Valor', - style: { - color: '#000000' - } + formatter: (value: number) => `R$ ${Number(value).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`, + }, + tickAmount: 4, + min: 5000000, + }, + tooltip: { + x: { + format: 'DESPESA DE yyyy' + }, + }, + responsive: [ + { + breakpoint: 1024, + options: { + xaxis: { + tickAmount: 5, + min: new Date(2016, 0, 1).getTime(), + max: new Date(2023, 0, 1).getTime(), + labels: { + style: { + fontSize: '10px', + }, }, + }, + yaxis: { labels: { - style: { - colors: '#000000' - } - } - }, - legend: { - show: false, - }, - responsive: [ - { - breakpoint: 1025, - options: { - chart: { - height: 424, - width: 800, - } - } + style: { + fontSize: '12px', + }, + }, + }, + legend: { + fontSize: '12px', + }, + } + }, + { + breakpoint: 768, + options: { + chart: { + height: 400, + }, + xaxis: { + tickAmount: 3, + min: new Date(2018, 0, 1).getTime(), + max: new Date(2023, 0, 1).getTime(), + labels: { + style: { + fontSize: '10px', + }, + }, + }, + yaxis: { + labels: { + style: { + fontSize: '10px', + }, + formatter: (value: number) => `${(value / 1000000).toFixed(1)}M`, // Formato em milhões + }, + }, + legend: { + position: 'bottom', + horizontalAlign: 'center', + fontSize: '10px', + }, + } + }, + { + breakpoint: 480, + options: { + chart: { + height: 300, + }, + xaxis: { + tickAmount: 2, + min: new Date(2020, 0, 1).getTime(), + max: new Date(2023, 0, 1).getTime(), + labels: { + style: { + fontSize: '8px', + }, }, - { - breakpoint: 640, - options: { - chart: { - height: 200, - width: 310, - } - } + }, + yaxis: { + labels: { + style: { + fontSize: '8px', + }, + formatter: (value: number) => `${(value / 1000000).toFixed(1)}M`, // Formato em milhões }, - { - breakpoint: 769, - options: { - chart: { - height: 424, - width: 700 - } - } + }, + legend: { + fontSize: '8px', + }, + } + } + ] + }); + + const isMobileScreen = () => window.innerWidth <= 768; + + useEffect(() => { + const updateChartOptionsForTheme = () => { + const isDarkMode = document.documentElement.classList.contains('dark'); + const isHighContrastMode = document.documentElement.classList.contains('high-contrast'); + + setChartOptions((prevOptions) => ({ + ...prevOptions, + chart: { + ...prevOptions.chart, + background: isHighContrastMode ? '#000000' : isDarkMode ? '#1f1f1f' : '#ffffff', + foreColor: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000', + }, + xaxis: { + ...prevOptions.xaxis, + labels: { + style: { + colors: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000', } - ], - theme: { - mode: 'light', // Modo padrão inicial - palette: 'palette1', + } }, + yaxis: { + ...prevOptions.yaxis, + labels: { + style: { + colors: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000', + }, + formatter: (value: number) => isMobileScreen() + ? `${(value / 1000000).toFixed(1)}M` + : `R$ ${value.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`, + } + }, + colors: isHighContrastMode ? ['#ED1C24', '#F19C28', '#2FB551'] : ['#ED1C24', '#F19C28', '#2FB551'], + })); + }; + + updateChartOptionsForTheme(); + + const observer = new MutationObserver(() => { + updateChartOptionsForTheme(); }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'] + }); + + window.addEventListener('resize', () => updateChartOptionsForTheme()); + + return () => { + observer.disconnect(); + window.removeEventListener('resize', () => updateChartOptionsForTheme()); + }; + }, []); - const series = [{ - name: 'Valor gasto', - data: [20000, 300000, 65000, 100000, 200000, 200000, 20000, 30000, 20000, 75000, 300000, 200000] - }]; - - useEffect(() => { - setIsClient(true); - - const root = document.documentElement; - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.attributeName === "class") { - const isDarkMode = root.classList.contains('dark'); - const isHighContrastMode = root.classList.contains('high-contrast'); // Verifica o modo de alto contraste - setChartOptions((prevOptions) => ({ - ...prevOptions, - chart: { - ...prevOptions.chart, - background: isHighContrastMode ? '#000000' : isDarkMode ? '#1f1f1f' : '#ffffff', - foreColor: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000', - }, - xaxis: { - ...prevOptions.xaxis, - labels: { - style: { - colors: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000' - } - } - }, - yaxis: { - ...prevOptions.yaxis, - labels: { - style: { - colors: isHighContrastMode ? '#FFFFFF' : isDarkMode ? '#e5e7eb' : '#000000' - } - }, - title: { - style: { - color: isDarkMode ? '#e5e7eb' : '#000000' - } - } - }, - theme: { - mode: isDarkMode ? 'dark' : 'light', - } - })); - } - }); - }); - - observer.observe(root, { attributes: true }); + const fetchData = async () => { + try { + const data = await fetchYearlyTendersData(); + console.log('API Response:', data); + + if (Array.isArray(data) && data.length > 0) { + const series = [ + { + name: 'Valor Empenhado', + data: data.map((item) => ({ + x: new Date(item.year, 0, 1).toISOString(), + y: item.committed_value || 0 + })), + }, + { + name: 'Valor Liquidado', + data: data.map((item) => ({ + x: new Date(item.year, 0, 1).toISOString(), + y: item.liquidated_value || 0 + })), + }, + { + name: 'Valor Pago', + data: data.map((item) => ({ + x: new Date(item.year, 0, 1).toISOString(), + y: item.paid_value || 0 + })), + }, + ]; + + console.log('Series Data Transformada:', series); + + setSeries(series); + setErrorMessage(null); + } else { + console.error('Dados inválidos retornados pela API.'); + setErrorMessage('Dados inválidos retornados pela API.'); + } + } catch (error) { + console.error('Erro ao buscar dados para o gráfico:', error); + setErrorMessage('Erro ao buscar dados para o gráfico.'); + } + }; + - return () => { - observer.disconnect(); - }; - }, []); + useEffect(() => { + fetchData(); + }, []); - return ( - <> - {isClient && ( - - )} - - ); -} + return ( +
+

+ Despesas em Cultura em Minas Gerais ao Longo dos Anos (2002-2023) +

+ {errorMessage &&

{errorMessage}

} +
+ +
+
    +
  • +
    +
    + Valor Empenhado: + Valor do orçamento reservado para fazer face a compromisso formalmente assumido com fornecedor ou credor. +
    +
  • +
  • +
    +
    + Valor Liquidado: + Valor que o fornecedor ou credor tem direito a receber referente a produto ou serviço devidamente entregue. +
    +
  • +
  • +
    +
    + Valor Pago: + Valor referente aos pagamentos efetuados através de movimentações bancárias, escriturais e apropriação contábil da despesa. +
    +
  • +
+
+ ); +}; export default Grafico; diff --git a/front/src/components/Header.tsx b/front/src/components/Header.tsx index ede779a..4d9ccd3 100644 --- a/front/src/components/Header.tsx +++ b/front/src/components/Header.tsx @@ -14,11 +14,28 @@ export function Header() { const [accessibilityMenuOpen, setAccessibilityMenuOpen] = useState(false); const [keepMenuOpen, setKeepMenuOpen] = useState(false); + // Carrega as configurações do localStorage + useEffect(() => { + const savedDarkMode = localStorage.getItem('darkMode') === 'true'; + const savedHighContrast = localStorage.getItem('highContrast') === 'true'; + const savedFontSize = localStorage.getItem('fontSize') || 'text-base'; + + setDarkMode(savedDarkMode); + setHighContrast(savedHighContrast); + setFontSize(savedFontSize); + }, []); + + // Atualiza as configurações e aplica as classes useEffect(() => { document.documentElement.classList.toggle('dark', darkMode); document.documentElement.classList.toggle('high-contrast', highContrast); document.documentElement.classList.toggle('font-lg', fontSize === 'text-lg'); document.documentElement.classList.toggle('font-original', fontSize === 'text-base'); + + // Salva as configurações no localStorage + localStorage.setItem('darkMode', darkMode.toString()); + localStorage.setItem('highContrast', highContrast.toString()); + localStorage.setItem('fontSize', fontSize); }, [darkMode, highContrast, fontSize]); const toggleNavbar = () => setIsOpen(!isOpen); @@ -64,7 +81,7 @@ export function Header() {
diff --git a/front/src/components/Informacoes.tsx b/front/src/components/Informacoes.tsx index b56d5a5..7780cc9 100644 --- a/front/src/components/Informacoes.tsx +++ b/front/src/components/Informacoes.tsx @@ -20,8 +20,16 @@ const Informacoes = () => {

Este é um projeto desenvolvido como parte da disciplina de Métodos de Desenvolvimento de Software (MDS) da Universidade de Brasília (UnB). O objetivo principal deste projeto é criar uma plataforma online para análise e - armazenamento de dados de licitações relacionadas aos gastos culturais apoiados pelo Governo Federal, utilizando a - plataforma e a API do Querido Diário. + armazenamento de dados de licitações relacionadas aos gastos culturais apoiados pelo Governo Federal, utilizando + informações da Secretaria de Cultura do Estado de Minas Gerais. +

+

+

+

A plataforma permite aos usuários filtrar dados por um intervalo de tempo para obter informações de interesse, + além de oferecer um dashboard informativo que facilita a consulta e visualização dos dados. Adicionalmente, + a plataforma busca abranger a comunidade com funcionalidades como modo noturno, alto contraste e aumento de + fonte para melhorar a acessibilidade, além de permitir o acesso via dispositivos móveis, garantindo + disponibilidade em várias plataformas.

diff --git a/front/src/components/Integrantes.tsx b/front/src/components/Integrantes.tsx index 8f6036f..c495d51 100644 --- a/front/src/components/Integrantes.tsx +++ b/front/src/components/Integrantes.tsx @@ -9,7 +9,7 @@ import Image from "next/image"; const Integrantes = () => { return (
-
+
@@ -90,7 +90,7 @@ const Integrantes = () => {
- + diff --git a/front/src/components/Mensagem.tsx b/front/src/components/Mensagem.tsx new file mode 100644 index 0000000..f8a6231 --- /dev/null +++ b/front/src/components/Mensagem.tsx @@ -0,0 +1,37 @@ +import { useState, useEffect } from "react"; + +const Mensagem = () => { + const [currentText, setCurrentText] = useState(0); + + const texts = [ + "Simplificando o acesso aos investimentos culturais", + "Facilitando a visualização de dados culturais", + "Promovendo a transparência nos gastos culturais", + "Aprimorando o acesso à cultura em Minas Gerais" + ]; + + useEffect(() => { + const interval = setInterval(() => { + setCurrentText((prevText) => (prevText + 1) % texts.length); + }, 2000); + + return () => clearInterval(interval); + }, [texts.length]); + + return ( +
+
+

+ Dados oficiais das despesas da Secretaria de Estado de Cultura de Minas Gerais +

+
+
+ +
+

{texts[currentText]}

+
+
+ ); +} + +export default Mensagem; diff --git a/front/src/components/Pilares.tsx b/front/src/components/Pilares.tsx index 9d1672a..5739580 100644 --- a/front/src/components/Pilares.tsx +++ b/front/src/components/Pilares.tsx @@ -17,17 +17,17 @@ const Pilares = () => { A Secretaria de Cultura e Turismo do Estado de Minas Gerais:

-
-

Deve preservar

-

o patrimônio cultural do estado de Minas gerais

+
+

Deve preservar

+

patrimônio cultural do estado de Minas gerais

-
-

Deve promover

-

a acessibilidade e inclusão social à cultura

+
+

Deve promover

+

acessibilidade e inclusão social à cultura

-
-

Deve fomentar

-

a produção artística da população

+
+

Deve fomentar

+

produção artística da população

diff --git a/front/src/components/Slider.tsx b/front/src/components/Slider.tsx index b063711..1cffac9 100644 --- a/front/src/components/Slider.tsx +++ b/front/src/components/Slider.tsx @@ -32,33 +32,40 @@ const Slider: React.FC = () => { }, []); return ( -
- - {data.map((item, index) => ( - - - Slider + ); }; diff --git a/front/src/services/api.ts b/front/src/services/api.ts index 596babf..3daacfa 100644 --- a/front/src/services/api.ts +++ b/front/src/services/api.ts @@ -5,8 +5,6 @@ interface SearchParams { startMonth: string; endYear: string; endMonth: string; - cityId: number; - unitId: string; } export const fetchCities = async () => { @@ -14,7 +12,7 @@ export const fetchCities = async () => { const response = await axios.get('http://localhost:5000/cities'); return response.data; } catch (error) { - console.error('Erro ao buscar cidades:', error); + console.error('Error fetching cities:', error); throw error; } }; @@ -24,21 +22,36 @@ export const fetchUnits = async () => { const response = await axios.get('http://localhost:5000/units'); return response.data; } catch (error) { - console.error('Erro ao buscar cidades:', error); + console.error('Error fetching units:', error); throw error; } }; -export const searchLicitacoes = async (params: SearchParams) => { - const { startYear, startMonth, endYear, endMonth, cityId, unitId} = params; +export const fetchYearlyTendersData = async () => { + try { + const requests = []; + for (let year = 2002; year <= 2023; year++) { + requests.push(axios.get(`https://minas-cultura-api.onrender.com/tenders/year?year=${year}`)); + } + const responses = await Promise.all(requests); + return responses.flatMap((response) => response.data); + } catch (error) { + console.error('Erro ao buscar dados anuais:', error); + return []; + } +}; + +/* pra quando a API remota estiver com a lógica certa*/ +export const fettchYearAndMonthTender = async (params: SearchParams) => { + const { startYear, startMonth, endYear, endMonth } = params; - const url = `http://localhost:5000/tenders?start=${startYear}${startMonth}&end=${endYear}${endMonth}&city=${cityId}`; + const url = `https://minas-cultura-api.onrender.com/tenders?start=${startYear}${startMonth}&end=${endYear}${endMonth}`; try { const response = await axios.get(url); return response.data; } catch (error) { - console.error('Erro ao buscar licitações:', error); + console.error('Error searching tenders:', error); throw error; } }; diff --git a/front/src/testes/Filtro.test.jsx b/front/src/testes/Filtro.test.jsx index b00965c..1810409 100644 --- a/front/src/testes/Filtro.test.jsx +++ b/front/src/testes/Filtro.test.jsx @@ -2,8 +2,7 @@ import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import Filtro from '../components/Filtro'; -import { fetchCities, fetchUnits, searchLicitacoes } from '../services/api'; -import ApexCharts from 'react-apexcharts'; +import { fettchYearAndMonthTender } from '../services/api'; // Mock das funções de API jest.mock('../services/api'); @@ -15,66 +14,43 @@ jest.mock('react-apexcharts', () => { }; }); -const mockCities = [ - { id: 1, name: 'Cidade 1' }, - { id: 2, name: 'Cidade 2' }, -]; - -const mockUnits = [ - { id: 1, name: 'Unidade 1' }, - { id: 2, name: 'Unidade 2' }, -]; - -const mockLicitacoes = [ +const mockData = [ { - administrative_unit_id: 1, - city_id: 1, committed_value: 1000, liquidated_value: 800, paid_value: 600, year: 2023, month: 1, }, - // Adicione mais dados conforme necessário ]; beforeEach(() => { - fetchCities.mockResolvedValue(mockCities); - fetchUnits.mockResolvedValue(mockUnits); - searchLicitacoes.mockResolvedValue(mockLicitacoes); + fettchYearAndMonthTender.mockResolvedValue(mockData); }); -test('renders Filtro component', async () => { +test('renders Dashboard component and interacts with date pickers', async () => { render(); // Verifica se o título está presente - expect(screen.getByText('Pesquise por cidade, período e tema')).toBeInTheDocument(); - - // Verifica se o seletor de cidades está presente - expect(screen.getByText('Selecione uma cidade')).toBeInTheDocument(); + expect(screen.getByText('Pesquise por período')).toBeInTheDocument(); - // Aguarda a carga das cidades - await waitFor(() => expect(fetchCities).toHaveBeenCalled()); - - // Simula a seleção de uma cidade - fireEvent.change(screen.getByRole('combobox'), { target: { value: '1' } }); - - // Verifica se a cidade foi selecionada - expect(screen.getByRole('combobox')).toHaveValue('1'); + // Verifica se os campos de data inicial e final estão presentes + expect(screen.getByPlaceholderText('Data Inicial')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Data Final')).toBeInTheDocument(); // Simula a seleção de datas - fireEvent.change(screen.getByPlaceholderText('Data Inicial'), { target: { value: '01 / 2023' } }); - fireEvent.change(screen.getByPlaceholderText('Data final'), { target: { value: '12 / 2023' } }); + fireEvent.change(screen.getByPlaceholderText('Data Inicial'), { target: { value: '01/2023' } }); + fireEvent.change(screen.getByPlaceholderText('Data Final'), { target: { value: '12/2023' } }); - // Aguarda a busca de licitações - await waitFor(() => expect(searchLicitacoes).toHaveBeenCalled()); + // Aguarda a busca de dados + await waitFor(() => expect(fettchYearAndMonthTender).toHaveBeenCalled()); - // Verifica se os gráficos são renderizados - expect(screen.getByText('Valor Empenhado')).toBeInTheDocument(); - expect(screen.getByText('Valor Liquidado')).toBeInTheDocument(); - expect(screen.getByText('Valor Pago')).toBeInTheDocument(); + // Verifica se os valores totais são exibidos + expect(screen.getByText('Total Empenhado')).toBeInTheDocument(); + expect(screen.getByText('Total Liquidado')).toBeInTheDocument(); + expect(screen.getByText('Total Pago')).toBeInTheDocument(); - // Verifica se há três gráficos renderizados + // Verifica se os gráficos são renderizados const charts = screen.getAllByTestId('mock-apexcharts'); - expect(charts).toHaveLength(3); + expect(charts).toHaveLength(2); // O Dashboard tem dois gráficos }); \ No newline at end of file diff --git a/front/src/testes/Informacoes.test.jsx b/front/src/testes/Informacoes.test.jsx index 58eb6f2..eaf708e 100644 --- a/front/src/testes/Informacoes.test.jsx +++ b/front/src/testes/Informacoes.test.jsx @@ -16,7 +16,7 @@ describe('Informacoes', () => { expect(title).toBeInTheDocument(); // Verifica se o parágrafo é renderizado - const paragraphText = "Este é um projeto desenvolvido como parte da disciplina de Métodos de Desenvolvimento de Software (MDS) da Universidade de Brasília (UnB). O objetivo principal deste projeto é criar uma plataforma online para análise e armazenamento de dados de licitações relacionadas aos gastos culturais apoiados pelo Governo Federal, utilizando a plataforma e a API do Querido Diário."; + const paragraphText = "Este é um projeto desenvolvido como parte da disciplina de Métodos de Desenvolvimento de Software (MDS) da Universidade de Brasília (UnB). O objetivo principal deste projeto é criar uma plataforma online para análise e armazenamento de dados de licitações relacionadas aos gastos culturais apoiados pelo Governo Federal, utilizando informações da Secretaria de Cultura do Estado de Minas Gerais."; const paragraph = screen.getByText((content, element) => { const hasText = element => element.textContent === paragraphText; const elementHasText = hasText(element); diff --git a/front/src/testes/Pilares.test.jsx b/front/src/testes/Pilares.test.jsx index d8f92a8..c3e8b14 100644 --- a/front/src/testes/Pilares.test.jsx +++ b/front/src/testes/Pilares.test.jsx @@ -13,19 +13,19 @@ describe('Pilares', () => { // Check if the first section is rendered const firstHeading = screen.getByText(/Deve preservar/i); - const firstSubheading = screen.getByText(/o patrimônio cultural do estado de Minas Gerais/i); + const firstSubheading = screen.getByText(/patrimônio cultural do estado de Minas Gerais/i); expect(firstHeading).toBeInTheDocument(); expect(firstSubheading).toBeInTheDocument(); // Check if the second section is rendered const secondHeading = screen.getByText(/Deve promover/i); - const secondSubheading = screen.getByText(/a acessibilidade e inclusão social à cultura/i); + const secondSubheading = screen.getByText(/acessibilidade e inclusão social à cultura/i); expect(secondHeading).toBeInTheDocument(); expect(secondSubheading).toBeInTheDocument(); // Check if the third section is rendered const thirdHeading = screen.getByText(/Deve fomentar/i); - const thirdSubheading = screen.getByText(/a produção artística da população/i); + const thirdSubheading = screen.getByText(/produção artística da população/i); expect(thirdHeading).toBeInTheDocument(); expect(thirdSubheading).toBeInTheDocument(); });