diff --git a/github-activity-tracker/frontend/src/App.tsx b/github-activity-tracker/frontend/src/App.tsx index 7418c14..98dc7be 100644 --- a/github-activity-tracker/frontend/src/App.tsx +++ b/github-activity-tracker/frontend/src/App.tsx @@ -190,6 +190,8 @@ function App() { startDate={startDate} endDate={endDate} onPeriodChange={handlePeriodChange} + onStartDateChange={setStartDate} + onEndDateChange={setEndDate} /> )} diff --git a/github-activity-tracker/frontend/src/components/CustomPeriodButton.tsx b/github-activity-tracker/frontend/src/components/CustomPeriodButton.tsx new file mode 100644 index 0000000..face92b --- /dev/null +++ b/github-activity-tracker/frontend/src/components/CustomPeriodButton.tsx @@ -0,0 +1,116 @@ +import React, { useEffect, useRef, useState } from "react"; +import type { PeriodValue } from "../lib/periods"; + +interface CustomPeriodButtonProps { + startDate: string; + endDate: string; + onPeriodChange: (p: PeriodValue) => void; + onStartDateChange: (value: string) => void; + onEndDateChange: (value: string) => void; + buttonClassName: string; +} + +const CustomPeriodButton: React.FC = ({ + startDate, + endDate, + onPeriodChange, + onStartDateChange, + onEndDateChange, + buttonClassName, +}) => { + const [showCustomPopup, setShowCustomPopup] = useState(false); + const [draftStart, setDraftStart] = useState(startDate); + const [draftEnd, setDraftEnd] = useState(endDate); + const customPopupRef = useRef(null); + + const toYmd = (date: Date) => + `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; + + const openCustomPopup = () => { + if (startDate && endDate) { + setDraftStart(startDate); + setDraftEnd(endDate); + } else { + const end = new Date(); + const start = new Date(); + start.setDate(end.getDate() - 6); + setDraftStart(toYmd(start)); + setDraftEnd(toYmd(end)); + } + setShowCustomPopup(true); + }; + + const applyCustomRange = () => { + if (!draftStart || !draftEnd || draftStart > draftEnd) return; + onStartDateChange(draftStart); + onEndDateChange(draftEnd); + onPeriodChange("custom"); + setShowCustomPopup(false); + }; + + useEffect(() => { + if (!showCustomPopup) return; + + const handleClickOutside = (event: MouseEvent) => { + if ( + customPopupRef.current && + !customPopupRef.current.contains(event.target as Node) + ) { + setShowCustomPopup(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [showCustomPopup]); + + return ( +
+ + {showCustomPopup && ( +
+

+ Custom date range +

+ + setDraftStart(e.target.value)} + className="themed-date-input w-full mb-3 px-3 py-2 border border-panel-border rounded-lg bg-surface text-brand-ink text-sm" + /> + + setDraftEnd(e.target.value)} + className="themed-date-input w-full mb-4 px-3 py-2 border border-panel-border rounded-lg bg-surface text-brand-ink text-sm" + /> +
+ + +
+
+ )} +
+ ); +}; + +export default CustomPeriodButton; diff --git a/github-activity-tracker/frontend/src/components/TopNav.tsx b/github-activity-tracker/frontend/src/components/TopNav.tsx index 9b8baf9..429666b 100644 --- a/github-activity-tracker/frontend/src/components/TopNav.tsx +++ b/github-activity-tracker/frontend/src/components/TopNav.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useState } from "react"; import { PERIOD_OPTIONS, type PeriodValue } from "../lib/periods"; import { fetchUserRoles } from "../lib/api"; @@ -9,6 +9,7 @@ import LeaderboardIconWhite from "../assets/LeaderboardIconWhite.svg"; import LeaderboardIconBlack from "../assets/LeaderboardIconBlack.svg"; import DownloadIcon from "../assets/DownloadIcon.svg"; import ThemeSwitcher from "./ThemeSwitcher"; +import CustomPeriodButton from "./CustomPeriodButton"; interface TopNavProps { activePage: "dashboard" | "leaderboard"; @@ -53,51 +54,6 @@ const TopNav: React.FC = ({ onDownloadJSON, }) => { const [userRoles, setUserRoles] = useState([]); - const [showCustomPopup, setShowCustomPopup] = useState(false); - const [draftStart, setDraftStart] = useState(startDate); - const [draftEnd, setDraftEnd] = useState(endDate); - const customPopupRef = useRef(null); - - const toYmd = (date: Date) => - `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; - - const openCustomPopup = () => { - if (startDate && endDate) { - setDraftStart(startDate); - setDraftEnd(endDate); - } else { - const end = new Date(); - const start = new Date(); - start.setDate(end.getDate() - 6); - setDraftStart(toYmd(start)); - setDraftEnd(toYmd(end)); - } - setShowCustomPopup(true); - }; - - const applyCustomRange = () => { - if (!draftStart || !draftEnd || draftStart > draftEnd) return; - onStartDateChange(draftStart); - onEndDateChange(draftEnd); - onPeriodChange("custom"); - setShowCustomPopup(false); - }; - - useEffect(() => { - if (!showCustomPopup) return; - - const handleClickOutside = (event: MouseEvent) => { - if ( - customPopupRef.current && - !customPopupRef.current.contains(event.target as Node) - ) { - setShowCustomPopup(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, [showCustomPopup]); useEffect(() => { let cancelled = false; @@ -215,58 +171,14 @@ const TopNav: React.FC = ({ {label} ))} -
- - {showCustomPopup && ( -
-

- Custom date range -

- - setDraftStart(e.target.value)} - className="w-full mb-3 px-3 py-2 border rounded-lg bg-white text-gray-900 text-sm" - /> - - setDraftEnd(e.target.value)} - className="w-full mb-4 px-3 py-2 border rounded-lg bg-white text-gray-900 text-sm" - /> -
- - -
-
- )} -
+ diff --git a/github-activity-tracker/frontend/src/components/UserProfile.tsx b/github-activity-tracker/frontend/src/components/UserProfile.tsx index 8d28ded..9d0e971 100644 --- a/github-activity-tracker/frontend/src/components/UserProfile.tsx +++ b/github-activity-tracker/frontend/src/components/UserProfile.tsx @@ -15,6 +15,7 @@ import CodeReviewIcon from "../assets/CodeReviewIcon.svg"; import IssueIcon from "../assets/IssueIcon.svg"; import DownloadIcon from "../assets/DownloadIcon.svg"; import ThemeSwitcher from "./ThemeSwitcher"; +import CustomPeriodButton from "./CustomPeriodButton"; import { useTheme } from "../ThemeContext"; interface UserProfileProps { @@ -25,6 +26,8 @@ interface UserProfileProps { startDate: string; endDate: string; onPeriodChange: (p: PeriodValue) => void; + onStartDateChange: (value: string) => void; + onEndDateChange: (value: string) => void; } interface DailyActivityRow { @@ -43,6 +46,8 @@ const UserProfile: React.FC = ({ startDate, endDate, onPeriodChange, + onStartDateChange, + onEndDateChange, }) => { const { theme } = useTheme(); const [userData, setUserData] = useState(null); @@ -94,7 +99,9 @@ const UserProfile: React.FC = ({ issues: userData?.overview?.issues || [], }; - const detailed: DailyActivityRow[] = userData?.daily_activity || []; + const detailed: DailyActivityRow[] = [...(userData?.daily_activity || [])].sort( + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), + ); return (
@@ -148,13 +155,18 @@ const UserProfile: React.FC = ({ {label} ))} - {period === "custom" && ( - - )} +
diff --git a/github-activity-tracker/frontend/src/index.css b/github-activity-tracker/frontend/src/index.css index cd18d6b..96e21a9 100644 --- a/github-activity-tracker/frontend/src/index.css +++ b/github-activity-tracker/frontend/src/index.css @@ -37,6 +37,11 @@ body { box-shadow: 0 10px 30px -18px var(--color-panel-shadow); } +/* Date inputs stay light-surface; keep calendar icon dark even under dark themes. */ +.themed-date-input { + color-scheme: light; +} + .orb { position: fixed; border-radius: 9999px;