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
54 changes: 54 additions & 0 deletions src/components/Analytics/AnalyticsDashboard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState } from 'react';
import { useSelector } from 'react-redux';
import ReviewersRequirementChart from './ReviewersRequirementChart';
import styles from './AnalyticsDashboard.module.css';

const durationOptions = [
{ label: 'Last Week', value: 'lastWeek' },
{ label: 'Last 2 weeks', value: 'last2weeks' },
{ label: 'Last Month', value: 'lastMonth' },
{ label: 'All Time', value: 'allTime' },
];

const AnalyticsDashboard = () => {
const darkMode = useSelector(state => state.theme.darkMode);
const [duration, setDuration] = useState('lastWeek');

return (
<div className={`${styles.page} ${darkMode ? styles.pageDark : ''}`}>
<div className={styles.header}>
<h2 className={styles.title}>Reviewers Ranked by Requirement Satisfied</h2>

<div className={styles.filterGroup}>
<label className={styles.filterLabel} htmlFor="duration">
Duration:
</label>
<select
id="duration"
className={styles.filterSelect}
value={duration}
onChange={e => setDuration(e.target.value)}
>
{durationOptions.map(opt => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
</div>

<p className={styles.helperText}>
Shows how many PR reviews each reviewer completed in the selected period, grouped by review
quality. The list length comes from the backend for that duration (not a fixed frontend
limit).
</p>

<div className={styles.chartCard}>
<ReviewersRequirementChart duration={duration} darkMode={darkMode} />
</div>
</div>
);
};

export default AnalyticsDashboard;
129 changes: 129 additions & 0 deletions src/components/Analytics/AnalyticsDashboard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
.page {
width: 100%;
max-width: 1400px;
margin: 0 auto;
padding: 1.5rem 1.25rem 2.5rem;
box-sizing: border-box;
min-height: calc(100vh - 120px);
}

.header {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.25rem;
}

.title {
margin: 0;
font-size: clamp(1.25rem, 2.5vw, 1.75rem);
font-weight: 600;
line-height: 1.3;
color: #052c65;
flex: 1 1 280px;
}

.filterGroup {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 0 0 auto;
}

.filterLabel {
margin: 0;
font-weight: 500;
color: #1f2937;
white-space: nowrap;
}

.filterSelect {
min-width: 160px;
padding: 0.45rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
background: #fff;
color: #111827;
font-size: 0.95rem;
}

.chartCard {
width: 100%;
border: 1px solid #e5e7eb;
border-radius: 10px;
background: #fff;
padding: 1rem 0.75rem 1.25rem;
box-sizing: border-box;
overflow-x: auto;
}

.helperText {
margin: 0 0 1rem;
font-size: 0.9rem;
color: #4b5563;
}

.statusMessage {
display: grid;
place-items: center;
min-height: 220px;
margin: 0;
text-align: center;
color: #374151;
font-size: 1rem;
}

/* Dark mode */
.pageDark {
color: #f8fafc;
}

.pageDark .title {
color: #e2e8f0;
}

.pageDark .filterLabel {
color: #e2e8f0;
}

.pageDark .filterSelect {
background: #2d3748;
border-color: #4a5568;
color: #f8fafc;
}

.pageDark .chartCard {
background: #1a202c;
border-color: #4a5568;
}

.pageDark .helperText,
.pageDark .statusMessage {
color: #cbd5e1;
}

@media (width <= 768px) {
.page {
padding: 1rem 0.75rem 2rem;
}

.header {
flex-direction: column;
align-items: stretch;
}

.filterGroup {
width: 100%;
}

.filterSelect {
flex: 1;
min-width: 0;
}

.chartCard {
padding: 0.75rem 0.25rem 1rem;
}
}
66 changes: 66 additions & 0 deletions src/components/Analytics/PopularPRChart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { Bar, BarChart, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';

const data = [
{ prNumber: 'PR1234', reviews: 10 },
{ prNumber: 'PR1235', reviews: 8 },
{ prNumber: 'PR1236', reviews: 15 },
{ prNumber: 'PR1237', reviews: 5 },
{ prNumber: 'PR1238', reviews: 12 },
{ prNumber: 'PR1231', reviews: 12 },
{ prNumber: 'PR1256', reviews: 17 },
{ prNumber: 'PR1255', reviews: 19 },
{ prNumber: 'PR1254', reviews: 1 },
{ prNumber: 'PR1253', reviews: 2 },
{ prNumber: 'PR1252', reviews: 7 },
{ prNumber: 'PR1251', reviews: 9 },
{ prNumber: 'PR1250', reviews: 11 },
{ prNumber: 'PR1249', reviews: 19 },
{ prNumber: 'PR1248', reviews: 0 },
{ prNumber: 'PR1242', reviews: 2 },
{ prNumber: 'PR1241', reviews: 50 },
];

const PopularPRChart = () => {
const filteredData = data;

return (
<div style={{ width: '100%', height: 500, overflowY: 'auto' }}>
<ResponsiveContainer width="100%" height={400}>
<BarChart
data={filteredData}
layout="vertical"
margin={{ top: 20, right: 30, left: 150, bottom: 40 }}
>
<XAxis
type="number"
label={{
value: 'Number of Reviews',
position: 'insideBottom',
offset: -5,
}}
/>

<YAxis
dataKey="prNumber"
type="category"
label={{
value: 'PR Numbers',
angle: -90,
position: 'insideLeft',
}}
width={100}
/>

<Tooltip />

<Bar dataKey="reviews" fill="#052C65">
<LabelList dataKey="reviews" position="right" />
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
);
};

export default PopularPRChart;
Loading
Loading