Skip to content

Commit ee8e6ed

Browse files
committed
Add history-service to docker-compose and enhance RecentSessionsList component
- Updated docker-compose.yml to include the history-service. - Refactored RecentSessionsList component to accept optional summaries prop for improved performance. - Enhanced API calls in frontend to handle user IDs more securely with URL encoding. - Updated various components to utilize React Query for data fetching, improving loading states and error handling. - Added partner username fetching in history service to display in RecentSessionsList and QuestionDetailPage. - Improved error handling in history service and added default responses for missing user progress. - Refactored progress-related components to use a consistent data structure and improved UI responsiveness.
1 parent cb8f087 commit ee8e6ed

File tree

19 files changed

+552
-362
lines changed

19 files changed

+552
-362
lines changed

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ services:
7676
- question-service
7777
- matching-service
7878
- collaboration-service
79+
- history-service
7980
networks:
8081
- app-network
8182

frontend/src/features/progress/RecentSessionsList.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useState, useEffect, useMemo } from 'react';
22
import { useNavigate } from 'react-router-dom';
33
import { getAllAttemptSummaries } from '../../lib/api';
44
// --- FIX: Import both time formatters ---
@@ -22,36 +22,66 @@ interface SessionSummary {
2222
question_title: string;
2323
question_difficulty: "Easy" | "Medium" | "Hard";
2424
partner_id: string;
25+
partner_username?: string;
2526
is_solved_successfully: boolean;
2627
has_penalty: boolean; // We need this to determine "Incomplete"
2728
started_at: string;
2829
time_taken_ms: number;
2930
}
3031

31-
const RecentSessionsList: React.FC<{ userId: string, limit: number }> = ({ userId, limit }) => {
32+
interface RecentSessionsListProps {
33+
userId: string;
34+
limit?: number;
35+
summaries?: SessionSummary[];
36+
}
37+
38+
const RecentSessionsList: React.FC<RecentSessionsListProps> = ({ userId, limit, summaries: providedSummaries }) => {
3239
const navigate = useNavigate();
3340
const [sessions, setSessions] = useState<SessionSummary[]>([]);
3441
const [loading, setLoading] = useState(true);
3542

43+
// Create a stable reference for providedSummaries to avoid dependency array issues
44+
const summariesKey = useMemo(() => {
45+
return providedSummaries ? JSON.stringify(providedSummaries.map(s => s.session_id)) : null;
46+
}, [providedSummaries]);
47+
3648
useEffect(() => {
37-
if (!userId) return;
49+
// If summaries are provided, use them directly
50+
if (providedSummaries && providedSummaries.length >= 0) {
51+
const sortedData = [...providedSummaries].sort((a:SessionSummary, b:SessionSummary) =>
52+
new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
53+
);
54+
setSessions(limit ? sortedData.slice(0, limit) : sortedData);
55+
setLoading(false);
56+
return;
57+
}
58+
59+
// Otherwise, fetch from API
60+
if (!userId) {
61+
setLoading(false);
62+
setSessions([]);
63+
return;
64+
}
3865

3966
const fetchRecent = async () => {
4067
try {
4168
setLoading(true);
4269
const data = await getAllAttemptSummaries(userId);
4370
// Sort by date DESC (as getAllAttemptSummaries might not guarantee order)
44-
const sortedData = data ? data.sort((a:SessionSummary, b:SessionSummary) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()) : [];
45-
setSessions(sortedData.slice(0, limit));
71+
const sortedData = data ? data.sort((a:SessionSummary, b:SessionSummary) =>
72+
new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
73+
) : [];
74+
setSessions(limit ? sortedData.slice(0, limit) : sortedData);
4675
} catch (error) {
4776
console.error("Failed to fetch recent sessions:", error);
77+
setSessions([]);
4878
} finally {
4979
setLoading(false);
5080
}
5181
};
5282

5383
fetchRecent();
54-
}, [userId, limit]);
84+
}, [userId, limit, summariesKey]);
5585

