Skip to content
Merged
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
14 changes: 8 additions & 6 deletions github-activity-tracker/backend/routes/userDetailsRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ const express = require('express');
const router = express.Router();
const { getUserDetails } = require('../services/userDetailsService');
const { resolveRoleFilter } = require('../services/userRolesService');
const { resolvePeriodQuery } = require('../utils/dateRange');

// GET /orgs/:org_id/users/:login?period=daily|weekly|monthly|yearly&role=Developer
// GET /orgs/:org_id/users/:login?period=daily|weekly|monthly|yearly|custom&role=Developer
router.get('/orgs/:org_id/users/:login', async (req, res) => {
const { org_id, login } = req.params;
const { period = 'weekly', role } = req.query;
const { role } = req.query;
const { error: periodError, period, startDate, endDate } = resolvePeriodQuery(req.query);

if (!login) {
return res.status(400).json({ error: 'Missing user login' });
}

if (!['daily', 'weekly', 'monthly', 'yearly'].includes(period)) {
return res.status(400).json({ error: 'Invalid period value' });
if (periodError) {
return res.status(400).json({ error: periodError });
}

try {
Expand All @@ -22,12 +24,12 @@ router.get('/orgs/:org_id/users/:login', async (req, res) => {
return res.status(400).json({ error });
}

const data = await getUserDetails(org_id, login, period, roleFilter);
const data = await getUserDetails(org_id, login, period, roleFilter, startDate, endDate);
return res.json(data);
} catch (err) {
console.error('Error in User Details API:', err);
return res.status(500).json({ error: 'Failed to fetch user details' });
}
});

module.exports = router;
module.exports = router;
46 changes: 31 additions & 15 deletions github-activity-tracker/backend/services/userDetailsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const dayjs = require('dayjs');
const pool = require('../db/dbPool');
const { isExcludedGitHubLogin } = require('../config/excludedGitHubLogins');
const { userDetailsJoinSql } = require('../utils/userRoleSql');
const { getCustomDateRange } = require('../utils/dateRange');

/* ------------------------------------------------
Helper: Calculate % change
Expand All @@ -14,7 +15,7 @@ function percentChange(current, previous) {
/* ------------------------------------------------
MAIN SERVICE
------------------------------------------------ */
async function getUserDetails(orgId, login, period, role = null) {
async function getUserDetails(orgId, login, period, role = null, startDate, endDate) {
if (isExcludedGitHubLogin(login)) {
throw new Error('User not found');
}
Expand Down Expand Up @@ -42,22 +43,37 @@ async function getUserDetails(orgId, login, period, role = null) {
const userId = user.id;

/* 2. Determine date ranges */
const periods = {
daily: 1,
weekly: 7,
monthly: 30,
yearly: 365,
};
let start;
let end;
let prevStart;
let prevEnd;
let days;

if (period === 'custom') {
const range = getCustomDateRange(startDate, endDate);
start = dayjs(range.start);
end = dayjs(range.end);
prevStart = dayjs(range.prevStart);
prevEnd = dayjs(range.prevEnd);
days = range.days;
} else {
const periods = {
daily: 1,
weekly: 7,
monthly: 30,
yearly: 365,
};

const days = periods[period];
if (!days) {
throw new Error('Invalid period');
}
days = periods[period];
if (!days) {
throw new Error('Invalid period');
}

const end = dayjs().endOf('day');
const start = end.subtract(days - 1, 'day').startOf('day');
const prevEnd = start.subtract(1, 'millisecond');
const prevStart = prevEnd.subtract(days - 1, 'day').startOf('day');
end = dayjs().endOf('day');
start = end.subtract(days - 1, 'day').startOf('day');
prevEnd = start.subtract(1, 'millisecond');
prevStart = prevEnd.subtract(days - 1, 'day').startOf('day');
}

const orgOwner = String(orgId).toLowerCase();

Expand Down
4 changes: 4 additions & 0 deletions github-activity-tracker/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ function App() {
org={selectedOrg}
userName={selectedUser}
onBack={() => setActivePage("dashboard")}
period={period}
startDate={startDate}
endDate={endDate}
onPeriodChange={handlePeriodChange}
/>
)}

Expand Down
4 changes: 2 additions & 2 deletions github-activity-tracker/frontend/src/components/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ const TopNav: React.FC<TopNavProps> = ({
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-sm"
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
Expand All @@ -245,7 +245,7 @@ const TopNav: React.FC<TopNavProps> = ({
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-sm"
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
Expand Down
42 changes: 34 additions & 8 deletions github-activity-tracker/frontend/src/components/UserProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import ActivityChart from "./ActivityChart";
import ActivityTrend from "./ActivityTrend";
import { fetchUserDetails } from "../lib/api";
import {
DEFAULT_PERIOD,
formatPeriodLabel,
PERIOD_OPTIONS,
type PeriodValue,
Expand All @@ -22,6 +21,10 @@ interface UserProfileProps {
org: string;
userName: string;
onBack: () => void;
period: PeriodValue;
startDate: string;
endDate: string;
onPeriodChange: (p: PeriodValue) => void;
}

interface DailyActivityRow {
Expand All @@ -32,24 +35,40 @@ interface DailyActivityRow {
issues: number;
}

const UserProfile: React.FC<UserProfileProps> = ({ org, userName, onBack }) => {
const UserProfile: React.FC<UserProfileProps> = ({
org,
userName,
onBack,
period,
startDate,
endDate,
onPeriodChange,
}) => {
const { theme } = useTheme();
const [period, setPeriod] = useState<PeriodValue>(DEFAULT_PERIOD);

const [userData, setUserData] = useState<any>(null);

useEffect(() => {
if (period === "custom" && (!startDate || !endDate || startDate > endDate)) {
return;
}

async function loadUser() {
try {
const data = await fetchUserDetails(org, userName, period);
const data = await fetchUserDetails(
org,
userName,
period,
startDate,
endDate,
);
setUserData(data);
} catch (err) {
console.error("Failed to load user details:", err);
}
}

loadUser();
}, [org, userName, period]);
}, [org, userName, period, startDate, endDate]);

const githubUsername = userData?.login || userName;
const githubProfileUrl = `https://github.com/${githubUsername}`;
Expand Down Expand Up @@ -119,7 +138,7 @@ const UserProfile: React.FC<UserProfileProps> = ({ org, userName, onBack }) => {
{PERIOD_OPTIONS.map(({ value, label }) => (
<button
key={value}
onClick={() => setPeriod(value)}
onClick={() => onPeriodChange(value)}
className={`px-5 py-2 rounded-full font-black transition-all ${
period === value
? "bg-brand-softer text-brand-dark shadow-lg"
Expand All @@ -129,6 +148,13 @@ const UserProfile: React.FC<UserProfileProps> = ({ org, userName, onBack }) => {
{label}
</button>
))}
{period === "custom" && (
<button
className="px-5 py-2 rounded-full font-black bg-brand-softer text-brand-dark shadow-lg"
>
Custom
</button>
)}
</div>

<div className="flex items-center gap-4">
Expand Down Expand Up @@ -248,4 +274,4 @@ const UserProfile: React.FC<UserProfileProps> = ({ org, userName, onBack }) => {
);
};

export default UserProfile;
export default UserProfile;
4 changes: 3 additions & 1 deletion github-activity-tracker/frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,14 @@ export const fetchUserDetails = async (
orgId: string,
login: string,
period: PeriodValue,
startDate?: string,
endDate?: string,
) => {
try {
const response = await axios.get(
`${API_BASE_URL}/orgs/${orgId}/users/${login}`,
{
params: { period },
params: periodParams(period, { startDate, endDate }),
},
);
return response.data;
Expand Down
Loading