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
2 changes: 2 additions & 0 deletions github-activity-tracker/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ function App() {
startDate={startDate}
endDate={endDate}
onPeriodChange={handlePeriodChange}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
)}

Expand Down
116 changes: 116 additions & 0 deletions github-activity-tracker/frontend/src/components/CustomPeriodButton.tsx
Original file line number Diff line number Diff line change
@@ -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<CustomPeriodButtonProps> = ({
startDate,
endDate,
onPeriodChange,
onStartDateChange,
onEndDateChange,
buttonClassName,
}) => {
const [showCustomPopup, setShowCustomPopup] = useState(false);
const [draftStart, setDraftStart] = useState(startDate);
const [draftEnd, setDraftEnd] = useState(endDate);
const customPopupRef = useRef<HTMLDivElement>(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 (
<div className="relative" ref={customPopupRef}>
<button type="button" className={buttonClassName} onClick={openCustomPopup}>
Custom
</button>
{showCustomPopup && (
<div className="absolute left-0 top-full mt-2 z-30 w-72 rounded-xl border border-panel-border bg-brand-softer p-4 shadow-lg">
<p className="text-sm font-medium text-brand-ink mb-3">
Custom date range
</p>
<label className="block text-xs text-brand-mid mb-1">From</label>
<input
type="date"
value={draftStart}
max={draftEnd || undefined}
onChange={(e) => 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"
/>
<label className="block text-xs text-brand-mid mb-1">To</label>
<input
type="date"
value={draftEnd}
min={draftStart || undefined}
onChange={(e) => 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"
/>
<div className="flex justify-end gap-2">
<button
type="button"
className="px-3 py-1.5 rounded-lg text-sm text-brand-dark bg-surface border border-panel-border hover:bg-brand-soft"
onClick={() => setShowCustomPopup(false)}
>
Cancel
</button>
<button
type="button"
className="px-3 py-1.5 rounded-full text-sm text-white bg-brand hover:bg-brand-hover disabled:opacity-50"
disabled={!draftStart || !draftEnd || draftStart > draftEnd}
onClick={applyCustomRange}
>
Apply
</button>
</div>
</div>
)}
</div>
);
};

export default CustomPeriodButton;
108 changes: 10 additions & 98 deletions github-activity-tracker/frontend/src/components/TopNav.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -53,51 +54,6 @@ const TopNav: React.FC<TopNavProps> = ({
onDownloadJSON,
}) => {
const [userRoles, setUserRoles] = useState<string[]>([]);
const [showCustomPopup, setShowCustomPopup] = useState(false);
const [draftStart, setDraftStart] = useState(startDate);
const [draftEnd, setDraftEnd] = useState(endDate);
const customPopupRef = useRef<HTMLDivElement>(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;
Expand Down Expand Up @@ -215,58 +171,14 @@ const TopNav: React.FC<TopNavProps> = ({
{label}
</button>
))}
<div className="relative" ref={customPopupRef}>
<button
className={periodBtn(period === "custom")}
onClick={openCustomPopup}
>
Custom
</button>
{showCustomPopup && (
<div className="absolute left-0 top-full mt-2 z-30 w-72 rounded-xl border bg-white p-4 shadow-lg">
<p className="text-sm font-medium text-gray-800 mb-3">
Custom date range
</p>
<label className="block text-xs text-gray-500 mb-1">
From
</label>
<input
type="date"
value={draftStart}
max={draftEnd || undefined}
onChange={(e) => setDraftStart(e.target.value)}
className="w-full mb-3 px-3 py-2 border rounded-lg bg-white text-gray-900 text-sm"
/>
<label className="block text-xs text-gray-500 mb-1">
To
</label>
<input
type="date"
value={draftEnd}
min={draftStart || undefined}
onChange={(e) => setDraftEnd(e.target.value)}
className="w-full mb-4 px-3 py-2 border rounded-lg bg-white text-gray-900 text-sm"
/>
<div className="flex justify-end gap-2">
<button
type="button"
className="px-3 py-1.5 rounded-lg text-sm text-gray-700 bg-gray-100 hover:bg-gray-200"
onClick={() => setShowCustomPopup(false)}
>
Cancel
</button>
<button
type="button"
className="px-3 py-1.5 rounded-full text-sm text-white bg-brand hover:bg-brand-hover disabled:opacity-50"
disabled={!draftStart || !draftEnd || draftStart > draftEnd}
onClick={applyCustomRange}
>
Apply
</button>
</div>
</div>
)}
</div>
<CustomPeriodButton
startDate={startDate}
endDate={endDate}
onPeriodChange={onPeriodChange}
onStartDateChange={onStartDateChange}
onEndDateChange={onEndDateChange}
buttonClassName={periodBtn(period === "custom")}
/>
</div>
</div>

Expand Down
28 changes: 20 additions & 8 deletions github-activity-tracker/frontend/src/components/UserProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,6 +26,8 @@ interface UserProfileProps {
startDate: string;
endDate: string;
onPeriodChange: (p: PeriodValue) => void;
onStartDateChange: (value: string) => void;
onEndDateChange: (value: string) => void;
}

interface DailyActivityRow {
Expand All @@ -43,6 +46,8 @@ const UserProfile: React.FC<UserProfileProps> = ({
startDate,
endDate,
onPeriodChange,
onStartDateChange,
onEndDateChange,
}) => {
const { theme } = useTheme();
const [userData, setUserData] = useState<any>(null);
Expand Down Expand Up @@ -94,7 +99,9 @@ const UserProfile: React.FC<UserProfileProps> = ({
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 (
<div className="min-h-screen">
Expand Down Expand Up @@ -148,13 +155,18 @@ const UserProfile: React.FC<UserProfileProps> = ({
{label}
</button>
))}
{period === "custom" && (
<button
className="px-5 py-2 rounded-full font-black bg-brand-softer text-brand-dark shadow-lg"
>
Custom
</button>
)}
<CustomPeriodButton
startDate={startDate}
endDate={endDate}
onPeriodChange={onPeriodChange}
onStartDateChange={onStartDateChange}
onEndDateChange={onEndDateChange}
buttonClassName={`px-5 py-2 rounded-full font-black transition-all ${
period === "custom"
? "bg-brand-softer text-brand-dark shadow-lg"
: "bg-white/20 text-white hover:bg-white/30"
}`}
/>
</div>

<div className="flex items-center gap-4">
Expand Down
5 changes: 5 additions & 0 deletions github-activity-tracker/frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading