From 32879da575278011d9d41f54485224746997225b Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Thu, 1 May 2025 10:05:23 -0700 Subject: [PATCH 01/12] reina create line graph chart for cost breakdown by expenditure --- .../ExpenditureGraph/ExpenditureLineGraph.css | 28 ++ .../ExpenditureGraph/ExpenditureLineGraph.jsx | 311 ++++++++++++++++++ .../ExpenditureLineGraphComponent.jsx | 226 +++++++++++++ .../WeeklyProjectSummary.jsx | 7 +- src/routes.js | 2 + src/utils/URL.js | 22 +- 6 files changed, 582 insertions(+), 14 deletions(-) create mode 100644 src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css create mode 100644 src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx create mode 100644 src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraphComponent.jsx diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css new file mode 100644 index 0000000000..d7d0eb3b48 --- /dev/null +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css @@ -0,0 +1,28 @@ +.expenditure-chart-container { + padding: 20px; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1) +} + +.project-filter { + margin: 20px; + justify-content:flex-end; + display: flex; + align-items: center; +} + +.filter-group { + margin: 20px; + justify-content:flex-end; + display: flex; + align-items: center; +} + +label { + margin: 20px; +} + +#project-select { + margin-left: 15px; +} diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx new file mode 100644 index 0000000000..e19a82df58 --- /dev/null +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -0,0 +1,311 @@ +import Chart from 'chart.js/auto'; +import { useEffect, useRef, useState } from 'react'; +import { ENDPOINTS } from 'utils/URL'; +import axios from 'axios'; +import './ExpenditureLineGraph.css'; + +export default function ExpenditureLineGraph() { + const chartRef = useRef(null); + const [chartInstance, setChartInstance] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [expenditureData, setExpenditureData] = useState([]); + const [projects, setProjects] = useState([]); + const [selectedProject, setSelectedProject] = useState('all'); + + // date filters + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [dateRange, setDateRange] = useState({ start: null, end: null }); + + // format the date as YYYY-MM-DD + const formatDateForInput = date => { + return date.toISOString().split('T')[0]; + }; + + // use effect fetches data + useEffect(() => { + const fetchExpenditureData = async () => { + try { + setLoading(true); + const response = await axios.get(ENDPOINTS.BM_EXPENDITURE); + + if (response.data.success) { + const { data } = response.data; + setExpenditureData(data); + // extract unique IDs + const uniqueProjects = [...new Set(data.map(item => item.projectId))]; + setProjects(uniqueProjects); + + // default date range + if (data.length > 0) { + const dates = data.map(item => new Date(item.date)); + const minDate = new Date(Math.min(...dates)); + const maxDate = new Date(Math.max(...dates)); + + setStartDate(formatDateForInput(minDate)); + setEndDate(formatDateForInput(maxDate)); + + // set date range + setDateRange({ + start: minDate, + end: maxDate, + }); + } + } else { + setError('Failed to fetch the data'); + } + } catch (err) { + setError(`Error fetching data: ${err.message}`); + } finally { + setLoading(false); + } + }; + + fetchExpenditureData(); + + // Clean up function + return () => { + if (chartInstance) { + chartInstance.destroy(); + setChartInstance(null); + } + }; + }, []); + + const updateChart = chartData => { + const ctx = chartRef.current.getContext('2d'); + + const chartTitle = + selectedProject === 'all' + ? 'Cost Breakdown by Type of Expenditure (all projects)' + : `Cost Breakdown by Type of Expenditure (Project: ${selectedProject})`; + + if (chartInstance) { + // Update existing chart + chartInstance.data = chartData; + chartInstance.options.plugins.title.text = chartTitle; + chartInstance.update(); + } else { + // Create new chart + const config = { + type: 'line', + data: chartData, + options: { + responsive: true, + plugins: { + title: { + display: true, + text: chartTitle, + }, + }, + scales: { + y: { + title: { + display: true, + text: 'Cost($)', + }, + }, + x: { + title: { + display: true, + text: 'Month', + }, + }, + }, + }, + }; + + const newChartInstance = new Chart(ctx, config); + setChartInstance(newChartInstance); + } + }; + + const processDataForChart = expenditureDataArr => { + // group data by month and cat + const groupedByMonth = {}; + const categories = new Set(); + + // Extract month from date and group data + expenditureDataArr.forEach(item => { + const date = new Date(item.date); + const month = date.toLocaleString('default', { month: 'short' }); + const year = date.getFullYear(); + const monthYear = `${month} ${year}`; + + if (!groupedByMonth[monthYear]) { + groupedByMonth[monthYear] = {}; + } + + if (!groupedByMonth[monthYear][item.category]) { + groupedByMonth[monthYear][item.category] = 0; + } + + groupedByMonth[monthYear][item.category] += item.cost; + categories.add(item.category); + }); + + // Prepare data for Chart.js + const labels = Object.keys(groupedByMonth).sort((a, b) => { + const [monthA, yearA] = a.split(' '); + const [monthB, yearB] = b.split(' '); + + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + if (yearA !== yearB) { + return yearA - yearB; + } + + return months.indexOf(monthA) - months.indexOf(monthB); + }); + + const datasets = Array.from(categories).map((category, index) => { + const colors = ['#6293CC', '#C55151', '#E8D06B', '#94B66F']; + const data = labels.map(month => groupedByMonth[month][category] || 0); + + return { + label: category, + data, + borderColor: colors[index % colors.length], + tension: 0.1, + }; + }); + + return { labels, datasets }; + }; + + // handle chart changes when filters change + useEffect(() => { + if (expenditureData.length > 0 && chartRef.current) { + let filteredData = expenditureData; + + // if not all data, filter by requested filtered title project + if (selectedProject !== 'all') { + filteredData = expenditureData.filter(item => item.projectId === selectedProject); + } + + // filter dates + if (dateRange.start && dateRange.end) { + filteredData = filteredData.filter(item => { + const itemDate = new Date(item.date); + return itemDate >= dateRange.start && itemDate <= dateRange.end; + }); + } + + // if data after filter? + if (filteredData.length === 0) { + if (chartInstance) { + chartInstance.destroy(); + setChartInstance(null); + } + return; + } + const processedData = processDataForChart(filteredData); + updateChart(processedData); + } + }, [selectedProject, dateRange, expenditureData]); + + const handleProjectChange = e => { + setSelectedProject(e.target.value); + }; + + const handleStartDateChange = e => { + const newStartDate = e.target.value; + setStartDate(newStartDate); + + if (newStartDate) { + const newStartDateTime = new Date(newStartDate); + setDateRange(prev => ({ + ...prev, + start: newStartDateTime, + })); + } else { + // if date cleared, use earliest date + const dates = expenditureData.map(item => new Date(item.date)); + setDateRange(prev => ({ + ...prev, + start: new Date(Math.min(...dates)), + })); + } + }; + + const handleEndDateChange = e => { + const newEndDate = e.target.value; + setEndDate(newEndDate); + + if (newEndDate) { + const newEndDateTime = new Date(newEndDate); + setDateRange(prev => ({ + ...prev, + end: newEndDateTime, + })); + } else { + // if date cleared, use earliest date + const dates = expenditureData.map(item => new Date(item.date)); + setDateRange(prev => ({ + ...prev, + end: new Date(Math.max(...dates)), + })); + } + }; + + return ( +
+

Cost Breakdown by Type of Expenditure

+
+ + +
+
+ + + + + +
+ {loading &&

Loading data...

} + {error &&

Error: {error}

} +
+ +
+
+ ); +} diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraphComponent.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraphComponent.jsx new file mode 100644 index 0000000000..9164f41cd5 --- /dev/null +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraphComponent.jsx @@ -0,0 +1,226 @@ +import Chart from 'chart.js/auto'; +import { useEffect, useRef, useState } from 'react'; +import { ENDPOINTS } from 'utils/URL'; +import axios from 'axios'; +import './ExpenditureLineGraph.css'; + +export default function ExpenditureLineGraph() { + const chartRef = useRef(null); + const [chartInstance, setChartInstance] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [expenditureData, setExpenditureData] = useState([]); + + // fetch data on component mount + useEffect(() => { + const fetchExpenditureData = async () => { + try { + setLoading(true); + const response = await axios.get(ENDPOINTS.BM_EXPENDITURE); + if (response.data.success) { + setExpenditureData(response.data.data); + } else { + setError('Failed to fetch data'); + } + } catch (err) { + setError(`Error: ${err.message}`); + } finally { + setLoading(false); + } + }; + + fetchExpenditureData(); + + // Clean up + return () => { + if (chartInstance) { + chartInstance.destroy(); + } + }; + }, []); + + // total expenditure by month - simplified + const processDataForChart = datas => { + // Group by month and sum all expenditures + const monthlyTotals = {}; + + datas.forEach(item => { + const date = new Date(item.date); + const month = date.toLocaleString('default', { month: 'short' }); + const year = date.getFullYear(); + const monthYear = `${month} ${year}`; + + if (!monthlyTotals[monthYear]) { + monthlyTotals[monthYear] = 0; + } + + // add to monthly total + monthlyTotals[monthYear] += item.amount || item.cost || 0; + }); + + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + const labels = Object.keys(monthlyTotals).sort((a, b) => { + const [monthA, yearA] = a.split(' '); + const [monthB, yearB] = b.split(' '); + + if (yearA !== yearB) { + return parseInt(yearA, 10) - parseInt(yearB, 10); + } + return months.indexOf(monthA) - months.indexOf(monthB); + }); + + const data = labels.map(month => monthlyTotals[month]); + + return { + labels, + datasets: [ + { + label: 'Total Expenditure', + data, + borderColor: '#6293CC', + backgroundColor: 'rgba(98, 147, 204, 0.1)', + borderWidth: 2, + fill: true, + tension: 0.2, + pointBackgroundColor: '#6293CC', + }, + ], + }; + }; + + // create/update chart + useEffect(() => { + if (!loading && !error && expenditureData.length > 0 && chartRef.current) { + const chartData = processDataForChart(expenditureData); + + if (chartInstance) { + chartInstance.data = chartData; + chartInstance.update(); + } else { + const ctx = chartRef.current.getContext('2d'); + const newChart = new Chart(ctx, { + type: 'line', + data: chartData, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + title: { + display: true, + text: 'Monthly Expenditure', + font: { + size: 12, + weight: 'bold', + }, + padding: { + top: 5, + bottom: 10, + }, + color: '#333', + }, + tooltip: { + enabled: true, + callbacks: { + label(context) { + return `$${context.parsed.y.toLocaleString()}`; + }, + }, + }, + }, + scales: { + y: { + display: true, + beginAtZero: true, + ticks: { + font: { size: 9 }, + callback(value) { + if (value >= 1000) { + return `$${value / 1000}k`; + } + return `$${value}`; + }, + }, + grid: { + color: 'rgba(0, 0, 0, 0.1)', + drawBorder: true, + }, + title: { + display: true, + text: '', + font: { size: 10 }, + }, + }, + x: { + display: true, + ticks: { + font: { size: 9 }, + maxRotation: 45, + minRotation: 45, + }, + grid: { + display: false, + }, + title: { + display: true, + text: '', + font: { size: 10 }, + }, + }, + }, + elements: { + point: { + radius: 3, + hoverRadius: 5, + }, + line: { + borderWidth: 2, + }, + }, + layout: { + padding: { + left: 10, + right: 10, + top: 5, + bottom: 10, + }, + }, + }, + }); + + setChartInstance(newChart); + } + } + }, [loading, error, expenditureData]); + + return ( +
+ +
+ ); +} diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index c987dba2c6..48fb8c01cf 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -5,6 +5,7 @@ import { v4 as uuidv4 } from 'uuid'; import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader'; import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions'; import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed'; +import ExpenditureLineGraph from '../ExpenditureGraph/ExpenditureLineGraphComponent'; const projectStatusButtons = [ { @@ -221,7 +222,7 @@ export default function WeeklyProjectSummary() { className: 'large', content: ( <> - {Array.from({ length: 4 }).map(() => { + {Array.from({ length: 3 }).map(() => { const uniqueId = uuidv4(); return (
@@ -230,6 +231,10 @@ export default function WeeklyProjectSummary() { ); })} +
+ +
+
📊 Big Card
), diff --git a/src/routes.js b/src/routes.js index 3e6a62cfa5..f774918b72 100644 --- a/src/routes.js +++ b/src/routes.js @@ -72,6 +72,7 @@ import CheckTypes from './components/BMDashboard/shared/CheckTypes'; import Toolslist from './components/BMDashboard/Tools/ToolsList'; import AddTool from './components/BMDashboard/Tools/AddTool'; import AddTeamMember from './components/BMDashboard/AddTeamMember/AddTeamMember'; +import ExpenditureLineGraph from './components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph'; // Community Portal import CPProtectedRoute from './components/common/CPDashboard/CPProtectedRoute'; @@ -391,6 +392,7 @@ export default( /> + diff --git a/src/utils/URL.js b/src/utils/URL.js index 5ff2ff74a5..f06b007a7d 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -7,7 +7,6 @@ export const ENDPOINTS = { USER_PROFILE_PROPERTY: userId => `${APIEndpoint}/userprofile/${userId}/property`, USER_PROFILES: `${APIEndpoint}/userprofile/`, UPDATE_REHIREABLE_STATUS: userId => `${APIEndpoint}/userprofile/${userId}/rehireable`, - TOGGLE_VISIBILITY: userId => `${APIEndpoint}/userprofile/${userId}/toggleInvisibility`, USER_PROFILE_UPDATE: `${APIEndpoint}/userprofile/update`, ADD_BLUE_SQUARE: userId => `${APIEndpoint}/userprofile/${userId}/addInfringement`, @@ -79,14 +78,6 @@ export const ENDPOINTS = { WEEKLY_SUMMARIES_REPORT: () => `${APIEndpoint}/reports/weeklysummaries`, SAVE_SUMMARY_RECEPIENTS: userid => `${APIEndpoint}/reports/recepients/${userid}`, GET_SUMMARY_RECEPIENTS: () => `${APIEndpoint}/reports/getrecepients`, - GET_CURRENT_WARNINGS: () => `${APIEndpoint}/currentWarnings`, - POST_NEW_WARNING: () => `${APIEndpoint}/currentWarnings`, - UPDATE_WARNING_DESCRIPTION: warningId => `${APIEndpoint}/currentWarnings/${warningId}`, - DELETE_WARNING_DESCRIPTION: warningId => `${APIEndpoint}/currentWarnings/${warningId}`, - EDIT_WARNING_DESCRIPTION: () => `${APIEndpoint}/currentWarnings/edit`, - GET_WARNINGS_BY_USER_ID: userId => `${APIEndpoint}/warnings/${userId}`, - POST_WARNINGS_BY_USER_ID: userId => `${APIEndpoint}/warnings/${userId}`, - DELETE_WARNINGS_BY_USER_ID: userId => `${APIEndpoint}/warnings/${userId}`, AUTHORIZE_WEEKLY_SUMMARY_REPORTS: () => `${APIEndpoint}/userProfile/authorizeUser/weeeklySummaries`, TOTAL_ORG_SUMMARY: (startDate, endDate, comparisonStartDate, comparisonEndDate) => @@ -100,6 +91,10 @@ export const ENDPOINTS = { POPUP_EDITOR_BY_ID: id => `${APIEndpoint}/popupeditor/${id}`, POPUP_EDITOR_BACKUP_BY_ID: id => `${APIEndpoint}/backup/popupeditor/${id}`, + GET_WARNINGS_BY_USER_ID: userId => `${APIEndpoint}/warnings/${userId}`, + POST_WARNINGS_BY_USER_ID: userId => `${APIEndpoint}/warnings/${userId}`, + DELETE_WARNINGS_BY_USER_ID: userId => `${APIEndpoint}/warnings/${userId}`, + TEAM_MEMBERS: teamId => `${APIEndpoint}/team/${teamId}/users`, TEAM_BY_ID: teamId => `${APIEndpoint}/team/${teamId}`, USER_UNREAD_TASK_NOTIFICATIONS: userId => `${APIEndpoint}/tasknotification/user/${userId}`, @@ -156,7 +151,6 @@ export const ENDPOINTS = { NON_HGN_EMAIL_SUBSCRIPTION: `${APIEndpoint}/add-non-hgn-email-subscription`, CONFIRM_EMAIL_SUBSCRIPTION: `${APIEndpoint}/confirm-non-hgn-email-subscription`, REMOVE_EMAIL_SUBSCRIPTION: `${APIEndpoint}/remove-non-hgn-email-subscription`, - // reasons endpoints CREATEREASON: () => { return `${APIEndpoint}/reason/`; @@ -181,7 +175,6 @@ export const ENDPOINTS = { GET_TOTAL_COUNTRY_COUNT: () => `${APIEndpoint}/getTotalCountryCount`, GET_ALL_FOLLOWUPS: () => `${APIEndpoint}/followup`, - SET_USER_FOLLOWUP: (userId, taskId) => `${APIEndpoint}/followup/${userId}/${taskId}`, GET_PROJECT_BY_PERSON: searchName => `${APIEndpoint}/userProfile/projects/${searchName}`, @@ -197,6 +190,7 @@ export const ENDPOINTS = { BM_REUSABLE_TYPES: `${APIEndpoint}/bm/invtypes/reusables`, BM_REUSABLES: `${APIEndpoint}/bm/reusables`, BM_PURCHASE_REUSABLES: `${APIEndpoint}/bm/reusables/purchase`, + BM_EQUIPMENTS: `${APIEndpoint}/bm/equipments`, BM_EQUIPMENT_TYPES: `${APIEndpoint}/bm/invtypes/equipments`, BM_EQUIPMENT_PURCHASE: `${APIEndpoint}/bm/equipment/purchase`, BM_PROJECTS: `${APIEndpoint}/bm/projects`, @@ -215,17 +209,19 @@ export const ENDPOINTS = { BM_EXTERNAL_TEAM: `${APIEndpoint}/bm/externalTeam`, BM_INVENTORY_UNITS: `${APIEndpoint}/bm/inventoryUnits`, BM_INVTYPE_ROOT: `${APIEndpoint}/bm/invtypes`, - BM_TOOLS: `${APIEndpoint}/bm/tools/`, BM_TOOL_BY_ID: singleToolId => `${APIEndpoint}/bm/tools/${singleToolId}`, BM_LOG_TOOLS: `${APIEndpoint}/bm/tools/log`, BM_EQUIPMENT_BY_ID: singleEquipmentId => `${APIEndpoint}/bm/equipment/${singleEquipmentId}`, - BM_EQUIPMENTS: `${APIEndpoint}/bm/equipments`, BM_INVTYPE_TYPE: type => `${APIEndpoint}/bm/invtypes/${type}`, + BM_EXPENDITURE: `${APIEndpoint}/bm/expenditure`, BM_TAGS: `${APIEndpoint}/bm/tags`, BM_TAG_ADD: `${APIEndpoint}/bm/tags`, BM_TAGS_DELETE: `${APIEndpoint}/bm/tags`, + BM_ORGS_WITH_LOCATION: `${APIEndpoint}/bm/orgLocation`, + ORG_DETAILS: (projectId) => `${APIEndpoint}/bm/orgLocation/${projectId}`, + GET_TIME_OFF_REQUESTS: () => `${APIEndpoint}/getTimeOffRequests`, ADD_TIME_OFF_REQUEST: () => `${APIEndpoint}/setTimeOffRequest`, UPDATE_TIME_OFF_REQUEST: id => `${APIEndpoint}/updateTimeOffRequest/${id}`, From 730d32aa5f7de8c3dd4185259a15bf135f8df8fb Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Thu, 1 May 2025 10:50:21 -0700 Subject: [PATCH 02/12] Reina create line graph chart for cost breakdown by expenditure --- .../ExpenditureGraph/ExpenditureLineGraph.css | 64 +++- .../ExpenditureGraph/ExpenditureLineGraph.jsx | 277 ++++++++++++++---- 2 files changed, 281 insertions(+), 60 deletions(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css index d7d0eb3b48..afd5f485ea 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css @@ -2,9 +2,62 @@ padding: 20px; background-color: #f9f9f9; border-radius: 8px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1) -} - + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + transition: all 0.3s ease; + } + + .expenditure-chart-container.dark-mode { + background-color: var(--bg-color, #1b2a41); + color: var(--text-color, #ffffff); + box-shadow: 0 2px 4px rgba(255,255,255,0.05); + } + + .expenditure-chart-container.dark-mode select, + .expenditure-chart-container.dark-mode input { + background-color: var(--section-bg, #253342); + color: var(--text-color, #ffffff); + border: 1px solid rgba(255, 255, 255, 0.2); + } + + .expenditure-chart-container.dark-mode select:focus, + .expenditure-chart-container.dark-mode input:focus { + border-color: var(--button-bg, #e8a71c); + outline: none; + } + + .expenditure-chart-container.dark-mode .filter-controls { + background-color: var(--section-bg, #253342); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .expenditure-chart-container h1 { + font-size: 1.5rem; + margin-bottom: 20px; + font-weight: 600; + } + + .project-filter { + margin: 20px; + justify-content: flex-end; + display: flex; + align-items: center; + } + + .filter-group { + margin: 20px; + justify-content: flex-end; + display: flex; + align-items: center; + } + + label { + margin: 20px; + } + + #project-select { + margin-left: 15px; + } + .project-filter { margin: 20px; justify-content:flex-end; @@ -26,3 +79,8 @@ label { #project-select { margin-left: 15px; } + +.dark-mode .project-filter, +.dark-mode .filter-group { + background-color: var (--card-bg); +} \ No newline at end of file diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index e19a82df58..0684f83702 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -1,8 +1,8 @@ +import { useRef, useState, useEffect } from 'react'; +import axios from 'axios'; import Chart from 'chart.js/auto'; -import { useEffect, useRef, useState } from 'react'; import { ENDPOINTS } from 'utils/URL'; -import axios from 'axios'; -import './ExpenditureLineGraph.css'; +import { useSelector } from 'react-redux'; export default function ExpenditureLineGraph() { const chartRef = useRef(null); @@ -12,12 +12,13 @@ export default function ExpenditureLineGraph() { const [expenditureData, setExpenditureData] = useState([]); const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState('all'); - // date filters const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [dateRange, setDateRange] = useState({ start: null, end: null }); + const darkMode = useSelector(state => state.theme.darkMode); + // format the date as YYYY-MM-DD const formatDateForInput = date => { return date.toISOString().split('T')[0]; @@ -29,23 +30,19 @@ export default function ExpenditureLineGraph() { try { setLoading(true); const response = await axios.get(ENDPOINTS.BM_EXPENDITURE); - if (response.data.success) { const { data } = response.data; setExpenditureData(data); // extract unique IDs const uniqueProjects = [...new Set(data.map(item => item.projectId))]; setProjects(uniqueProjects); - // default date range if (data.length > 0) { const dates = data.map(item => new Date(item.date)); const minDate = new Date(Math.min(...dates)); const maxDate = new Date(Math.max(...dates)); - setStartDate(formatDateForInput(minDate)); setEndDate(formatDateForInput(maxDate)); - // set date range setDateRange({ start: minDate, @@ -75,16 +72,25 @@ export default function ExpenditureLineGraph() { const updateChart = chartData => { const ctx = chartRef.current.getContext('2d'); - const chartTitle = selectedProject === 'all' ? 'Cost Breakdown by Type of Expenditure (all projects)' : `Cost Breakdown by Type of Expenditure (Project: ${selectedProject})`; + const gridColor = darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; + const textColor = darkMode ? '#ffffff' : '#666666'; + if (chartInstance) { // Update existing chart chartInstance.data = chartData; chartInstance.options.plugins.title.text = chartTitle; + chartInstance.options.plugins.title.color = textColor; + chartInstance.options.scales.y.grid.color = gridColor; + chartInstance.options.scales.x.grid.color = gridColor; + chartInstance.options.scales.y.ticks.color = textColor; + chartInstance.options.scales.x.ticks.color = textColor; + chartInstance.options.scales.y.title.color = textColor; + chartInstance.options.scales.x.title.color = textColor; chartInstance.update(); } else { // Create new chart @@ -97,6 +103,30 @@ export default function ExpenditureLineGraph() { title: { display: true, text: chartTitle, + color: textColor, + font: { + size: 14, + weight: 'bold', + }, + padding: { + top: 10, + bottom: 15, + }, + }, + legend: { + labels: { + color: textColor, + font: { + size: 12, + }, + }, + }, + tooltip: { + backgroundColor: darkMode ? '#3a506b' : 'rgba(0, 0, 0, 0.7)', + titleColor: '#ffffff', + bodyColor: '#ffffff', + borderColor: darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.1)', + borderWidth: 1, }, }, scales: { @@ -104,18 +134,53 @@ export default function ExpenditureLineGraph() { title: { display: true, text: 'Cost($)', + color: textColor, + font: { + size: 12, + weight: 'bold', + }, + }, + ticks: { + color: textColor, + font: { + size: 11, + }, + callback(value) { + if (value >= 1000) { + return `$${value / 1000}k`; + } + return `$${value}`; + }, + }, + grid: { + color: gridColor, + borderColor: gridColor, }, }, x: { title: { display: true, text: 'Month', + color: textColor, + font: { + size: 12, + weight: 'bold', + }, + }, + ticks: { + color: textColor, + font: { + size: 11, + }, + }, + grid: { + color: gridColor, + borderColor: gridColor, }, }, }, }, }; - const newChartInstance = new Chart(ctx, config); setChartInstance(newChartInstance); } @@ -145,11 +210,9 @@ export default function ExpenditureLineGraph() { categories.add(item.category); }); - // Prepare data for Chart.js const labels = Object.keys(groupedByMonth).sort((a, b) => { const [monthA, yearA] = a.split(' '); const [monthB, yearB] = b.split(' '); - const months = [ 'Jan', 'Feb', @@ -166,21 +229,23 @@ export default function ExpenditureLineGraph() { ]; if (yearA !== yearB) { - return yearA - yearB; + return parseInt(yearA, 10) - parseInt(yearB, 10); } - return months.indexOf(monthA) - months.indexOf(monthB); }); const datasets = Array.from(categories).map((category, index) => { const colors = ['#6293CC', '#C55151', '#E8D06B', '#94B66F']; const data = labels.map(month => groupedByMonth[month][category] || 0); - return { label: category, data, borderColor: colors[index % colors.length], + backgroundColor: darkMode + ? `${colors[index % colors.length]}33` + : `${colors[index % colors.length]}1A`, tension: 0.1, + fill: false, }; }); @@ -213,10 +278,29 @@ export default function ExpenditureLineGraph() { } return; } + const processedData = processDataForChart(filteredData); updateChart(processedData); } - }, [selectedProject, dateRange, expenditureData]); + }, [selectedProject, dateRange, expenditureData, darkMode]); + + useEffect(() => { + if (chartInstance) { + const textColor = darkMode ? '#ffffff' : '#666666'; + const gridColor = darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; + + chartInstance.options.plugins.title.color = textColor; + chartInstance.options.plugins.legend.labels.color = textColor; + chartInstance.options.scales.y.grid.color = gridColor; + chartInstance.options.scales.x.grid.color = gridColor; + chartInstance.options.scales.y.ticks.color = textColor; + chartInstance.options.scales.x.ticks.color = textColor; + chartInstance.options.scales.y.title.color = textColor; + chartInstance.options.scales.x.title.color = textColor; + + chartInstance.update(); + } + }, [darkMode, chartInstance]); const handleProjectChange = e => { setSelectedProject(e.target.value); @@ -225,7 +309,6 @@ export default function ExpenditureLineGraph() { const handleStartDateChange = e => { const newStartDate = e.target.value; setStartDate(newStartDate); - if (newStartDate) { const newStartDateTime = new Date(newStartDate); setDateRange(prev => ({ @@ -245,7 +328,6 @@ export default function ExpenditureLineGraph() { const handleEndDateChange = e => { const newEndDate = e.target.value; setEndDate(newEndDate); - if (newEndDate) { const newEndDateTime = new Date(newEndDate); setDateRange(prev => ({ @@ -263,47 +345,128 @@ export default function ExpenditureLineGraph() { }; return ( -
-

