From 9e8aa8b00d6205f3ff1ea1b48c1dca1f67053959 Mon Sep 17 00:00:00 2001 From: arpit2006 Date: Mon, 1 Jun 2026 18:40:09 +0530 Subject: [PATCH] Add active days analytics flow --- src/components/Dashboard.tsx | 174 +++++++++++++------ src/components/__test__/Dashboard.test.tsx | 23 +++ src/pages/Tracker/Tracker.tsx | 190 +++++++++++++++++---- 3 files changed, 303 insertions(+), 84 deletions(-) create mode 100644 src/components/__test__/Dashboard.test.tsx diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index e28358f0..5a11f46d 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -31,8 +31,23 @@ interface DashboardProps { theme: Theme; } +export const getTotalActiveDays = (issues: GitHubItem[], prs: GitHubItem[]) => { + const activeDays = new Set(); + + [...issues, ...prs].forEach((item) => { + const activityDate = item?.created_at?.slice(0, 10); + + if (activityDate) { + activeDays.add(activityDate); + } + }); + + return activeDays.size; +}; + const Dashboard: React.FC = ({ totalIssues, totalPrs, data, theme }) => { - + const totalContributions = totalIssues + totalPrs; + // Data for Pie Chart const pieData = [ { name: 'Issues', value: totalIssues }, @@ -64,75 +79,124 @@ const Dashboard: React.FC = ({ totalIssues, totalPrs, data, them const hasData = totalIssues > 0 || totalPrs > 0; - if (!hasData) { - return ( - - - No data available. Enter a username to view analytics. + const renderChartCard = ( + title: string, + subtitle: string, + content: React.ReactNode + ) => ( + + + + {title} - - ); - } + + {subtitle} + + + + + {content} + + + ); return ( - - {/* Pie Chart: Issues vs PRs */} + {!hasData && ( + + + No data available. Enter a username to view analytics. + + + )} + + - - - Contribution Mix (Total) - - - - - {pieData.map((_entry, index) => ( - - ))} - - - - - - + {renderChartCard( + 'Contribution Mix', + `Total activity across ${totalContributions} items`, + + + + + + {pieData.map((_entry, index) => ( + + ))} + + + + + + + + + + + Issues + + + {totalIssues} + + + + + Pull Requests + + + {totalPrs} + + + + + )} - {/* Bar Chart: Activity by Repository */} - - - Top Repositories (Current View) - - {barData.length > 0 ? ( - - - - - - 0 ? ( + + + + + + - + ) : ( - No repository data found in this view. + No repository data found in this view. - )} - + ) + )} diff --git a/src/components/__test__/Dashboard.test.tsx b/src/components/__test__/Dashboard.test.tsx new file mode 100644 index 00000000..6750b45c --- /dev/null +++ b/src/components/__test__/Dashboard.test.tsx @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest'; +import { getTotalActiveDays } from '../Dashboard'; + +describe('getTotalActiveDays', () => { + it('counts unique YYYY-MM-DD values across issues and pull requests', () => { + const issues = [ + { created_at: '2026-05-01T12:00:00Z' }, + { created_at: '2026-05-01T18:30:00Z' }, + { created_at: '2026-05-02T09:15:00Z' }, + ]; + + const prs = [ + { created_at: '2026-05-02T20:45:00Z' }, + { created_at: '2026-05-03T08:00:00Z' }, + ]; + + expect(getTotalActiveDays(issues as never[], prs as never[])).toBe(3); + }); + + it('returns 0 for empty datasets', () => { + expect(getTotalActiveDays([], [])).toBe(0); + }); +}); \ No newline at end of file diff --git a/src/pages/Tracker/Tracker.tsx b/src/pages/Tracker/Tracker.tsx index 576f39bf..d31df33b 100644 --- a/src/pages/Tracker/Tracker.tsx +++ b/src/pages/Tracker/Tracker.tsx @@ -28,13 +28,16 @@ import { MenuItem, FormControl, InputLabel, + Typography, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { useGitHubAuth } from "../../hooks/useGitHubAuth"; import { useGitHubData } from "../../hooks/useGitHubData"; +import Dashboard from "../../components/Dashboard"; import { KeyIcon } from "lucide-react"; const ROWS_PER_PAGE = 10; +const ACTIVE_DAYS_ROWS_PER_PAGE = 8; interface GitHubItem { id: number; @@ -71,6 +74,8 @@ const Home: React.FC = () => { const [tab, setTab] = useState(0); const [page, setPage] = useState(0); + const [activeDaysPage, setActiveDaysPage] = useState(0); + const [hasFetchedData, setHasFetchedData] = useState(false); const [issueFilter, setIssueFilter] = useState("all"); const [prFilter, setPrFilter] = useState("all"); @@ -89,6 +94,7 @@ const Home: React.FC = () => { const handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); setPage(0); + setHasFetchedData(true); fetchData(username, 1, ROWS_PER_PAGE); }; @@ -157,11 +163,41 @@ const Home: React.FC = () => { return ; }; - // Current data and filtered data according to tab and filters - const currentRawData = tab === 0 ? issues : prs; - const currentFilteredData = filterData(currentRawData, tab === 0 ? issueFilter : prFilter); + const filteredIssues = filterData(issues, issueFilter); + const filteredPrs = filterData(prs, prFilter); + const currentFilteredData = tab === 0 ? filteredIssues : filteredPrs; const totalCount = tab === 0 ? totalIssues : totalPrs; + const showStateFilter = tab !== 2; + const isActiveDaysTab = tab === 2; + const activeDaysData = Array.from( + new Set( + [...filteredIssues, ...filteredPrs] + .map((item) => item.created_at?.slice(0, 10)) + .filter((date): date is string => Boolean(date)) + ) + ) + .map((date) => ({ + date, + count: [...filteredIssues, ...filteredPrs].filter( + (item) => item.created_at?.slice(0, 10) === date + ).length, + })) + .sort((a, b) => b.date.localeCompare(a.date)); + const totalActiveDays = activeDaysData.length; + const activeDaysPageData = activeDaysData.slice( + activeDaysPage * ACTIVE_DAYS_ROWS_PER_PAGE, + activeDaysPage * ACTIVE_DAYS_ROWS_PER_PAGE + ACTIVE_DAYS_ROWS_PER_PAGE + ); + + useEffect(() => { + if ( + activeDaysPage > 0 && + activeDaysPage * ACTIVE_DAYS_ROWS_PER_PAGE >= activeDaysData.length + ) { + setActiveDaysPage(0); + } + }, [activeDaysData.length, activeDaysPage]); return ( @@ -242,6 +278,15 @@ const Home: React.FC = () => { + {hasFetchedData && ( + + )} + {/* Filters */} { onChange={(_, v) => { setTab(v); setPage(0); + setActiveDaysPage(0); }} sx={{ flex: 1 }} > + - - State - - + {showStateFilter && ( + + State + + + )} {(authError || dataError) && ( @@ -334,6 +383,89 @@ const Home: React.FC = () => { + ) : isActiveDaysTab ? ( + + + + + + Analytics Snapshot + + + Total Active Days + + + Unique calendar days with at least one issue or pull request activity. + + + + + {totalActiveDays} + + + + + + + + + Date + Activities + + + + + {activeDaysPageData.length > 0 ? ( + activeDaysPageData.map((item) => ( + + {item.date} + {item.count} + + )) + ) : ( + + + No active days found for the current filters. + + + )} + +
+ + setActiveDaysPage(newPage)} + rowsPerPage={ACTIVE_DAYS_ROWS_PER_PAGE} + rowsPerPageOptions={[ACTIVE_DAYS_ROWS_PER_PAGE]} + /> +
+
) : (