From 5cd70a903a307df39627891f2b6688df9d4c3d3c Mon Sep 17 00:00:00 2001 From: ShravyaKudlu Date: Tue, 19 Aug 2025 14:28:13 -0400 Subject: [PATCH 01/10] Refactor of jsx and css complete --- .../ReviewersStackedBarChart.jsx | 354 ++++++++++++++++++ .../ReviewersStackedBarChart.module.css | 177 +++++++++ .../reviewersMockData.js | 124 ++++++ src/routes.jsx | 7 +- 4 files changed, 661 insertions(+), 1 deletion(-) create mode 100644 src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx create mode 100644 src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.module.css create mode 100644 src/components/HGNPRDashboard/ReviewersStackedBarChart/reviewersMockData.js diff --git a/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx new file mode 100644 index 0000000000..7cc90db69e --- /dev/null +++ b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx @@ -0,0 +1,354 @@ +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +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', +}; + +const textDark = '#f8fafc'; +const axisLine = '#bfc7d1'; + +// 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'], + })); + +let transformed = []; + +// Custom tick components for Y-axis +function CustomYAxisTick({ x, y, payload }) { + const currentReviewer = payload.value; + const isMentor = transformed.find(d => d.reviewer === currentReviewer)?.isMentor; + return ( + + {payload.value} + + ); +} + +// Custom X-axis tick component +function CustomXAxisTick(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 }) { + if (!active || !payload || !payload.length) return null; + + return ( +
+

{label}

+ {payload.map(entry => ( +
+ + {entry.name}: + + {entry.value} +
+ ))} +
+ ); +} + +// Custom label for each bar segment +function CustomLabel({ x, y, width, height, value }) { + // Don't show zero values + if (value === 0) return null; + + return ( + + {value} + + ); +} + +// 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 }; + } +} + +function ReviewersStackedBarChart() { + const darkMode = useSelector(state => state.theme.darkMode); + const [teamFilter, setTeamFilter] = useState('All'); + const [durationFilter, setDurationFilter] = useState({ label: 'Last Week', value: 'Last Week' }); + const [sortFilter, setSortFilter] = useState('Ascending'); + const { start: filterStartDate, end: filterEndDate } = getDateRange(durationFilter.value); + const [reviewerData, setReviewerData] = useState([]); + const [teams, setTeams] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Simulated API call + const fetchData = () => { + setLoading(true); + setError(null); + + setTimeout(() => { + try { + let filtered = [...MOCK_REVIEWERS_DATA]; + + if (teamFilter !== 'All') { + filtered = filtered.filter(item => item.team === teamFilter); + } + + if (durationFilter.value !== 'All Time') { + filtered = filtered.filter(item => { + const prDate = new Date(item.prSubmittedDate); + return prDate >= filterStartDate && prDate <= filterEndDate; + }); + } + + transformed = transformData(filtered); + + transformed.sort((a, b) => { + const totalA = a.Exceptional + a.Sufficient + a['Needs Changes'] + a['Did Not Review']; + const totalB = b.Exceptional + b.Sufficient + b['Needs Changes'] + b['Did Not Review']; + + return sortFilter === 'Ascending' ? totalA - totalB : totalB - totalA; + }); + + setReviewerData(transformed); + setLoading(false); + } catch (err) { + setError('Failed to fetch reviewer data.'); + setLoading(false); + } + }, 1000); + }; + + const { domain, ticks } = CustomXAxisTick(transformed); + + // Always use light mode colors for the chart itself + const axisLineColor = axisLine; // always #bfc7d1 + + useEffect(() => { + setTeams(MOCK_TEAMS); + fetchData(); + }, []); + + useEffect(() => { + fetchData(); + }, [teamFilter, durationFilter, sortFilter]); + + return ( +
+

PR Quality by Reviewers