5686
const handleSessionClick = (questionId: string) => {
5787
// Navigate to the detail page for that question
@@ -91,7 +121,7 @@ const RecentSessionsList: React.FC<{ userId: string, limit: number }> = ({ userI
91121
<div className="flex items-center gap-4 text-sm text-muted-foreground">
92122
<div className="flex items-center gap-1">
93123
<User className="h-3 w-3" />
94-
{session.partner_id}
124+
{session.partner_username || session.partner_id}
95125
</div>
96126
<div className="flex items-center gap-1">
97127
<Clock className="h-3 w-3" />

frontend/src/features/progress/progressCard.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import ArrowDownIcon from '../../assets/arrow-down-icon.svg'; // Example icon
77
import ArrowUpIcon from '../../assets/arrow-up-icon.svg'; // Example icon
88

99
// Placeholder data TODO: replace with data fetched from API
10-
const placeholderStats = {
11-
totalSessions: 24,
12-
completed: 18,
13-
successRate: 75,
14-
dayStreak: 24,
10+
const placeholderProgress = {
11+
total_sessions_completed: 18,
12+
total_successes: 15,
13+
total_time_ms: 3600000, // 1 hour in milliseconds
14+
current_streak: 24,
1515
};
1616

1717
const placeholderRecentSessions = [
@@ -51,7 +51,7 @@ const ProgressCard = () => {
5151
{isOpen && (
5252
<div className={styles.content}>
5353
<div className={styles.topRow}>
54-
<StatsGrid stats={placeholderStats} />
54+
<StatsGrid progress={placeholderProgress} />
5555
<RecentSessions sessions={placeholderRecentSessions} />
5656
</div>
5757
<TopicProgress progressData={placeholderTopicProgress} />
Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,72 @@
11
import React from 'react';
2-
import StatBox from './statBox';
3-
import styles from './progressCard.module.css';
2+
import { Card } from "@/components/ui/card";
3+
import {
4+
Trophy,
5+
Target,
6+
Clock,
7+
TrendingUp,
8+
} from "lucide-react";
9+
import { formatDuration } from '../../lib/timeFormatters';
10+
11+
interface Stat {
12+
label: string;
13+
value: string | number;
14+
icon: React.ComponentType<{ className?: string }>;
15+
color: string;
16+
}
417

518
interface StatsGridProps {
6-
stats: {
7-
totalSessions: number;
8-
completed: number;
9-
successRate: number;
10-
dayStreak: number;
11-
};
19+
progress: {
20+
total_sessions_completed?: number;
21+
total_successes?: number;
22+
total_time_ms?: number;
23+
current_streak?: number;
24+
} | null;
1225
}
1326

14-
const StatsGrid = ({ stats }: StatsGridProps) => {
27+
const StatsGrid: React.FC<StatsGridProps> = ({ progress }) => {
28+
const stats: Stat[] = [
29+
{
30+
label: "Sessions Completed",
31+
value: progress?.total_sessions_completed ?? 0,
32+
icon: Trophy,
33+
color: "text-yellow-500"
34+
},
35+
{
36+
label: "Problems Solved",
37+
value: progress?.total_successes ?? 0,
38+
icon: Target,
39+
color: "text-green-500"
40+
},
41+
{
42+
label: "Hours Practiced",
43+
value: formatDuration(progress?.total_time_ms),
44+
icon: Clock,
45+
color: "text-blue-500"
46+
},
47+
{
48+
label: "Current Streak",
49+
value: progress?.current_streak ?? 0,
50+
icon: TrendingUp,
51+
color: "text-purple-500"
52+
},
53+
];
54+
1555
return (
16-
<div className={styles.statsGrid}>
17-
<StatBox label="Total Sessions" value={stats.totalSessions} />
18-
<StatBox label="Completed" value={stats.completed} />
19-
<StatBox label="Success Rate" value={`${stats.successRate}%`} />
20-
<StatBox label="Day Streak" value={stats.dayStreak} />
56+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6">
57+
{stats.map((stat) => (
58+
<Card key={stat.label} className="p-6 bg-card border-0 shadow-card hover:shadow-elegant transition-smooth">
59+
<div className="flex items-center justify-between">
60+
<div>
61+
<p className="text-3xl font-bold">{stat.value}</p>
62+
<p className="text-sm text-muted-foreground mt-1">{stat.label}</p>
63+
</div>
64+
<stat.icon className={`h-10 w-10 ${stat.color}`} />
65+
</div>
66+
</Card>
67+
))}
2168
</div>
2269
);
2370
};
2471

25-
export default StatsGrid;
72+
export default StatsGrid;

frontend/src/lib/api.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const getTopics = async () => {
5858
* (For the stat bars on Home and Profile)
5959
*/
6060
export const getHistoryProgress = async (userId: string) => {
61-
const response = await historyApi.get(`/api/history/progress/${userId}`);
61+
const response = await historyApi.get(`/api/history/progress/${encodeURIComponent(userId)}`);
6262
return response.data;
6363
};
6464

@@ -67,7 +67,7 @@ export const getHistoryProgress = async (userId: string) => {
6767
* (For the main lists on Home and History Dashboard)
6868
*/
6969
export const getAllAttemptSummaries = async (userId: string) => {
70-
const response = await historyApi.get(`/api/history/all-summaries/${userId}`);
70+
const response = await historyApi.get(`/api/history/all-summaries/${encodeURIComponent(userId)}`);
7171
return response.data;
7272
};
7373

@@ -76,23 +76,23 @@ export const getAllAttemptSummaries = async (userId: string) => {
7676
* (For the "Question Detail" page - Pic 3)
7777
*/
7878
export const getQuestionAttempts = async (userId: string, questionId: string) => {
79-
const response = await historyApi.get(`/api/history/question-attempts/${userId}/${questionId}`);
79+
const response = await historyApi.get(`/api/history/question-attempts/${encodeURIComponent(userId)}/${encodeURIComponent(questionId)}`);
8080
return response.data;
8181
};
8282

8383
/**
8484
* Fetches the list of "active" question IDs for the reset page.
8585
*/
8686
export const getActiveAttempts = async (userId: string) => {
87-
const response = await historyApi.get(`/api/history/active-attempts/${userId}`);
87+
const response = await historyApi.get(`/api/history/active-attempts/${encodeURIComponent(userId)}`);
8888
return response.data;
8989
};
9090

9191
/**
9292
* Resets a list of questions, making them available for matching again.
9393
*/
9494
export const resetQuestions = async (userId: string, questionIds: string[]) => {
95-
const response = await historyApi.post(`/api/history/reset-questions/${userId}`, { questionIds });
95+
const response = await historyApi.post(`/api/history/reset-questions/${encodeURIComponent(userId)}`, { questionIds });
9696
return response.data;
9797
};
9898
export const cancelMatch = async data => matchingApi.delete(`/api/matches/${data.userId}`, data);

0 commit comments

Comments
 (0)