Cost Breakdown by Type of Expenditure

-
- - + + {projects.map(project => ( + + ))} + +
+ +
- - {projects.map(project => ( - - ))} - -
-
- - - - - + + + + + +
- {loading &&

Loading data...

} - {error &&

Error: {error}

} -
+ + {loading && ( +

Loading data...

+ )} + + {error && ( +

+ Error: {error} +

+ )} + +
From 07ca8481e21973a12b797faca4db5d139a9b7671 Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Thu, 1 May 2025 11:13:49 -0700 Subject: [PATCH 03/12] Reina create line graph chart for cost breakdown by expenditure --- .../BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index 0684f83702..82e20afb8c 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -352,7 +352,7 @@ export default function ExpenditureLineGraph() { color: darkMode ? 'var(--text-color)' : 'inherit', }} > - Cost Breakdown by Type of Expenditure + Cost Breakdown by Type of Expenditures
Date: Wed, 18 Jun 2025 20:52:54 -0700 Subject: [PATCH 04/12] feedback changes --- .../ExpenditureGraph/ExpenditureLineGraph.jsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index 82e20afb8c..a250b2ea8f 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -30,7 +30,7 @@ export default function ExpenditureLineGraph() { try { setLoading(true); const response = await axios.get(ENDPOINTS.BM_EXPENDITURE); - if (response.data.success) { + if (response?.data?.success) { const { data } = response.data; setExpenditureData(data); // extract unique IDs @@ -83,7 +83,21 @@ export default function ExpenditureLineGraph() { if (chartInstance) { // Update existing chart chartInstance.data = chartData; - chartInstance.options.plugins.title.text = chartTitle; + const { options } = chartInstance; + const { plugins, scales } = options; + const { x, y } = scales; + + plugins.title.text = chartTitle; + plugins.title.color = textColor; + + x.grid.color = gridColor; + y.grid.color = gridColor; + + x.ticks.color = textColor; + y.ticks.color = textColor; + + x.title.color = textColor; + y.title.color = textColor; chartInstance.options.plugins.title.color = textColor; chartInstance.options.scales.y.grid.color = gridColor; chartInstance.options.scales.x.grid.color = gridColor; From 38bfb7d4d89ff1b53950ae829f680142fac9332d Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Mon, 23 Jun 2025 11:38:10 -0700 Subject: [PATCH 05/12] test issues --- src/routes.js | 1 - src/utils/URL.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes.js b/src/routes.js index 789948403d..05ee42c64b 100644 --- a/src/routes.js +++ b/src/routes.js @@ -91,7 +91,6 @@ import AddTeamMember from './components/BMDashboard/AddTeamMember/AddTeamMember' import ExpenditureLineGraph from './components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph'; import AttendanceNoShow from './components/AttendanceSystem/AttendanceNoShowCharts'; -import AddTeamMember from './components/BMDashboard/AddTeamMember/AddTeamMember'; // eslint-disable-next-line import/order import IssueChart from './components/BMDashboard/Issues/issueCharts'; import BMTimeLogger from './components/BMDashboard/BMTimeLogger/BMTimeLogger'; diff --git a/src/utils/URL.js b/src/utils/URL.js index e8ec3c9736..f1fecef142 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -21,6 +21,7 @@ export const ENDPOINTS = { USER_AUTOCOMPLETE: searchText => `${APIEndpoint}/userProfile/autocomplete/${searchText}`, SEARCH_USER: `${APIEndpoint}/users/search`, TOGGLE_BIO_STATUS: userId => `${APIEndpoint}/userProfile/${userId}/toggleBio`, + TOGGLE_VISIBILITY: userId => `${APIEndpoint}/userProfile/${userId}/toggleVisibility`, INFO_COLLECTIONS: `${APIEndpoint}/informations`, INFO_COLLECTION: infoId => `${APIEndpoint}/informations/${infoId}`, From bfd83bdf53ed5381cbd026f46278e13f8f18e1ab Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Mon, 23 Jun 2025 11:53:27 -0700 Subject: [PATCH 06/12] lint errors --- .../BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index e17b9cc098..d4a8fc8333 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -9,6 +9,7 @@ import PaidLaborCost from './PaidLaborCost/PaidLaborCost'; import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions'; import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed'; import ExpenditureLineGraph from '../ExpenditureGraph/ExpenditureLineGraphComponent'; +import styles from './WeeklyProjectSummary.module.css'; const projectStatusButtons = [ { From 3d0c25106e0e8af6e92befa85c5ede68007f2fb6 Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Wed, 9 Jul 2025 15:10:54 -0700 Subject: [PATCH 07/12] light and dark mode fix --- .../ExpenditureGraph/ExpenditureLineGraph.css | 28 +- .../ExpenditureGraph/ExpenditureLineGraph.jsx | 442 ++++++++++-------- 2 files changed, 264 insertions(+), 206 deletions(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css index afd5f485ea..75da5b8227 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css @@ -1,9 +1,30 @@ +:root { + --bg-color: #f9f9f9; + --section-bg: #f5f5f5; + --text-color: #222; + --card-bg: #fff; + --button-bg: #e8a71c; +} + +.dark-mode { + --bg-color: #1b2a41; + --section-bg: #253342; + --text-color: #ffffff; + --card-bg: #16213e; + --button-bg: #e8a71c; +} + +.body.dark-mode, .dark-mode body { + background-color: #1b2a41 !important; +} + .expenditure-chart-container { padding: 20px; background-color: #f9f9f9; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: all 0.3s ease; + min-height: 100vh; } .expenditure-chart-container.dark-mode { @@ -49,11 +70,6 @@ display: flex; align-items: center; } - - label { - margin: 20px; - } - #project-select { margin-left: 15px; } @@ -82,5 +98,5 @@ label { .dark-mode .project-filter, .dark-mode .filter-group { - background-color: var (--card-bg); + background-color: var(--card-bg); } \ No newline at end of file diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index a250b2ea8f..eb6251c721 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -12,6 +12,7 @@ export default function ExpenditureLineGraph() { const [expenditureData, setExpenditureData] = useState([]); const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState('all'); + // date filters const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); @@ -70,7 +71,17 @@ export default function ExpenditureLineGraph() { }; }, []); - const updateChart = chartData => { + // Destroy and recreate chart when theme changes + useEffect(() => { + if (chartInstance) { + chartInstance.destroy(); + setChartInstance(null); + } + }, [darkMode]); + + const createChart = chartData => { + if (!chartRef.current) return; + const ctx = chartRef.current.getContext('2d'); const chartTitle = selectedProject === 'all' @@ -79,125 +90,114 @@ export default function ExpenditureLineGraph() { const gridColor = darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; const textColor = darkMode ? '#ffffff' : '#666666'; + const chartBackgroundColor = darkMode ? '#1b2a41' : '#ffffff'; + // Destroy existing chart if it exists if (chartInstance) { - // Update existing chart - chartInstance.data = chartData; - const { options } = chartInstance; - const { plugins, scales } = options; - const { x, y } = scales; - - plugins.title.text = chartTitle; - plugins.title.color = textColor; - - x.grid.color = gridColor; - y.grid.color = gridColor; - - x.ticks.color = textColor; - y.ticks.color = textColor; - - x.title.color = textColor; - y.title.color = textColor; - chartInstance.options.plugins.title.color = textColor; - chartInstance.options.scales.y.grid.color = gridColor; - chartInstance.options.scales.x.grid.color = gridColor; - chartInstance.options.scales.y.ticks.color = textColor; - chartInstance.options.scales.x.ticks.color = textColor; - chartInstance.options.scales.y.title.color = textColor; - chartInstance.options.scales.x.title.color = textColor; - chartInstance.update(); - } else { - // Create new chart - const config = { - type: 'line', - data: chartData, - options: { - responsive: true, - plugins: { + chartInstance.destroy(); + } + + const config = { + type: 'line', + data: chartData, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: chartTitle, + color: textColor, + font: { + size: 14, + weight: 'bold', + }, + padding: { + top: 10, + bottom: 15, + }, + }, + legend: { + labels: { + color: textColor, + font: { + size: 12, + }, + }, + }, + tooltip: { + backgroundColor: darkMode ? '#3a506b' : 'rgba(0, 0, 0, 0.7)', + titleColor: '#ffffff', + bodyColor: '#ffffff', + borderColor: darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.1)', + borderWidth: 1, + }, + }, + scales: { + y: { title: { display: true, - text: chartTitle, + text: 'Cost($)', color: textColor, font: { - size: 14, + size: 12, weight: 'bold', }, - padding: { - top: 10, - bottom: 15, - }, }, - legend: { - labels: { - color: textColor, - font: { - size: 12, - }, + ticks: { + color: textColor, + font: { + size: 11, + }, + callback(value) { + if (value >= 1000) { + return `$${value / 1000}k`; + } + return `$${value}`; }, }, - tooltip: { - backgroundColor: darkMode ? '#3a506b' : 'rgba(0, 0, 0, 0.7)', - titleColor: '#ffffff', - bodyColor: '#ffffff', - borderColor: darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.1)', - borderWidth: 1, + grid: { + color: gridColor, + borderColor: gridColor, }, }, - scales: { - y: { - title: { - display: true, - text: 'Cost($)', - color: textColor, - font: { - size: 12, - weight: 'bold', - }, - }, - ticks: { - color: textColor, - font: { - size: 11, - }, - callback(value) { - if (value >= 1000) { - return `$${value / 1000}k`; - } - return `$${value}`; - }, - }, - grid: { - color: gridColor, - borderColor: gridColor, + x: { + title: { + display: true, + text: 'Month', + color: textColor, + font: { + size: 12, + weight: 'bold', }, }, - x: { - title: { - display: true, - text: 'Month', - color: textColor, - font: { - size: 12, - weight: 'bold', - }, - }, - ticks: { - color: textColor, - font: { - size: 11, - }, - }, - grid: { - color: gridColor, - borderColor: gridColor, + ticks: { + color: textColor, + font: { + size: 11, }, }, + grid: { + color: gridColor, + borderColor: gridColor, + }, }, }, - }; - const newChartInstance = new Chart(ctx, config); - setChartInstance(newChartInstance); - } + animation: { + onComplete() { + const chart = this; + ctx.save(); + ctx.globalCompositeOperation = 'destination-over'; + ctx.fillStyle = chartBackgroundColor; + ctx.fillRect(0, 0, chart.width, chart.height); + ctx.restore(); + }, + }, + }, + }; + + const newChartInstance = new Chart(ctx, config); + setChartInstance(newChartInstance); }; const processDataForChart = expenditureDataArr => { @@ -205,7 +205,7 @@ export default function ExpenditureLineGraph() { const groupedByMonth = {}; const categories = new Set(); - // Extract month from date and group data + // month from date expenditureDataArr.forEach(item => { const date = new Date(item.date); const month = date.toLocaleString('default', { month: 'short' }); @@ -251,6 +251,7 @@ export default function ExpenditureLineGraph() { const datasets = Array.from(categories).map((category, index) => { const colors = ['#6293CC', '#C55151', '#E8D06B', '#94B66F']; const data = labels.map(month => groupedByMonth[month][category] || 0); + return { label: category, data, @@ -294,28 +295,10 @@ export default function ExpenditureLineGraph() { } const processedData = processDataForChart(filteredData); - updateChart(processedData); + createChart(processedData); } }, [selectedProject, dateRange, expenditureData, darkMode]); - useEffect(() => { - if (chartInstance) { - const textColor = darkMode ? '#ffffff' : '#666666'; - const gridColor = darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; - - chartInstance.options.plugins.title.color = textColor; - chartInstance.options.plugins.legend.labels.color = textColor; - chartInstance.options.scales.y.grid.color = gridColor; - chartInstance.options.scales.x.grid.color = gridColor; - chartInstance.options.scales.y.ticks.color = textColor; - chartInstance.options.scales.x.ticks.color = textColor; - chartInstance.options.scales.y.title.color = textColor; - chartInstance.options.scales.x.title.color = textColor; - - chartInstance.update(); - } - }, [darkMode, chartInstance]); - const handleProjectChange = e => { setSelectedProject(e.target.value); }; @@ -362,8 +345,9 @@ export default function ExpenditureLineGraph() {

Cost Breakdown by Type of Expenditures @@ -372,88 +356,118 @@ export default function ExpenditureLineGraph() {
-
- - -
-
- - - - - + + +
+ +
+ > + + + + +

- {loading && ( -

Loading data...

+

+ Loading data... +

)} {error && ( @@ -465,6 +479,9 @@ export default function ExpenditureLineGraph() { padding: '10px', borderRadius: '4px', border: darkMode ? '1px solid #ff6b6b' : '1px solid #d32f2f', + textAlign: 'center', + maxWidth: '800px', + margin: '0 auto 20px auto', }} > Error: {error} @@ -473,15 +490,40 @@ export default function ExpenditureLineGraph() {
- +
+ +
); From b9057f4a73b6b373de50a9cf145830ae2b0a5a08 Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Sun, 13 Jul 2025 13:31:43 -0700 Subject: [PATCH 08/12] test errors --- .../BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index 82559e5414..139a3dee02 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -181,7 +181,7 @@ export default function WeeklyProjectSummary() { if (index === 1) { content = ; } else if (index === 2) { - content = ; + // content = ; } else { content =

📊 Card

; } From a5845435704731c8ce00906e87443593629ea592 Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Wed, 16 Jul 2025 13:43:08 -0700 Subject: [PATCH 09/12] fixed filter layout --- .../ExpenditureGraph/ExpenditureLineGraph.jsx | 162 +++++++++--------- 1 file changed, 85 insertions(+), 77 deletions(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index eb6251c721..00b2d6478f 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -341,27 +341,35 @@ export default function ExpenditureLineGraph() { } }; - return ( -
-

- Cost Breakdown by Type of Expenditures -

- +return ( +
+

+ Cost Breakdown by Type of Expenditures +

+
-
- {loading && ( -

- Loading data... -

- )} +
- {error && ( -

- Error: {error} -

- )} + {loading && ( +

+ Loading data... +

+ )} + {error && ( +

+ Error: {error} +

+ )} + +
-
- -
+ />
- ); +
+); } From 1bf8efbb1eb000a19a194c33a5af9f9433be3b33 Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Fri, 25 Jul 2025 09:31:41 -0700 Subject: [PATCH 10/12] styling changes and test changes --- .../ExpenditureGraph/ExpenditureLineGraph.css | 71 ++-- .../ExpenditureGraph/ExpenditureLineGraph.jsx | 316 +++++++++--------- 2 files changed, 202 insertions(+), 185 deletions(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css index 75da5b8227..7098f117bd 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.css @@ -14,10 +14,6 @@ --button-bg: #e8a71c; } -.body.dark-mode, .dark-mode body { - background-color: #1b2a41 !important; -} - .expenditure-chart-container { padding: 20px; background-color: #f9f9f9; @@ -27,29 +23,50 @@ min-height: 100vh; } - .expenditure-chart-container.dark-mode { - background-color: var(--bg-color, #1b2a41); - color: var(--text-color, #ffffff); - box-shadow: 0 2px 4px rgba(255,255,255,0.05); - } - - .expenditure-chart-container.dark-mode select, - .expenditure-chart-container.dark-mode input { - background-color: var(--section-bg, #253342); - color: var(--text-color, #ffffff); - border: 1px solid rgba(255, 255, 255, 0.2); - } - - .expenditure-chart-container.dark-mode select:focus, - .expenditure-chart-container.dark-mode input:focus { - border-color: var(--button-bg, #e8a71c); - outline: none; - } - - .expenditure-chart-container.dark-mode .filter-controls { - background-color: var(--section-bg, #253342); - border: 1px solid rgba(255, 255, 255, 0.1); - } +.expenditure-chart-container.dark-mode { + background-color: #1b2a41 !important; + color: #ffffff !important; + box-shadow: none !important; + padding: 0 !important; + min-height: 100vh; + border-radius: 0 !important; +} + +.expenditure-chart-container.dark-mode .chart-inner-container { + background-color: #16213e !important; + border: 1px solid #233554 !important; + box-shadow: none !important; + border-radius: 0 !important; + width: 100%; + height: 100%; + margin: 0; + padding: 20px; +} + +.expenditure-chart-container.dark-mode .filter-controls, +.expenditure-chart-container.dark-mode .project-filter, +.expenditure-chart-container.dark-mode .filter-group { + background-color: #16213e !important; + color: #ffffff !important; + border: none !important; +} + +.expenditure-chart-container.dark-mode .error { + background-color: #2a3a5a !important; + color: #ff6b6b !important; + border: 1px solid #ff6b6b !important; +} + +.expenditure-chart-container.dark-mode .chart-inner-container { + background-color: #16213e !important; + border: 1px solid #233554 !important; + box-shadow: none !important; +} + +.expenditure-chart-container.dark-mode canvas { + background-color: #16213e !important; + border-radius: 8px; +} .expenditure-chart-container h1 { font-size: 1.5rem; diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index 00b2d6478f..21f6188aa8 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -341,198 +341,198 @@ export default function ExpenditureLineGraph() { } }; -return ( -
-

- Cost Breakdown by Type of Expenditures -

-
+ return ( +
+

+ Cost Breakdown by Type of Expenditures +

- - + + {projects.map(project => ( + + ))} + +
+
- - {projects.map(project => ( - - ))} - -
-
- - - - + + + + +
-
- {loading && ( -

- Loading data... -

- )} + {loading && ( +

+ Loading data... +

+ )} + + {error && ( +

+ Error: {error} +

+ )} - {error && ( -

- Error: {error} -

- )} - -
- + > + +
-
-); + ); } From 9dec5f2afd449444c533c08f3af3e08896705481 Mon Sep 17 00:00:00 2001 From: ReinaT5678 Date: Wed, 30 Jul 2025 11:52:19 -0700 Subject: [PATCH 11/12] feedback fixes for errors --- .../ExpenditureGraph/ExpenditureLineGraph.jsx | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index 21f6188aa8..9105a6a33c 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -12,6 +12,8 @@ export default function ExpenditureLineGraph() { const [expenditureData, setExpenditureData] = useState([]); const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState('all'); + const [dateError, setDateError] = useState(null); + const [noDataError, setNoDataError] = useState(null); // date filters const [startDate, setStartDate] = useState(''); @@ -92,7 +94,6 @@ export default function ExpenditureLineGraph() { const textColor = darkMode ? '#ffffff' : '#666666'; const chartBackgroundColor = darkMode ? '#1b2a41' : '#ffffff'; - // Destroy existing chart if it exists if (chartInstance) { chartInstance.destroy(); } @@ -248,9 +249,31 @@ export default function ExpenditureLineGraph() { return months.indexOf(monthA) - months.indexOf(monthB); }); + if (labels.length === 1) { + const [month, year] = labels[0].split(' '); + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + const monthIndex = months.indexOf(month); + const nextMonthIndex = (monthIndex + 1) % 12; + const nextYear = nextMonthIndex === 0 ? parseInt(year) + 1 : parseInt(year); + labels.push(`${months[nextMonthIndex]} ${nextYear}`); + } + const datasets = Array.from(categories).map((category, index) => { const colors = ['#6293CC', '#C55151', '#E8D06B', '#94B66F']; - const data = labels.map(month => groupedByMonth[month][category] || 0); + const data = labels.map(month => groupedByMonth[month]?.[category] || 0); return { label: category, @@ -270,6 +293,20 @@ export default function ExpenditureLineGraph() { // handle chart changes when filters change useEffect(() => { if (expenditureData.length > 0 && chartRef.current) { + // Reset error states + setDateError(null); + setNoDataError(null); + + // Validate dates + if (dateRange.start && dateRange.end && dateRange.start > dateRange.end) { + setDateError('Start date cannot be greater than end date'); + if (chartInstance) { + chartInstance.destroy(); + setChartInstance(null); + } + return; + } + let filteredData = expenditureData; // if not all data, filter by requested filtered title project @@ -287,6 +324,7 @@ export default function ExpenditureLineGraph() { // if data after filter? if (filteredData.length === 0) { + setNoDataError('No data available for the selected date range and project'); if (chartInstance) { chartInstance.destroy(); setChartInstance(null); @@ -313,7 +351,6 @@ export default function ExpenditureLineGraph() { start: newStartDateTime, })); } else { - // if date cleared, use earliest date const dates = expenditureData.map(item => new Date(item.date)); setDateRange(prev => ({ ...prev, @@ -332,7 +369,6 @@ export default function ExpenditureLineGraph() { end: newEndDateTime, })); } else { - // if date cleared, use earliest date const dates = expenditureData.map(item => new Date(item.date)); setDateRange(prev => ({ ...prev, @@ -498,6 +534,41 @@ export default function ExpenditureLineGraph() { Error: {error}

)} + {dateError && ( +

+ {dateError} +

+ )} + + {noDataError && ( +

+ {noDataError} +

+ )}
Date: Wed, 30 Jul 2025 11:59:24 -0700 Subject: [PATCH 12/12] fixed lint issues --- .../BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx index 9105a6a33c..d9bf3fbcfc 100644 --- a/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx +++ b/src/components/BMDashboard/ExpenditureGraph/ExpenditureLineGraph.jsx @@ -12,8 +12,8 @@ export default function ExpenditureLineGraph() { const [expenditureData, setExpenditureData] = useState([]); const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState('all'); - const [dateError, setDateError] = useState(null); - const [noDataError, setNoDataError] = useState(null); + const [dateError, setDateError] = useState(null); + const [noDataError, setNoDataError] = useState(null); // date filters const [startDate, setStartDate] = useState(''); @@ -267,10 +267,9 @@ export default function ExpenditureLineGraph() { ]; const monthIndex = months.indexOf(month); const nextMonthIndex = (monthIndex + 1) % 12; - const nextYear = nextMonthIndex === 0 ? parseInt(year) + 1 : parseInt(year); + const nextYear = nextMonthIndex === 0 ? parseInt(year, 10) + 1 : parseInt(year, 10); labels.push(`${months[nextMonthIndex]} ${nextYear}`); } - const datasets = Array.from(categories).map((category, index) => { const colors = ['#6293CC', '#C55151', '#E8D06B', '#94B66F']; const data = labels.map(month => groupedByMonth[month]?.[category] || 0);