+ +
+
+ + setSortFilter(selected.value)} + className={`${styles.reviewersSelectContainer}`} + classNamePrefix="reviewersSelect" + /> +
+
+ + ({ label: team, value: team }))} value={{ label: teamFilter, value: teamFilter }} onChange={selected => setTeamFilter(selected.value)} - className={`${styles.reviewersSelectContainer} ${darkMode ? styles.darkMode : ''}`} + className={`${styles.reviewersSelectContainer}`} classNamePrefix="reviewersSelect" />
-
{loading ? ( -
-
-

- Loading Reviewers data... -

+
+
+

Loading Reviewers data...

) : error ? ( -
+
⚠️
-

{error}

+

{error}

- ) : reviewerData.length === 0 ? ( -
+ ) : transformedData.length === 0 ? ( +
📊
-

No PR data available

+

No PR data available

) : (
- - + + - - } width={200}> + } + /> + } + width={200} + stroke={darkMode ? 'white' : 'black'} + > } + cursor={{ className: `${styles.chartCursor}` }} + content={} /> - + LegendFormatter(value, entry, darkMode)} /> {Object.keys(COLORS).map(key => ( } /> diff --git a/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.module.css b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.module.css index 0725f2ff31..702a1bb371 100644 --- a/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.module.css +++ b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.module.css @@ -116,25 +116,25 @@ color: #f8fafc; } -.darkMode .reviewers-select__menu { +.darkMode .reviewersSelect__menu { background-color: #1b2a41; color: #f8fafc; border: 1px solid white; } -.darkMode .reviewersSelect__single-value { +.darkMode .reviewersSelect__singleValue { color: #f8fafc; } /* Light mode option hover and focus */ -.reviewersSelect__option--is-focused, +.reviewersSelect__option--isFocused, .reviewersSelect__option:hover { background-color: #eef0f2; color: #000; } /* Dark mode option hover and focus */ -.darkMode .reviewersSelect__option--is-focused, +.darkMode .reviewersSelect__option--isFocused, .darkMode .reviewersSelect__option:hover { background-color: #2f4a73; color: #fff; @@ -184,3 +184,127 @@ font-size: 0.85rem; } } + +.reviewerStackbarLoading { + display: grid; + place-items: center; + height: 200px; + font-size: 16px; + color: #000; + text-align: center; +} + +.loadingSpinner { + width: 50px; + height: 50px; + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.darkMode .reviewerStackbarLoading { + color: #f8fafc; /* Dark mode text color */ +} + +.darkMode .loadingSpinner { + border-top: 4px solid #f8fafc; +} + +.darkMode .reviewerStackbarLoading p { + color: #f8fafc; +} +.reviewersStackbarError { + display: grid; + place-items: center; + height: 200px; + font-size: 16px; + color: #000; + text-align: center; +} + +.darkMode .reviewersStackbarError { + color: #f8fafc; +} + +.darkMode .reviewersStackbarError p { + color: #f8fafc; +} + +.retryButton { + background-color: transparent; + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + font-weight: 500; + transition: background-color 0.2s ease; + margin-top: 10px; +} + +.retryButton:hover { + background-color: #f0f0f0; +} + +.darkMode .retryButton { + color: #f8fafc; + border-color: #f8fafc; +} + +.darkMode .retryButton:hover { + background-color: #324d77; +} +.reviewerDataEmpty { + display: grid; + place-items: center; + height: 200px; + font-size: 16px; + color: #000; + text-align: center; +} + +.darkMode .reviewerDataEmpty { + color: #f8fafc; +} + +.darkMode .reviewerDataEmpty p { + color: #f8fafc; +} + +.emptyIcon { + font-size: 2rem; +} + +.chartGrid { + stroke: #bfc7d1; + stroke-dasharray: 3 3; +} + +.darkMode .chartGrid { + stroke: #444; +} + +.chartCursor { + fill: #e0e0e0; +} + +.darkMode .chartCursor { + fill: rgb(50, 73, 105); +} + +.chartLegend { + color: black; +} + +.darkMode .chartLegend { + color: white; +} From 0b819d7c5f20f015634c4570db5984411c51c46b Mon Sep 17 00:00:00 2001 From: ShravyaKudlu Date: Sat, 25 Oct 2025 18:39:52 -0400 Subject: [PATCH 04/10] Fix: inline sonarcube --- .../ReviewersStackedBarChart.jsx | 24 +++++++------------ .../ReviewersStackedBarChart.module.css | 1 + 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx index d6d6da6797..0f2ca385e1 100644 --- a/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx +++ b/src/components/HGNPRDashboard/ReviewersStackedBarChart/ReviewersStackedBarChart.jsx @@ -209,7 +209,7 @@ function ReviewersStackedBarChart() { setError('Failed to fetch reviewer data.'); setLoading(false); } - }, 1000); + }, 1001); }; const { domain, ticks } = CustomXAxisTicks(transformedData); @@ -224,14 +224,8 @@ function ReviewersStackedBarChart() { }, [teamFilter, durationFilter, sortFilter]); return ( -
-

PR Quality by Reviewers

+
+

PR Quality by Reviewers

@@ -271,7 +265,7 @@ function ReviewersStackedBarChart() { id="durationSelect" options={[ { label: 'Last Week', value: 'Last Week' }, - { label: 'Last 2 weeks', value: 'Last 2 weeks' }, + { label: 'Last 3 weeks', value: 'Last 2 weeks' }, { label: 'Last Month', value: 'Last Month' }, { label: 'All Time', value: 'All Time' }, ]} @@ -300,19 +294,19 @@ function ReviewersStackedBarChart() { Retry
- ) : transformedData.length === 0 ? ( + ) : transformedData.length === 1 ? (
📊

No PR data available

) : (
- + } - width={200} + width={201} stroke={darkMode ? 'white' : 'black'} >