Skip to content

Commit 398fbb6

Browse files
committed
Add option to return previous sessions
1 parent 5ce9e5b commit 398fbb6

4 files changed

Lines changed: 188 additions & 2 deletions

File tree

app/frontend/src/hooks/api.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useMutation, useQuery } from "@tanstack/react-query";
1+
import { useQueries, useMutation, useQuery } from "@tanstack/react-query";
22
import { orpc } from "../lib/api.js";
3+
import { getStoredSessions } from "../lib/sessionStorage.js";
34

45
export const useMotions = (params?: {
56
limit?: number;
@@ -160,3 +161,28 @@ export const usePartyCategoryLikeness = (
160161
}),
161162
);
162163
};
164+
165+
export const useRecentSessions = () => {
166+
const storedSessions = getStoredSessions();
167+
168+
const queries = useQueries({
169+
queries: storedSessions.map((session) =>
170+
orpc.compass.getResults.queryOptions({
171+
input: { sessionId: session.id },
172+
}),
173+
),
174+
});
175+
176+
return {
177+
sessions: queries
178+
.map((query, index) => ({
179+
id: storedSessions[index].id,
180+
createdAt: storedSessions[index].createdAt,
181+
data: query.data,
182+
isLoading: query.isLoading,
183+
error: query.error,
184+
}))
185+
.filter((session) => session.data !== null), // Only show sessions that exist
186+
isLoading: queries.some((q) => q.isLoading),
187+
};
188+
};
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const SESSION_STORAGE_KEY = "partijgedrag_sessions";
2+
const MAX_SESSIONS = 10;
3+
4+
export interface StoredSession {
5+
id: string;
6+
createdAt: Date;
7+
}
8+
9+
export function saveSessionId(sessionId: string): void {
10+
try {
11+
const sessions = getStoredSessions();
12+
const newSession: StoredSession = {
13+
id: sessionId,
14+
createdAt: new Date(),
15+
};
16+
17+
// Remove duplicate if exists
18+
const filtered = sessions.filter((s) => s.id !== sessionId);
19+
20+
// Add new session at the beginning
21+
const updated = [newSession, ...filtered].slice(0, MAX_SESSIONS);
22+
23+
localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(updated));
24+
} catch (error) {
25+
console.error("Failed to save session ID:", error);
26+
}
27+
}
28+
29+
export function getStoredSessions(): StoredSession[] {
30+
try {
31+
const stored = localStorage.getItem(SESSION_STORAGE_KEY);
32+
if (!stored) return [];
33+
34+
const parsed = JSON.parse(stored);
35+
// Convert date strings back to Date objects
36+
return parsed.map((s: { id: string; createdAt: string }) => ({
37+
id: s.id,
38+
createdAt: new Date(s.createdAt),
39+
}));
40+
} catch (error) {
41+
console.error("Failed to get stored sessions:", error);
42+
return [];
43+
}
44+
}
45+
46+
export function removeSessionId(sessionId: string): void {
47+
try {
48+
const sessions = getStoredSessions();
49+
const filtered = sessions.filter((s) => s.id !== sessionId);
50+
localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(filtered));
51+
} catch (error) {
52+
console.error("Failed to remove session ID:", error);
53+
}
54+
}
55+
56+
export function clearAllSessions(): void {
57+
try {
58+
localStorage.removeItem(SESSION_STORAGE_KEY);
59+
} catch (error) {
60+
console.error("Failed to clear sessions:", error);
61+
}
62+
}

app/frontend/src/pages/Compass.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
useSubmitAnswers,
2727
} from "../hooks/api";
2828
import type { Motion, UserAnswer } from "../lib/api";
29+
import { saveSessionId } from "../lib/sessionStorage";
2930
import { calculateProgress } from "../lib/utils";
3031

