diff --git a/src/components/graph/cativoXLivreChart/index.tsx b/src/components/graph/cativoXLivreChart/index.tsx index 21dbc62..d2d8130 100644 --- a/src/components/graph/cativoXLivreChart/index.tsx +++ b/src/components/graph/cativoXLivreChart/index.tsx @@ -79,6 +79,8 @@ export function CativoXLivreChart({ title, subtitle, chartData, label, dataset1, const options: any = config(miniature) + const hasEstimated = chartData?.some((value) => value.dad_estimado) + const data: any = { labels, datasets: chartData?.map(value => value.dad_estimado)?.includes(true) ? [ @@ -184,7 +186,7 @@ export function CativoXLivreChart({ title, subtitle, chartData, label, dataset1, return ( - + {/* */}
diff --git a/src/components/graph/costIndicatorChart/index.tsx b/src/components/graph/costIndicatorChart/index.tsx index 768410a..3637c6e 100644 --- a/src/components/graph/costIndicatorChart/index.tsx +++ b/src/components/graph/costIndicatorChart/index.tsx @@ -80,7 +80,7 @@ export default function CostIndicatorChart({ title, data1, data2, label, subtitl return ( - + {/* */} prev.economia_acumulada < current.economia_acumulada ? prev.economia_acumulada : current.economia_acumulada, 0)) + 350, min: 0, grid: { @@ -79,26 +78,28 @@ export function GrossAnualChart({ title, subtitle, dataProps = [], label, datase }, plugins: { datalabels: { + display: true, + color: '#255488', + clip: false, formatter: (value, ctx) => { const percentage = (dataProps[ctx.dataIndex]?.econ_percentual * 100).toFixed(0) + "%"; const result = `${spacement(parseInt(value).toLocaleString('pt-br'))}${percentage}\n${parseInt(value).toLocaleString('pt-br')}${spacement(parseInt(value).toLocaleString('pt-br'))}` return value == null ? null : result }, - display: true, - anchor: "end", - align: "end", + anchor: 'end', + align: 'end', + offset: 5, font: { weight: 'bold', size: !miniature ? window.innerWidth / 80 : window.innerWidth / 125, }, - color: '#255488', }, legend: { position: 'bottom' as const, }, title: { - display: false, + display: true, text: '', }, }, @@ -126,7 +127,7 @@ export function GrossAnualChart({ title, subtitle, dataProps = [], label, datase { type: 'bar', label: dataset, - stacked: true, + // stacked: true, data: consolidatedData, datalabels: { // backgroundColor: '#255488', @@ -134,19 +135,19 @@ export function GrossAnualChart({ title, subtitle, dataProps = [], label, datase // opacity: .8, display: (ctx) => ctx.dataIndex === 0, // Exibe apenas o primeiro }, - borderRadius: 10, + skipNull: true, + borderRadius: 8, backgroundColor: '#255488', }, { type: 'bar', - stacked: true, label: 'Estimado', - spanGaps: true, datalabels: { // keep the previous behaviour of offsetting the second estimated bar if needed }, data: estimatedData, - borderRadius: 10, + skipNull: true, + borderRadius: 8, backgroundColor: draw('diagonal-right-left', '#C2d5fb'), }, ], @@ -154,8 +155,7 @@ export function GrossAnualChart({ title, subtitle, dataProps = [], label, datase return ( - - + ) } diff --git a/src/components/graph/grossAnualChart/GrossAnualChartView.ts b/src/components/graph/grossAnualChart/GrossAnualChartView.ts index f50b83d..77f6953 100644 --- a/src/components/graph/grossAnualChart/GrossAnualChartView.ts +++ b/src/components/graph/grossAnualChart/GrossAnualChartView.ts @@ -3,21 +3,23 @@ import styled from "styled-components" export const GrossAnualChartView = styled.div` width: 90%; + transform: translateY(-25px); + @media (max-width: 900px) { min-width: 20rem } ` -export const ChartTitleView = styled.div` - display: flex; - justify-content: center; - align-items: center; - margin-top: 30px; +// export const ChartTitleView = styled.div` +// display: flex; +// justify-content: center; +// align-items: center; +// margin-top: 30px; - flex-direction: column; +// flex-direction: column; - * { - margin: 0; - } -` +// * { +// margin: 0; +// } +// ` diff --git a/src/components/graph/grossMensalChart/GrossMensalChart.tsx b/src/components/graph/grossMensalChart/GrossMensalChart.tsx index 7e5ce71..4c770df 100644 --- a/src/components/graph/grossMensalChart/GrossMensalChart.tsx +++ b/src/components/graph/grossMensalChart/GrossMensalChart.tsx @@ -88,7 +88,7 @@ export default function GrossMensalChart({ datalabels: { display: true, color: '#255488', - clip: true, + clip: false, formatter: (value, ctx) => { let sum = 0 const dataArr = ctx.chart.data.datasets[0].data @@ -152,7 +152,7 @@ export default function GrossMensalChart({ return ( - + {/* */} ) diff --git a/src/pages/accumulatedSavings/index.tsx b/src/pages/accumulatedSavings/index.tsx index 567e261..3a35308 100644 --- a/src/pages/accumulatedSavings/index.tsx +++ b/src/pages/accumulatedSavings/index.tsx @@ -1,7 +1,7 @@ import { GetServerSideProps } from 'next' import Head from 'next/head' import { parseCookies } from 'nookies' - +import { useEffect, useState } from 'react' // import Chart2 from '../../components/graph/Chart2' import GrossMensalChart from '../../components/graph/grossMensalChart/GrossMensalChart' @@ -10,6 +10,7 @@ import PageTitle from '../../components/pageTitle/PageTitle' import getAPIClient from '../../services/ssrApi' import { AccumulatedSavingsView } from '../../styles/layouts/economy/accumulatedSavings/AccumulatedSavingsView' +import { getLastConsolidatedYear, populateGraphDataForYear } from '../../utils/dataProcessing' export default function AccumulatedSavings({graphData, years, userName}: any) { const months = [ @@ -27,6 +28,19 @@ export default function AccumulatedSavings({graphData, years, userName}: any) { 'Dez' ] + const [processedData, setProcessedData] = useState(graphData) + const [lastConsolidatedYear, setLastConsolidatedYear] = useState(null) + + useEffect(() => { + // Calculate the last consolidated year + const lastYear = getLastConsolidatedYear(graphData, true) + setLastConsolidatedYear(lastYear) + + // Populate graph data with consolidated and estimated data for that year + const populatedData = populateGraphDataForYear(graphData, lastYear) + setProcessedData(populatedData) + }, [graphData]) + return ( @@ -37,8 +51,8 @@ export default function AccumulatedSavings({graphData, years, userName}: any) {
{/* (null) + + useEffect(() => { + // Calculate the last consolidated year + const lastYear = getLastConsolidatedYear(graphData, true) + setLastConsolidatedYear(lastYear) + + // Populate graph data with consolidated and estimated data for that year + const populatedData = populateGraphDataForYear(graphData, lastYear) + setProcessedGraphData(populatedData) + }, [graphData]) useEffect(() => { api.post('/economy/estimates', unity!==''?{ @@ -42,7 +55,14 @@ export default function CostIndicator({graphData, userName, clients}: any) { {"type" : "=", "field":"dados_cadastrais.cod_smart_unidade", "value": unity} ] }:{}).then(res => { - setGraphDataState(res.data.data) + // Apply data processing to filtered result + if (res.data.data && res.data.data.length > 0) { + const lastYear = getLastConsolidatedYear(res.data.data, true) + const populatedData = populateGraphDataForYear(res.data.data, lastYear) + setGraphDataState(populatedData) + } else { + setGraphDataState(res.data.data) + } }) }, [unity]) @@ -75,12 +95,16 @@ export default function CostIndicator({graphData, userName, clients}: any) {
value.mes.slice(4, 8).includes('2021')) + data1={unity!==''? graphDataState.filter((value, index) => value.mes.slice(0, 4).includes(lastConsolidatedYear?.toString() || '')) + .map(value => value?.custo_unit && !!parseInt(value?.custo_unit) ? value.custo_unit : null) : - graphData.filter((value, index) => value.mes.slice(4, 8).includes('2021'))} - data2={unity!==''? graphDataState.filter((value, index) => value.mes.slice(4, 8).includes('2022')) + processedGraphData.filter((value, index) => value.mes.slice(0, 4).includes(lastConsolidatedYear?.toString() || '')) + .map(value => value?.custo_unit && !!parseInt(value?.custo_unit) ? value.custo_unit : null)} + data2={unity!==''? graphDataState.filter((value, index) => value.mes.slice(0, 4).includes(lastConsolidatedYear?.toString() || '')) + .map(value => value?.custo_unit && !!parseInt(value?.custo_unit) ? value.custo_unit : null) : - graphData.filter((value, index) => value.mes.slice(4, 8).includes('2022'))} + processedGraphData.filter((value, index) => value.mes.slice(0, 4).includes(lastConsolidatedYear?.toString() || '')) + .map(value => value?.custo_unit && !!parseInt(value?.custo_unit) ? value.custo_unit : null)} label={months} />
diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index c070023..9aed9f2 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -18,6 +18,7 @@ import CostIndicatorChart from '../../components/graph/costIndicatorChart' import { GrossAnualChart } from '../../components/graph/grossAnualChart/GrossAnualChart' import GrossMensalChart from '../../components/graph/grossMensalChart/GrossMensalChart' import getAPIClient from '../../services/ssrApi' +import { getLastConsolidatedYear, populateGraphDataForYear } from '../../utils/dataProcessing' import Box from '@mui/material/Box' import Modal from '@mui/material/Modal' @@ -66,30 +67,41 @@ export default function Dashboard({ grossAnualGraph, grossAnualYears, grossMensa const [lastDataBrutaMensalS, setLastDataBrutaMensal] = useState('') const [lastDataBrutaAnualS, setLastDataBrutaAnual] = useState('') + const [processedMensalData, setProcessedMensalData] = useState(grossMensalGraph) + const [lastConsolidatedYear, setLastConsolidatedYear] = useState(null) const [open, setOpen] = useState(true); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); useEffect(() => { + // Calculate the last consolidated year + const lastYear = getLastConsolidatedYear(grossMensalGraph, true) + setLastConsolidatedYear(lastYear) + + // Populate graph data with consolidated and estimated data for that year + const populatedData = populateGraphDataForYear(grossMensalGraph, lastYear) + setProcessedMensalData(populatedData) + + // Calculate last data values let lastDataMensal = '0' let lastDataAnual = '0' let index = 0 - while (index < grossMensalGraph.length) { - if (!grossMensalGraph[index].dad_estimado && grossMensalGraph[index].economia_acumulada !== null) - lastDataMensal = grossMensalGraph[index].economia_acumulada + while (index < populatedData.length) { + if (!populatedData[index].dad_estimado && populatedData[index].economia_acumulada !== null) + lastDataMensal = String(populatedData[index].economia_acumulada) index++ } setLastDataBrutaMensal(`${parseFloat(lastDataMensal).toFixed(3)}`) index = 0 while (index < grossAnualGraph.length) { if (!grossAnualGraph[index].dad_estimado) - lastDataAnual = grossAnualGraph[index].economia_acumulada + lastDataAnual = String(grossAnualGraph[index].economia_acumulada) index++ } setLastDataBrutaAnual(`${parseFloat(lastDataAnual).toFixed(3)}`) - }, []) + }, [grossMensalGraph, grossAnualGraph]) return ( @@ -126,8 +138,8 @@ export default function Dashboard({ grossAnualGraph, grossAnualYears, grossMensa diff --git a/src/pages/economy/index.tsx b/src/pages/economy/index.tsx index 3880341..74db3f8 100644 --- a/src/pages/economy/index.tsx +++ b/src/pages/economy/index.tsx @@ -13,6 +13,7 @@ import { api } from '../../services/api'; import getAPIClient from '../../services/ssrApi'; import { TableHeader } from '../../styles/layouts/pld/PldView'; import RenderIf from '../../utils/renderIf'; +import { getLastConsolidatedYear, populateGraphDataForYear } from '../../utils/dataProcessing'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; @@ -32,6 +33,8 @@ export default function economy({ userName, anual, years, brutaMensal, catLiv, c const [catLivDataState, setCatLivDataState] = useState(null); const [indicatorDataState, setIndicatorDataState] = useState(null); + const [processedBrutaMensal, setProcessedBrutaMensal] = useState(brutaMensal) + const [lastConsolidatedYear, setLastConsolidatedYear] = useState(null) const currentYear = new Date().getUTCFullYear() const previousYear = new Date().getUTCFullYear() - 1 @@ -52,13 +55,24 @@ export default function economy({ userName, anual, years, brutaMensal, catLiv, c ] const [lastDataBruta, setLastDataBruta] = useState('') + + useEffect(() => { + // Calculate the last consolidated year + const lastYear = getLastConsolidatedYear(brutaMensal, true) + setLastConsolidatedYear(lastYear) + + // Populate graph data with consolidated and estimated data for that year + const populatedData = populateGraphDataForYear(brutaMensal, lastYear) + setProcessedBrutaMensal(populatedData) + }, [brutaMensal]) + useEffect(() => { let lastData = '0' let index = 0 if (economyMenu) { - while (index < brutaMensal.length) { - if (!brutaMensal[index].dad_estimado) - lastData = brutaMensal[index].economia_acumulada + while (index < processedBrutaMensal.length) { + if (!processedBrutaMensal[index].dad_estimado) + lastData = processedBrutaMensal[index].economia_acumulada index++ } } else { @@ -69,7 +83,7 @@ export default function economy({ userName, anual, years, brutaMensal, catLiv, c } } setLastDataBruta(`${parseFloat(lastData).toFixed(3)}`) - }, [economyMenu]) + }, [economyMenu, processedBrutaMensal]) useEffect(() => { console.log(indicatorDataState) }, [indicatorDataState]) @@ -148,8 +162,8 @@ export default function economy({ userName, anual, years, brutaMensal, catLiv, c
diff --git a/src/pages/estimatedCost/index.tsx b/src/pages/estimatedCost/index.tsx index 4f30642..1d6ce06 100644 --- a/src/pages/estimatedCost/index.tsx +++ b/src/pages/estimatedCost/index.tsx @@ -16,11 +16,24 @@ import getAPIClient from '../../services/ssrApi' import { EstimatedCostView } from '../../styles/layouts/economy/estimatedCost/EstimatedCostView' import { api } from '../../services/api' +import { getLastConsolidatedYear, populateGraphDataForYear } from '../../utils/dataProcessing' export default function EstimatedCost({graphData, userName, clients}: any) { const [unity, setUnity] = useState(null); const [graphDataState, setGraphDataState] = useState(null); + const [processedGraphData, setProcessedGraphData] = useState(graphData) + const [lastConsolidatedYear, setLastConsolidatedYear] = useState(null) + + useEffect(() => { + // Calculate the last consolidated year + const lastYear = getLastConsolidatedYear(graphData, true) + setLastConsolidatedYear(lastYear) + + // Populate graph data with consolidated and estimated data for that year + const populatedData = populateGraphDataForYear(graphData, lastYear) + setProcessedGraphData(populatedData) + }, [graphData]) useEffect(() => { api.post('/economy/estimates', unity!==''?{ @@ -28,7 +41,14 @@ export default function EstimatedCost({graphData, userName, clients}: any) { {"type" : "=", "field":"dados_cadastrais.cod_smart_unidade", "value": unity} ] }:{}).then(res => { - setGraphDataState(res.data.data) + // Apply data processing to filtered result + if (res.data.data && res.data.data.length > 0) { + const lastYear = getLastConsolidatedYear(res.data.data, true) + const populatedData = populateGraphDataForYear(res.data.data, lastYear) + setGraphDataState(populatedData) + } else { + setGraphDataState(res.data.data) + } }) }, [unity]) @@ -60,7 +80,7 @@ export default function EstimatedCost({graphData, userName, clients}: any) {
-
diff --git a/src/utils/dataProcessing.ts b/src/utils/dataProcessing.ts new file mode 100644 index 0000000..b483e56 --- /dev/null +++ b/src/utils/dataProcessing.ts @@ -0,0 +1,287 @@ +/** + * Utility functions to process economy data from API + * Calculates the year with the last consolidated data and populates graphs accordingly + */ + +interface EconomyData { + ano?: string | number; + mes?: string; + economia_acumulada?: number; + economia_mensal?: number; + dad_estimado: boolean; + [key: string]: any; +} + +/** + * Extract year from mes field supporting multiple formats + * Supports: "YYYY-MM-DD", "MMM/YYYY", "MM/YYYY" + * @param mesValue - The mes field value + * @returns Year as number + */ +function extractYearFromMes(mesValue: string): number { + const mesStr = mesValue.toString(); + + // Handle "YYYY-MM-DD" format + if (mesStr.includes('-')) { + const yearStr = mesStr.split('-')[0]; + return parseInt(yearStr); + } + + // Handle "MMM/YYYY" or "MM/YYYY" format + if (mesStr.includes('/')) { + const yearStr = mesStr.split('/')[1]; + return parseInt(yearStr); + } + + // Fallback: try to parse as number if it's just a year + const parsed = parseInt(mesStr); + return !isNaN(parsed) ? parsed : new Date().getFullYear(); +} + +/** + * Find the year with the last consolidated (non-estimated) data + * @param data - Array of economy data + * @param isMonthly - If true, extract year from 'mes' field; if false, use 'ano' field + * @returns The year with the last consolidated data + */ +export function getLastConsolidatedYear(data: EconomyData[], isMonthly: boolean = false): number { + if (!data || data.length === 0) { + return new Date().getFullYear(); + } + + // Filter only consolidated data (dad_estimado === false) + const consolidatedData = data.filter(item => !item.dad_estimado); + + // Extract all years present in the dataset (consolidated + estimated) + const allYears = data + .map(item => { + if (isMonthly && item.mes) { + return extractYearFromMes(item.mes); + } + return parseInt(item.ano?.toString() || new Date().getFullYear().toString()); + }) + .filter(year => !Number.isNaN(year)); + + // Extract years from consolidated records + const consolidatedYears = consolidatedData + .map(item => { + if (isMonthly && item.mes) { + return extractYearFromMes(item.mes); + } + return parseInt(item.ano?.toString() || new Date().getFullYear().toString()); + }) + .filter(year => !Number.isNaN(year)); + + // Prefer the latest consolidated year; if none, fall back to the latest year available + if (consolidatedYears.length > 0) { + return Math.max(...consolidatedYears); + } + + if (allYears.length > 0) { + return Math.max(...allYears); + } + + return new Date().getFullYear(); +} + +/** + * Filter and prepare graph data for a specific year + * Uses consolidated data for that year and estimated data for remaining months + * @param data - Array of economy data (monthly) + * @param targetYear - The year to filter by + * @returns Processed array with consolidated and estimated data + */ +export function populateGraphDataForYear(data: EconomyData[], targetYear: number | string): EconomyData[] { + if (!data || data.length === 0) { + return []; + } + + const year = parseInt(targetYear.toString()); + const currentMonth = new Date().getMonth() + 1; // 1-12 + const currentYear = new Date().getFullYear(); + + // Filter data for the target year + const yearData = data.filter(item => { + if (item.mes) { + const itemYear = extractYearFromMes(item.mes); + return itemYear === year; + } + return false; + }); + + // If no data for the year, try to use the latest year available to avoid empty charts + if (yearData.length === 0) { + const fallbackYear = data + .map(item => (item.mes ? extractYearFromMes(item.mes) : parseInt(item.ano?.toString() || '0'))) + .filter(year => !Number.isNaN(year)) + .sort((a, b) => b - a)[0]; + + if (!fallbackYear) { + return []; + } + + return populateGraphDataForYear(data, fallbackYear); + } + + // Create a map of months for quick lookup + const dataMap = new Map(); + yearData.forEach(item => { + if (item.mes) { + const mesStr = item.mes.toString(); + let month = 0; + + // Extract month from different formats + if (mesStr.includes('-')) { + // YYYY-MM-DD format + month = parseInt(mesStr.split('-')[1]); + } else if (mesStr.includes('/')) { + // MMM/YYYY or MM/YYYY format + const parts = mesStr.split('/'); + const monthPart = parts[0]; + // Try to parse as number first + month = parseInt(monthPart); + // If it's NaN, it's a month name, so we need to convert it + if (isNaN(month)) { + const monthNames = ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez']; + month = monthNames.indexOf(monthPart.toLowerCase()) + 1; + } + } + + if (month > 0) { + dataMap.set(month, item); + } + } + }); + + // Build complete year data (1-12 months) + const completeYearData: EconomyData[] = []; + for (let month = 1; month <= 12; month++) { + if (dataMap.has(month)) { + completeYearData.push(dataMap.get(month)!); + } else { + // Fill missing months with estimated data + // Find the last consolidated data or use the last available data as base + const lastConsolidated = Array.from(dataMap.values()) + .filter(item => !item.dad_estimado) + .sort((a, b) => { + const mesA = a.mes?.toString() || ''; + const mesB = b.mes?.toString() || ''; + + let monthA = 0, monthB = 0; + + if (mesA.includes('-')) { + monthA = parseInt(mesA.split('-')[1]); + } else if (mesA.includes('/')) { + const part = mesA.split('/')[0]; + monthA = parseInt(part) || getMonthNumber(part); + } + + if (mesB.includes('-')) { + monthB = parseInt(mesB.split('-')[1]); + } else if (mesB.includes('/')) { + const part = mesB.split('/')[0]; + monthB = parseInt(part) || getMonthNumber(part); + } + + return monthB - monthA; + })[0]; + + if (lastConsolidated) { + const lastConsolidatedMonth = extractMonthFromMes(lastConsolidated.mes || ''); + if (month > lastConsolidatedMonth) { + // Create estimated data entry for future months + completeYearData.push({ + ...lastConsolidated, + mes: `${month < 10 ? '0' : ''}${month}/${year}`, + dad_estimado: true + }); + } else if (dataMap.size > 0) { + // Use the first available data as template for earlier months + const firstData = Array.from(dataMap.values())[0]; + completeYearData.push({ + ...firstData, + mes: `${month < 10 ? '0' : ''}${month}/${year}`, + dad_estimado: true + }); + } + } else if (dataMap.size > 0) { + // Use the first available data as template + const firstData = Array.from(dataMap.values())[0]; + completeYearData.push({ + ...firstData, + mes: `${month < 10 ? '0' : ''}${month}/${year}`, + dad_estimado: true + }); + } + } + } + + return completeYearData.sort((a, b) => { + const monthA = extractMonthFromMes(a.mes || ''); + const monthB = extractMonthFromMes(b.mes || ''); + return monthA - monthB; + }); +} + +/** + * Extract month number from mes field + * @param mesValue - The mes field value + * @returns Month number (1-12) + */ +function extractMonthFromMes(mesValue: string): number { + const mesStr = mesValue.toString(); + + // Handle "YYYY-MM-DD" format + if (mesStr.includes('-')) { + return parseInt(mesStr.split('-')[1]); + } + + // Handle "MMM/YYYY" or "MM/YYYY" format + if (mesStr.includes('/')) { + const monthPart = mesStr.split('/')[0]; + const parsed = parseInt(monthPart); + if (!isNaN(parsed)) { + return parsed; + } + return getMonthNumber(monthPart); + } + + return 1; // Default to January +} + +/** + * Convert month name to month number + * @param monthName - Month name (Jan, Feb, etc. or jan, fev, etc.) + * @returns Month number (1-12) + */ +function getMonthNumber(monthName: string): number { + const months = ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez']; + return months.indexOf(monthName.toLowerCase()) + 1 || 1; +} + +/** + * Get consolidated and estimated data split for a specific year + * Returns both consolidated and estimated datasets separately + * @param data - Array of economy data + * @param targetYear - The year to filter by + * @returns Object with consolidated and estimated arrays + */ +export function getConsolidatedAndEstimatedData( + data: EconomyData[], + targetYear: number | string +): { consolidated: EconomyData[]; estimated: EconomyData[] } { + const year = parseInt(targetYear.toString()); + + const yearData = data.filter(item => { + if (item.mes) { + const itemYear = extractYearFromMes(item.mes); + return itemYear === year; + } + return false; + }); + + const consolidated = yearData.filter(item => !item.dad_estimado); + const estimated = yearData.filter(item => item.dad_estimado); + + return { consolidated, estimated }; +}