Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/BMDashboard/BMDashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { fetchBMProjects } from '../../actions/bmdashboard/projectActions';
import ProjectsList from './Projects/ProjectsList';
import ProjectSelectForm from './Projects/ProjectSelectForm';
import ProjectStatusDonutChart from './ProjectStatus/ProjectStatusDonutChart';

Check warning on line 7 in src/components/BMDashboard/BMDashboard.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'ProjectStatusDonutChart'.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ2c0mSmWySueEBazRlD&open=AZ2c0mSmWySueEBazRlD&pullRequest=5127
import BMError from './shared/BMError';
import styles from './BMDashboard.module.css';

Expand Down Expand Up @@ -201,7 +201,7 @@
) : (
<>
<ProjectSelectForm />
<ProjectStatusDonutChart />
{/* <ProjectStatusDonutChart /> */}
<ProjectsList />
</>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer, Label } from 'recharts';
import axios from 'axios';
import styles from './ProjectStatusDonutChart.module.css';

const COLORS = ['#B39DDB', '#80DEEA', '#FFABAB']; // Active, Completed, Delayed
const COLORS = ['#B39DDB', '#80DEEA', '#FFABAB'];

export default function ProjectStatusDonutChart() {
const darkMode = useSelector(state => state.theme?.darkMode || false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [statusData, setStatusData] = useState(null);
Expand All @@ -18,31 +20,19 @@ export default function ProjectStatusDonutChart() {
setLoading(true);
setError(null);

// Build query string
const query = [];
if (startDate) query.push(`startDate=${startDate}`);
if (endDate) query.push(`endDate=${endDate}`);
const queryString = query.length ? `?${query.join('&')}` : '';

// Get token from localStorage (Dev Admin session)
const token = localStorage.getItem('token');

const res = await axios.get(`http://localhost:4500/api/projects/status${queryString}`, {
headers: { Authorization: token },
});

// TEMPORARY MOCK DATA - for testing purposes
/*setStatusData({
totalProjects: 50,
activeProjects: 20,
completedProjects: 20,
delayedProjects: 10,
});
return;*/

setStatusData(res.data);
} catch (err) {
// console.error(err);
setError('Unable to load project status.');
} finally {
setLoading(false);
Expand All @@ -63,10 +53,9 @@ export default function ProjectStatusDonutChart() {
{ name: 'Delayed Projects', value: statusData.delayedProjects },
];

// SHOW MESSAGE WHEN THERE IS NO DATA
if (pieData.every(item => item.value === 0)) {
return (
<div className={styles.container}>
<div className={`${styles.container} ${darkMode ? styles.darkContainer : ''}`}>
<h2 className={styles.title}>PROJECT STATUS</h2>
<p className={styles.noDataMessage}>No project status data available.</p>
</div>
Expand All @@ -84,7 +73,7 @@ export default function ProjectStatusDonutChart() {
!statusData.activeProjects && !statusData.completedProjects && !statusData.delayedProjects;

return (
<div className={styles.container}>
<div className={`${styles.container} ${darkMode ? styles.darkContainer : ''}`}>
<h2 className={styles.title}>PROJECT STATUS</h2>

<div className={styles.filterRow}>
Expand All @@ -108,9 +97,8 @@ export default function ProjectStatusDonutChart() {
</div>

<div className={styles.chartWrapper}>
{/* Only draw the ring if at least one status has data */}
{!allZero && (
<ResponsiveContainer width="100%" aspect={1}>
<ResponsiveContainer width="100%" height={350}>
<PieChart margin={{ top: 10, right: 10, bottom: 40, left: 10 }}>
<Pie
data={pieData}
Expand Down Expand Up @@ -142,7 +130,20 @@ export default function ProjectStatusDonutChart() {
/>
</Pie>

<Tooltip />
<Tooltip
contentStyle={{
backgroundColor: darkMode ? '#2a3f5f' : '#ffffff',
borderRadius: '8px',
border: darkMode ? '1px solid #3a506b' : '1px solid #e5e7eb',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.3)',
color: darkMode ? '#ffffff' : '#111827',
}}
itemStyle={{
color: darkMode ? '#ffffff' : '#111827',
fontWeight: '600',
textTransform: 'capitalize',
}}
/>
<Legend verticalAlign="bottom" align="center" />
</PieChart>
</ResponsiveContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
border-radius: 12px;
position: relative;
border: 1px solid rgba(0, 0, 0, 0.12);
transition: background-color 0.3s, color 0.3s;
}

.title {
Expand Down Expand Up @@ -78,6 +79,7 @@
text-align: center;
}
}

.centerLabel {
font-size: 16px;
font-weight: 600;
Expand All @@ -94,4 +96,29 @@
text-align: center;
margin-top: 16px;
font-weight: 500;
}

.darkContainer {
background-color: #1b2a41 !important;
color: #ffffff !important;
border-color: #3a506b !important;
}

.darkContainer input[type="date"] {
background-color: #2a3f5f !important;
color: #ffffff !important;
border: 1px solid #3a506b !important;
color-scheme: dark;
padding: 4px 8px;
border-radius: 4px;
}

.darkContainer .label {
color: #b5bac5 !important;
}

.darkContainer .title,
.darkContainer h3,
.darkContainer .noDataMessage {
color: #ffffff !important;
}
87 changes: 73 additions & 14 deletions src/components/BMDashboard/Projects/ProjectsList.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { Row, Col } from 'reactstrap';
import { Row, Col, FormGroup, Label, Spinner } from 'reactstrap';
import Select from 'react-select';
import ProjectSummary from './ProjectSummary';
import styles from '../BMDashboard.module.css';

function ProjectsList() {
const projects = useSelector(state => state.bmProjects) || [];
const darkMode = useSelector(state => state.theme?.darkMode || false);

const [selectedProjects, setSelectedProjects] = useState([]);
const [isUpdating, setIsUpdating] = useState(false);

const timeoutRef = useRef(null);

useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, []);

const projectOptions = projects.map(project => ({
value: project._id,
label: project.name,
}));

const handleSelectChange = selectedOptions => {
setSelectedProjects(selectedOptions || []);
setIsUpdating(true);

timeoutRef.current = setTimeout(() => {
setSelectedProjects(selectedOptions || []);
setIsUpdating(false);
}, 600);
};

const filteredProjects = selectedProjects.length
? projects.filter(project => selectedProjects.some(selected => selected.value === project._id))
: projects;

// Custom styles for react-select in dark mode
const selectStyles = {
control: (base, state) => ({
...base,
Expand Down Expand Up @@ -105,34 +120,78 @@ function ProjectsList() {
className="ml-0 text-center mt-5"
style={{ width: '100%', display: 'flex', justifyContent: 'center', flexDirection: 'column' }}
>
<Col md="8" lg="6" className="mb-3" style={{ margin: '0 auto' }}>
<Select
isMulti
options={projectOptions}
onChange={handleSelectChange}
placeholder="Select Projects"
styles={selectStyles}
classNamePrefix="react-select"
/>
<Col md="8" lg="6" className="mb-4" style={{ margin: '0 auto' }}>
<FormGroup className="text-left" style={{ textAlign: 'left' }}>
<Label
className={darkMode ? 'text-light' : ''}
style={{ fontWeight: '600', fontSize: '1.1rem', marginBottom: '0.5rem' }}
>
Filter Building Summaries
</Label>

<Select
isMulti
isDisabled={isUpdating}
options={projectOptions}
onChange={handleSelectChange}
placeholder="Select a project to view summary details."
styles={selectStyles}
classNamePrefix="react-select"
value={selectedProjects}
/>

<small className={`form-text ${darkMode ? 'text-light opacity-75' : 'text-muted'} mt-2`}>
Select one or multiple projects from the dropdown to filter the inventory and building
data below.
</small>
</FormGroup>
</Col>

<Col xs="12">
<div
className="d-flex align-items-center justify-content-center mb-3"
style={{ minHeight: '40px' }}
>
<h3 className={`m-0 ${darkMode ? 'text-light' : ''}`} style={{ fontSize: '1.4rem' }}>
{selectedProjects.length > 0
? `Showing Summary for: ${selectedProjects.map(p => p.label).join(', ')}`
: 'Showing All Projects'}
</h3>
{isUpdating && (
<Spinner size="sm" color="primary" className="ml-3" style={{ marginLeft: '15px' }} />
)}
</div>

{filteredProjects.length ? (
<ul
className={`${styles.projectsList} ${
darkMode ? styles.darkProjectsList : styles.lightProjectsList
}`}
style={{
opacity: isUpdating ? 0.6 : 1,
transition: 'opacity 0.2s ease',
listStyleType: 'none',
paddingLeft: 0,
margin: '0 auto',
}}
>
{filteredProjects.map(project => (
<li
className={`${darkMode ? styles.darkProjectSummary : styles.projectSummary}`}
key={project._id}
style={{
listStyleType: 'none',
marginBottom: '3rem',
}}
>
<ProjectSummary project={project} />
</li>
))}
</ul>
) : (
<p className={darkMode ? 'text-light' : ''}>No projects data</p>
<p className={darkMode ? 'text-light mt-4' : 'mt-4'}>
No projects data available for the selected filters.
</p>
)}
</Col>
</Row>
Expand Down
Loading