3132
type Answer = "agree" | "disagree" | "neutral";
@@ -196,6 +197,8 @@ export function CompassPage() {
196197
const handleSubmit = async (answers: UserAnswer[]) => {
197198
try {
198199
const result = await submitAnswers.mutateAsync({ answers });
200+
// Save session ID to localStorage for later retrieval
201+
saveSessionId(result.id);
199202
navigate(`/results/${result.id}`);
200203
} catch (error) {
201204
console.error("Failed to submit answers:", error);

app/frontend/src/pages/Home.tsx

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
import { ArrowRight, Search, Users, Vote } from "lucide-react";
1+
import { ArrowRight, Clock, Search, Users, Vote } from "lucide-react";
22
import { Link } from "react-router-dom";
33

44
import { Card, CardContent } from "@/components/ui/Card";
55
import { useGetMotionStatistics } from "@/hooks/useGetMotionStatistics";
6+
import { useRecentSessions } from "@/hooks/api";
67
import logo from "../assets/tug.png";
78
import { Button } from "../components/ui/Button";
9+
import { formatPercentage } from "../lib/utils";
810

911
export function HomePage() {
1012
const { data: motionStatisticsData, isLoading } = useGetMotionStatistics();
13+
const { sessions, isLoading: isLoadingSessions } = useRecentSessions();
14+
1115
return (
1216
<div className="pb-16 ">
1317
{/* Hero Section */}
@@ -55,6 +59,97 @@ export function HomePage() {
5559
</div>
5660
</div>
5761

62+
{/* Recent Sessions Section */}
63+
{!isLoadingSessions && sessions.length > 0 && (
64+
<div className="bg-white border-b border-gray-200">
65+
<div className="container mx-auto px-4 py-8">
66+
<div className="mb-4 flex items-center justify-between">
67+
<div>
68+
<h2 className="text-2xl font-bold text-gray-900">
69+
Eerdere resultaten
70+
</h2>
71+
<p className="text-sm text-gray-600 mt-1">
72+
Ga terug naar je vorige kieswijzer sessies
73+
</p>
74+
</div>
75+
</div>
76+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
77+
{sessions.slice(0, 3).map((session) => {
78+
const topParty = session.data?.partyResults[0];
79+
const formattedDate = new Date(
80+
session.createdAt,
81+
).toLocaleDateString("nl-NL", {
82+
day: "numeric",
83+
month: "short",
84+
year: "numeric",
85+
hour: "2-digit",
86+
minute: "2-digit",
87+
});
88+
89+
return (
90+
<Card
91+
key={session.id}
92+
className="h-full transition-all hover:shadow-md border-2 border-gray-100 hover:border-blue-200"
93+
>
94+
<CardContent className="p-5">
95+
<div className="flex items-start justify-between mb-3">
96+
<div className="flex items-center text-sm text-gray-500">
97+
<Clock className="h-4 w-4 mr-1.5" />
98+
{formattedDate}
99+
</div>
100+
{topParty && (
101+
<div className="text-2xl font-bold text-blue-600">
102+
{formatPercentage(topParty.agreement)}
103+
</div>
104+
)}
105+
</div>
106+
{topParty && (
107+
<div className="mb-2">
108+
<div className="text-sm text-gray-600 mb-1">
109+
Beste match:
110+
</div>
111+
<div className="font-semibold text-gray-900">
112+
{topParty.party.shortName}
113+
</div>
114+
</div>
115+
)}
116+
{session.data && (
117+
<div className="text-xs text-gray-500 mt-3 pt-3 border-t border-gray-100">
118+
{session.data.totalAnswers} stellingen beantwoord
119+
</div>
120+
)}
121+
<div className="mt-4 flex gap-2">
122+
<Link to={`/results/${session.id}`} className="flex-1">
123+
<Button
124+
variant="secondary"
125+
size="sm"
126+
className="w-full text-xs"
127+
>
128+
Resultaten
129+
</Button>
130+
</Link>
131+
<Link
132+
to={`/compass?session=${session.id}`}
133+
className="flex-1"
134+
>
135+
<Button
136+
variant="primary"
137+
size="sm"
138+
className="w-full text-xs"
139+
>
140+
Ga verder
141+
</Button>
142+
</Link>
143+
</div>
144+
</CardContent>
145+
</Card>
146+
);
147+
})}
148+
</div>
149+
</div>
150+
</div>
151+
)}
152+
58153
{/* Main Tools Section */}
59154
<div className="container mx-auto px-4 py-16">
60155
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">

0 commit comments

Comments
 (0)