From 9a0970f2c3004e581a6d67b7fdd5eff7182c462d Mon Sep 17 00:00:00 2001 From: Swati Kanwar Date: Tue, 19 May 2026 21:13:38 +0530 Subject: [PATCH 1/2] =?UTF-8?q?feat(tracker):=20enhance=20UI=20=E2=80=94?= =?UTF-8?q?=20improved=20header,=20filters,=20and=20dark-mode=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Tracker/Tracker.tsx | 842 ++++++++++++++++++++++++---------- 1 file changed, 600 insertions(+), 242 deletions(-) diff --git a/src/pages/Tracker/Tracker.tsx b/src/pages/Tracker/Tracker.tsx index ce4116f..3dbda9a 100644 --- a/src/pages/Tracker/Tracker.tsx +++ b/src/pages/Tracker/Tracker.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react" +import React, { useState, useEffect, useMemo } from "react" import { IssueOpenedIcon, IssueClosedIcon, @@ -20,6 +20,7 @@ import { TableRow, TablePagination, Link, + CircularProgress, Alert, Tabs, Tab, @@ -27,9 +28,21 @@ import { MenuItem, FormControl, InputLabel, - Skeleton, Typography, + Chip, + Divider, + InputAdornment, + Tooltip, + IconButton, + Skeleton, + Stack, + LinearProgress, } from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import FilterListIcon from "@mui/icons-material/FilterList"; +import ClearIcon from "@mui/icons-material/Clear"; +import GitHubIcon from "@mui/icons-material/GitHub"; +import CallMadeIcon from "@mui/icons-material/CallMade"; import { useTheme } from "@mui/material/styles"; import { useGitHubAuth } from "../../hooks/useGitHubAuth"; import { useGitHubData } from "../../hooks/useGitHubData"; @@ -46,9 +59,39 @@ interface GitHubItem { html_url: string; } -const Home: React.FC = () => { +// ── Stat Card ──────────────────────────────────────────────────────────────── +const StatCard: React.FC<{ + label: string; + value: number; + color: string; + bg: string; +}> = ({ label, value, color, bg }) => ( + + + {label} + + + {value} + + +); +// ── Main Component ──────────────────────────────────────────────────────────── +const Home: React.FC = () => { const theme = useTheme(); + const isDark = theme.palette.mode === "dark"; const { username, @@ -71,337 +114,652 @@ const Home: React.FC = () => { const [tab, setTab] = useState(0); const [page, setPage] = useState(0); - const [issueFilter, setIssueFilter] = useState("all"); const [prFilter, setPrFilter] = useState("all"); const [searchTitle, setSearchTitle] = useState(""); const [selectedRepo, setSelectedRepo] = useState(""); const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); + const [hasFetched, setHasFetched] = useState(false); - // Fetch data when username, tab, or page changes useEffect(() => { - if (username) { - fetchData(username, page + 1, ROWS_PER_PAGE); - } + if (username) fetchData(username, page + 1, ROWS_PER_PAGE); }, [tab, page]); const handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); setPage(0); + setHasFetched(true); fetchData(username, 1, ROWS_PER_PAGE); }; - const handlePageChange = (_: unknown, newPage: number) => { - setPage(newPage); + const handleClearFilters = () => { + setSearchTitle(""); + setSelectedRepo(""); + setStartDate(""); + setEndDate(""); + setIssueFilter("all"); + setPrFilter("all"); }; + const hasActiveFilters = + !!searchTitle || !!selectedRepo || !!startDate || !!endDate || + issueFilter !== "all" || prFilter !== "all"; + + const handlePageChange = (_: unknown, newPage: number) => setPage(newPage); + const formatDate = (dateString: string): string => - new Date(dateString).toLocaleDateString(); + new Date(dateString).toLocaleDateString("en-US", { + year: "numeric", month: "short", day: "numeric", + }); const filterData = (data: GitHubItem[], filterType: string): GitHubItem[] => { let filtered = [...data]; if (["open", "closed", "merged"].includes(filterType)) { filtered = filtered.filter((item) => { - if (filterType === "merged") { - return !!item.pull_request?.merged_at - } - else if (filterType === "closed") { - return item.state === "closed" && !item.pull_request?.merged_at - } - else { - //open - return item.state === "open" - } + if (filterType === "merged") return !!item.pull_request?.merged_at; + if (filterType === "closed") return item.state === "closed" && !item.pull_request?.merged_at; + return item.state === "open"; }); } - if (searchTitle) { - filtered = filtered.filter((item) => - item.title.toLowerCase().includes(searchTitle.toLowerCase()) - ); - } - if (selectedRepo) { - filtered = filtered.filter((item) => - item.repository_url.includes(selectedRepo) - ); - } - if (startDate) { - filtered = filtered.filter( - (item) => new Date(item.created_at) >= new Date(startDate) - ); - } - if (endDate) { - filtered = filtered.filter( - (item) => new Date(item.created_at) <= new Date(endDate) - ); - } + if (searchTitle) filtered = filtered.filter((i) => i.title.toLowerCase().includes(searchTitle.toLowerCase())); + if (selectedRepo) filtered = filtered.filter((i) => i.repository_url.includes(selectedRepo)); + if (startDate) filtered = filtered.filter((i) => new Date(i.created_at) >= new Date(startDate)); + if (endDate) filtered = filtered.filter((i) => new Date(i.created_at) <= new Date(endDate)); return filtered; }; - const getStatusIcon = (item: GitHubItem) => { - - if (item.pull_request) { + const currentRawData = tab === 0 ? issues : prs; - if (item.pull_request.merged_at) - return ; + const stats = useMemo(() => { + const open = currentRawData.filter((i) => i.state === "open").length; + const merged = currentRawData.filter((i) => !!i.pull_request?.merged_at).length; + const closed = currentRawData.filter((i) => i.state === "closed" && !i.pull_request?.merged_at).length; + return { open, merged, closed }; + }, [currentRawData]); - if (item.state === 'closed') - return ; + const currentFilteredData = filterData(currentRawData, tab === 0 ? issueFilter : prFilter); + const totalCount = tab === 0 ? totalIssues : totalPrs; + const visibleStats = useMemo(() => { + const open = currentFilteredData.filter((i) => i.state === "open").length; + const merged = currentFilteredData.filter((i) => !!i.pull_request?.merged_at).length; + const closed = currentFilteredData.filter((i) => i.state === "closed" && !i.pull_request?.merged_at).length; + return { open, merged, closed }; + }, [currentFilteredData]); + + const activeFilterSummary = useMemo(() => { + const labels: string[] = []; + if (tab === 0 ? issueFilter !== "all" : prFilter !== "all") labels.push("state"); + if (searchTitle) labels.push("title"); + if (selectedRepo) labels.push("repo"); + if (startDate || endDate) labels.push("date"); + return labels; + }, [tab, issueFilter, prFilter, searchTitle, selectedRepo, startDate, endDate]); + + const activeFilterCount = activeFilterSummary.length; + const visibleShare = totalCount ? Math.round((currentFilteredData.length / totalCount) * 100) : 0; + const progressValue = Math.max(8, visibleShare || 0); - return ; + const getStatusIcon = (item: GitHubItem) => { + if (item.pull_request) { + if (item.pull_request.merged_at) return ; + if (item.state === "closed") return ; + return ; } + if (item.state === "closed") return ; + return ; + }; - if (item.state === 'closed') - return ; - - return ; + const getStateChip = (item: GitHubItem) => { + const isMerged = !!item.pull_request?.merged_at; + const state = isMerged ? "merged" : item.state; + const styles: Record = { + open: { bg: isDark ? "#1a3a1a" : "#dafbe1", color: isDark ? "#56d364" : "#1a7f37" }, + closed: { bg: isDark ? "#3a1a1a" : "#fff0f0", color: isDark ? "#f85149" : "#cf222e" }, + merged: { bg: isDark ? "#2a1a4a" : "#ede9fe", color: isDark ? "#a371f7" : "#6e40c9" }, + }; + const s = styles[state] ?? styles["closed"]; + return ( + + ); }; + const cellSx = { + fontSize: "0.82rem", + py: 1.3, + borderBottom: `1px solid ${theme.palette.divider}`, + }; - // 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 totalCount = tab === 0 ? totalIssues : totalPrs; + const surfaceGradient = isDark + ? "radial-gradient(circle at top right, rgba(59,130,246,0.18), transparent 35%), linear-gradient(180deg, rgba(15,23,42,0.92), rgba(15,23,42,0.72))" + : "radial-gradient(circle at top right, rgba(37,99,235,0.14), transparent 34%), linear-gradient(180deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92))"; return ( - - {/* Auth Form */} - + + + + + + + + + GitHub Activity Tracker + + + Review your issues and pull requests in one place with quick filters, clean status badges, + and a layout that stays readable in both light and dark themes. + + + + + + Overview + + + + Loaded items + {currentRawData.length} + + + Visible after filters + {currentFilteredData.length} + + + + Visibility + {visibleShare}% + + + + + + + + + {/* ── Page header ── */} + + + + Activity dashboard + + + + {/* ── Auth card ── */} + + + Authentication + + + Enter a GitHub username and personal access token to load activity securely. +
- + setUsername(e.target.value)} required - sx={{ flex: 1, minWidth: 150 }} + size="small" + sx={{ flex: 1, minWidth: 150, "& .MuiOutlinedInput-root": { borderRadius: "12px" } }} /> setToken(e.target.value)} type="password" - sx={{ flex: 1, minWidth: 150 }} - // Helper link to guide users on generating a GitHub Personal Access Token + required + size="small" + sx={{ flex: 2, minWidth: 200, "& .MuiOutlinedInput-root": { borderRadius: "12px" } }} + InputProps={{ + endAdornment: token ? ( + + + setToken("")} edge="end"> + + + + + ) : null, + }} helperText={ How to generate? } /> - -
- {/* Filters */} - - setSearchTitle(e.target.value)} - sx={{ minWidth: 200 }} - /> - setSelectedRepo(e.target.value)} - sx={{ minWidth: 200 }} - /> - setStartDate(e.target.value)} - InputLabelProps={{ shrink: true }} - sx={{ minWidth: 150 }} - /> - setEndDate(e.target.value)} - InputLabelProps={{ shrink: true }} - sx={{ minWidth: 150 }} - /> - + {/* ── Error alert ── */} + {(authError || dataError) && ( + + {authError || dataError} + + )} - {/* Tabs + State Filter */} - + + + {tab === 1 && ( + + )} + + + )} + + {/* ── Main card ── */} + - { - setTab(v); - setPage(0); + {/* Tabs + state filter */} + - - - - - State - - - - - {(authError || dataError) && ( - - {authError || dataError} - - )} - - {loading ? ( - - - - - - Title - Repository - State - Created - - - - - {[...Array(5)].map((_, index) => ( - - - - - - - - - - - - - - - - - - ))} - -
-
-
-) : !authError && !dataError && currentFilteredData.length === 0 ? ( - - - No Data Found - - - - Try adjusting filters or searching for another GitHub user. - - -) : ( - - + + Issues + + + } + /> + + Pull Requests + + + } + /> + + + + State + + + - + {/* Filter bar */} + + setSearchTitle(e.target.value)} + size="small" + sx={{ minWidth: 180, "& .MuiOutlinedInput-root": { borderRadius: "12px", fontSize: "0.82rem" } }} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + setSelectedRepo(e.target.value)} + size="small" + sx={{ minWidth: 160, "& .MuiOutlinedInput-root": { borderRadius: "12px", fontSize: "0.82rem" } }} + /> + setStartDate(e.target.value)} + size="small" + InputLabelProps={{ shrink: true }} + sx={{ minWidth: 140, "& .MuiOutlinedInput-root": { borderRadius: "12px", fontSize: "0.82rem" } }} + /> + setEndDate(e.target.value)} + size="small" + InputLabelProps={{ shrink: true }} + sx={{ minWidth: 140, "& .MuiOutlinedInput-root": { borderRadius: "12px", fontSize: "0.82rem" } }} + /> + {hasActiveFilters && ( + + + + )} + + {currentFilteredData.length} of {currentRawData.length} shown + {activeFilterCount > 0 ? ` • ${activeFilterCount} filter${activeFilterCount > 1 ? "s" : ""} active` : ""} + + + {/* Content area */} + {loading ? ( + + {[...Array(6)].map((_, i) => ( + + ))} + + ) : !hasFetched ? ( + + + + No data yet + + + Enter your credentials above and click Fetch Data to load a cleaner, more interactive view of your GitHub activity. + + + ) : currentFilteredData.length === 0 ? ( + + + No results match your filters. + + + Try widening the date range, switching the state filter, or searching a shorter title. + + {hasActiveFilters && ( + + )} + + ) : ( + - - Title - Repository - State - Created + {[ + { label: "Title", align: "left" as const }, + { label: "Repository", align: "center" as const }, + { label: "State", align: "center" as const }, + { label: "Created", align: "left" as const }, + ].map(({ label, align }) => ( + + {label} + + ))} - {currentFilteredData.map((item) => ( - - - - {getStatusIcon(item)} + window.open(item.html_url, "_blank", "noopener,noreferrer")} + > + {/* Title */} + + + + {getStatusIcon(item)} + - {item.title} + {item.title} + + - - - {item.repository_url.split("/").slice(-1)[0]} + {/* Repository */} + + - - {item.pull_request?.merged_at ? "merged" : item.state} + {/* State */} + + {getStateChip(item)} - {formatDate(item.created_at)} - + {/* Created */} + + + {formatDate(item.created_at)} + + ))} -
+ { onPageChange={handlePageChange} rowsPerPage={ROWS_PER_PAGE} rowsPerPageOptions={[ROWS_PER_PAGE]} + sx={{ fontSize: "0.78rem", borderTop: "none" }} /> -
- - )} + )} +
); }; From 813580924c48219d875320fc6ad5cd0025dcd04e Mon Sep 17 00:00:00 2001 From: Swati Kanwar <144903933+swati-204@users.noreply.github.com> Date: Wed, 20 May 2026 09:32:27 +0530 Subject: [PATCH 2/2] Update src/pages/Tracker/Tracker.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/pages/Tracker/Tracker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Tracker/Tracker.tsx b/src/pages/Tracker/Tracker.tsx index 3dbda9a..7436325 100644 --- a/src/pages/Tracker/Tracker.tsx +++ b/src/pages/Tracker/Tracker.tsx @@ -164,8 +164,8 @@ const Home: React.FC = () => { } if (searchTitle) filtered = filtered.filter((i) => i.title.toLowerCase().includes(searchTitle.toLowerCase())); if (selectedRepo) filtered = filtered.filter((i) => i.repository_url.includes(selectedRepo)); - if (startDate) filtered = filtered.filter((i) => new Date(i.created_at) >= new Date(startDate)); - if (endDate) filtered = filtered.filter((i) => new Date(i.created_at) <= new Date(endDate)); + if (startDate) filtered = filtered.filter((i) => i.created_at.slice(0, 10) >= startDate); + if (endDate) filtered = filtered.filter((i) => i.created_at.slice(0, 10) <= endDate); return filtered; };