diff --git a/src/Routes/Router.tsx b/src/Routes/Router.tsx index 40a7861..c08dee8 100644 --- a/src/Routes/Router.tsx +++ b/src/Routes/Router.tsx @@ -7,6 +7,7 @@ import Signup from "../pages/Signup/Signup.tsx"; import Login from "../pages/Login/Login.tsx"; import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx"; import Home from "../pages/Home/Home.tsx"; +import Issues from "../pages/Issues/Issues.tsx"; const Router = () => { return ( @@ -19,6 +20,7 @@ const Router = () => { } /> } /> } /> + } /> ); }; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 8272f7c..7592909 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -33,6 +33,7 @@ const Navbar: React.FC = () => {
Home Tracker + Issues Contributors
@@ -82,6 +83,7 @@ const Navbar: React.FC = () => {
setIsOpen(false)}>Home setIsOpen(false)}>Tracker + setIsOpen(false)}>Issues setIsOpen(false)}>Contributors
@@ -98,4 +100,4 @@ const MobileNavLink = ({ to, children, onClick }: { to: string, children: React. `block px-6 py-4 rounded-2xl text-xl font-bold transition-all ${isActive ? "bg-blue-600 text-white shadow-lg translate-x-2" : "text-slate-600 dark:text-gray-400 hover:bg-white/40 dark:hover:bg-gray-800/40"}`}>{children} ); -export default Navbar; \ No newline at end of file +export default Navbar; diff --git a/src/pages/Issues/Issues.tsx b/src/pages/Issues/Issues.tsx new file mode 100644 index 0000000..09ca3ff --- /dev/null +++ b/src/pages/Issues/Issues.tsx @@ -0,0 +1,237 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { + Container, + Box, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TablePagination, + Link, + CircularProgress, + Alert, + FormControl, + InputLabel, + Select, + MenuItem, + Typography, + SelectChangeEvent, +} from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { IssueOpenedIcon, IssueClosedIcon } from "@primer/octicons-react"; + +const ROWS_PER_PAGE = 10; + +interface IssueItem { + id: number; + title: string; + state: string; + created_at: string; + repository_url: string; + html_url: string; +} + +const Issues: React.FC = () => { + const theme = useTheme(); + + const [issues, setIssues] = useState([]); + const [totalIssues, setTotalIssues] = useState(0); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [page, setPage] = useState(0); + + const [language, setLanguage] = useState(""); + const [tag, setTag] = useState(""); + const [sortOrder, setSortOrder] = useState("desc"); + + const fetchIssues = useCallback(async (currentPage: number, currentLanguage: string, currentTag: string, currentOrder: string) => { + setLoading(true); + setError(""); + + try { + let q = "is:issue is:open"; + if (currentLanguage) { + q += ` language:${currentLanguage}`; + } + if (currentTag) { + q += ` label:"${currentTag}"`; + } + + const response = await fetch( + `https://api.github.com/search/issues?q=${encodeURIComponent(q)}&sort=created&order=${currentOrder}&per_page=${ROWS_PER_PAGE}&page=${currentPage + 1}` + ); + + if (!response.ok) { + if (response.status === 403) { + throw new Error("GitHub API rate limit exceeded."); + } + throw new Error("Failed to fetch data"); + } + + const data = await response.json(); + setIssues(data.items); + setTotalIssues(data.total_count > 1000 ? 1000 : data.total_count); // GitHub limits search results to 1000 + } catch (err: unknown) { + if (err instanceof Error) { + setError(err.message || "Failed to fetch issues"); + } else { + setError("Failed to fetch issues"); + } + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchIssues(page, language, tag, sortOrder); + }, [page, language, tag, sortOrder, fetchIssues]); + + const handlePageChange = (_: unknown, newPage: number) => { + setPage(newPage); + }; + + const formatDate = (dateString: string): string => + new Date(dateString).toLocaleDateString(); + + return ( + + + Explore GitHub Issues + + + + + + Language + + + + + Tags / Labels + + + + + Sort by Time + + + + + + {error && ( + + {error} + + )} + + {loading ? ( + + + + ) : ( + + + + + + Title + Repository + State + Created + + + + {issues.map((item) => ( + + + {item.state === "closed" ? ( + + ) : ( + + )} + + {item.title} + + + + {item.repository_url.split("/").slice(-2).join("/")} + + {item.state} + {formatDate(item.created_at)} + + ))} + {issues.length === 0 && !loading && !error && ( + + + No issues found. + + + )} + +
+ +
+
+ )} +
+ ); +}; + +export default Issues;