diff --git a/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx new file mode 100644 index 0000000000..15b280c253 --- /dev/null +++ b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx @@ -0,0 +1,593 @@ +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import Select from 'react-select'; +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + Legend, + LabelList, + ResponsiveContainer, + CartesianGrid, + Label, +} from 'recharts'; +import styles from './ReviewersStackedBarChart.module.css'; +import MOCK_REVIEWERS_DATA from './reviewersMockData'; + +// Dummy data +const MOCK_TEAMS = ['All', 'Alpha', 'Beta', 'Gamma', 'Delta']; + +// Chart segment colors +const COLORS = { + Exceptional: '#5D3FD3', + Sufficient: '#28A745', + 'Needs Changes': '#FFC107', + 'Did Not Review': '#DC3545', +}; + +// Transform data to suit Recharts +const transformData = rawData => + rawData.map(item => ({ + reviewer: item.reviewer, + isMentor: item.isMentor, + team: item.team, + Exceptional: item.counts.Exceptional, + Sufficient: item.counts.Sufficient, + 'Needs Changes': item.counts['Needs Changes'], + 'Did Not Review': item.counts['Did Not Review'], + })); + +// Custom tick components for Y-axis +function CustomYAxisTick({ x, y, payload, data, darkMode }) { + const currentReviewer = payload.value; + const isMentor = data.find(d => d.reviewer === currentReviewer)?.isMentor; + const axisColor = darkMode ? 'white' : 'black'; + + return ( + + {payload.value} + + ); +} + +CustomYAxisTick.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + payload: PropTypes.shape({ + value: PropTypes.string, + }), + data: PropTypes.arrayOf( + PropTypes.shape({ + reviewer: PropTypes.string, + isMentor: PropTypes.bool, + }), + ).isRequired, + darkMode: PropTypes.bool.isRequired, +}; + +CustomYAxisTick.defaultProps = { + x: 0, + y: 0, + payload: { value: '' }, +}; + +// Custom X-axis tick component +function CustomXAxisTick({ x, y, payload, darkMode }) { + const textColor = darkMode ? 'white' : 'black'; + return ( + + {payload.value} + + ); +} + +CustomXAxisTick.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + payload: PropTypes.shape({ + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + }), + darkMode: PropTypes.bool.isRequired, +}; + +CustomXAxisTick.defaultProps = { + x: 0, + y: 0, + payload: { value: '' }, +}; + +// Custom X-axis tick component to generate ticks +function CustomXAxisTicks(data) { + const max = Math.max( + ...data.map(d => d.Exceptional + d.Sufficient + d['Needs Changes'] + d['Did Not Review']), + 0, + ); + const upper = Math.ceil(max / 10) * 10; + const ticks = []; + for (let i = 0; i <= upper; i += 10) { + ticks.push(i); + } + return { domain: [0, upper], ticks }; +} + +// Custom tooltip component +function CustomTooltip({ active, payload, label, darkMode }) { + if (!active || !payload?.length) return null; + + return ( +
+
+

{label}

+ {payload.map(entry => ( +
+ + {entry.name}: + + {entry.value} +
+ ))} +
+
+ ); +} + +CustomTooltip.propTypes = { + active: PropTypes.bool, + payload: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string, + value: PropTypes.number, + color: PropTypes.string, + }), + ), + label: PropTypes.string, + darkMode: PropTypes.bool.isRequired, +}; + +CustomTooltip.defaultProps = { + active: false, + payload: null, + label: '', +}; + +// Custom label for each bar segment +function CustomLabel({ x, y, width, height, value }) { + if (value === 0) return null; + + const textColor = '#fff'; + + return ( + + {value} + + ); +} + +CustomLabel.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + width: PropTypes.number, + height: PropTypes.number, + value: PropTypes.number, +}; + +CustomLabel.defaultProps = { + x: 0, + y: 0, + width: 0, + height: 0, + value: 0, +}; + +// Function to get date range based on selected duration +function getDateRange(option) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + switch (option) { + case 'Last Week': { + const end = new Date(today); + end.setDate(end.getDate() - 1); + const start = new Date(end); + start.setDate(start.getDate() - 6); + return { start, end }; + } + case 'Last 2 weeks': { + const end = new Date(today); + end.setDate(end.getDate() - 1); + const start = new Date(end); + start.setDate(start.getDate() - 13); + return { start, end }; + } + case 'Last Month': { + const end = new Date(today); + end.setDate(end.getDate() - 1); + const start = new Date(end.getFullYear(), end.getMonth() - 1, 1); + return { start, end }; + } + case 'All Time': + default: + return { start: null, end: null }; + } +} + +// Custom legend formatter +function LegendFormatter(value, entry, darkMode) { + const textColor = darkMode ? 'white' : 'black'; + return {value}; +} + +// Individual filter components +function TeamFilter({ teams, teamFilter, setTeamFilter }) { + return ( +
+ + setSortFilter(selected.value)} + className={`${styles.reviewersSelectContainer}`} + classNamePrefix="reviewersSelect" + /> +
+ ); +} + +SortFilter.propTypes = { + sortFilter: PropTypes.string.isRequired, + setSortFilter: PropTypes.func.isRequired, +}; + +function DurationFilter({ durationFilter, setDurationFilter }) { + const durationOptions = [ + { label: 'Last Week', value: 'Last Week' }, + { label: 'Last 2 weeks', value: 'Last 2 weeks' }, + { label: 'Last Month', value: 'Last Month' }, + { label: 'All Time', value: 'All Time' }, + ]; + + return ( +
+ +