Skip to content

Commit 02ec1f5

Browse files
committed
feat: add score history page for users and coalitions
1 parent 9a45626 commit 02ec1f5

6 files changed

Lines changed: 207 additions & 113 deletions

File tree

src/routes/coalitions.ts

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Express } from 'express';
22
import passport from 'passport';
33
import { IntraUser, PrismaClient } from '@prisma/client';
4-
import { getCoalitionScore, getBlocAtDate, scoreSumsToRanking, getCoalitionTopContributors, SMALL_CONTRIBUTION_TYPES } from '../utils';
4+
import { getCoalitionScore, getBlocAtDate, scoreSumsToRanking, getCoalitionTopContributors, SMALL_CONTRIBUTION_TYPES, getPageNav, getPageNumber } from '../utils';
55
import { ASSISTANT_GROUP_ID, ASSISTANTS_CAN_QUIZ } from '../env';
66

77
export const setupCoalitionRoutes = function(app: Express, prisma: PrismaClient): void {
@@ -117,29 +117,6 @@ export const setupCoalitionRoutes = function(app: Express, prisma: PrismaClient)
117117
});
118118
const topContributorsWeek = await scoreSumsToRanking(prisma, topScoresWeek, 'Top contributors of the past 7 days');
119119

120-
const latestScores = await prisma.codamCoalitionScore.findMany({
121-
where: {
122-
coalition_id: coalition.id,
123-
},
124-
orderBy: {
125-
created_at: 'desc',
126-
},
127-
include: {
128-
user: {
129-
select: {
130-
intra_user: {
131-
select: {
132-
login: true,
133-
usual_full_name: true,
134-
image: true,
135-
},
136-
},
137-
},
138-
},
139-
},
140-
take: 50,
141-
});
142-
143120
const latestBigScores = await prisma.codamCoalitionScore.findMany({
144121
where: {
145122
coalition_id: coalition.id,
@@ -184,7 +161,6 @@ export const setupCoalitionRoutes = function(app: Express, prisma: PrismaClient)
184161

185162
viewOptions['topContributors'] = topContributors;
186163
viewOptions['topContributorsWeek'] = topContributorsWeek;
187-
viewOptions['latestScores'] = latestScores;
188164
viewOptions['latestBigScores'] = latestBigScores;
189165
viewOptions['coalitionScore'] = coalitionScore;
190166
}
@@ -226,4 +202,80 @@ export const setupCoalitionRoutes = function(app: Express, prisma: PrismaClient)
226202
coalitionColored: false,
227203
});
228204
});
205+
206+
app.get('/coalitions/:coalitionId/scores', passport.authenticate('session', {
207+
keepSessionInfo: true,
208+
}), async (req, res) => {
209+
const coalitionId = parseInt(req.params.coalitionId);
210+
if (!coalitionId || isNaN(coalitionId) || coalitionId <= 0) {
211+
return res.status(400).send('Invalid coalition ID');
212+
}
213+
214+
const coalition = await prisma.codamCoalition.findFirst({
215+
where: {
216+
id: parseInt(req.params.coalitionId),
217+
},
218+
include: {
219+
intra_coalition: true,
220+
},
221+
});
222+
if (!coalition) {
223+
return res.status(404).send('Coalition not found');
224+
}
225+
226+
const currentBloc = await getBlocAtDate(prisma);
227+
if (!currentBloc) {
228+
return res.status(400).send('No season currently ongoing');
229+
}
230+
const itemsPerPage = 100;
231+
const totalPages = await prisma.codamCoalitionScore.count({
232+
where: {
233+
coalition_id: coalition.id,
234+
created_at: {
235+
gte: currentBloc.begin_at,
236+
lt: currentBloc.end_at,
237+
},
238+
},
239+
}).then(count => Math.ceil(count / itemsPerPage));
240+
const pageNum = getPageNumber(req, totalPages);
241+
const scores = await prisma.codamCoalitionScore.findMany({
242+
where: {
243+
coalition_id: coalition.id,
244+
created_at: {
245+
gte: currentBloc.begin_at,
246+
lt: currentBloc.end_at,
247+
},
248+
},
249+
orderBy: {
250+
created_at: 'desc',
251+
},
252+
include: {
253+
coalition: {
254+
select: {
255+
intra_coalition: {
256+
select: {
257+
name: true,
258+
color: true,
259+
},
260+
}
261+
}
262+
},
263+
},
264+
take: itemsPerPage,
265+
skip: (pageNum - 1) * itemsPerPage,
266+
});
267+
268+
// Create a list of pages for the pagination nav
269+
const pageNav = getPageNav(pageNum, totalPages);
270+
271+
return res.render('history.njk', {
272+
historyTitle: `Score History for ${coalition.intra_coalition.name} coalition in the current season`,
273+
scores,
274+
pageNum,
275+
totalPages,
276+
pageNav,
277+
coalitionColored: false,
278+
multipleUsers: true,
279+
});
280+
});
229281
};

src/routes/profile.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { PrismaClient } from '@prisma/client';
22
import { Express } from 'express';
33
import { ExpressIntraUser } from '../sync/oauth';
4-
import { getUserScores, getUserRankingAcrossAllRankings, getUserSeasonRanking, SMALL_CONTRIBUTION_TYPES } from '../utils';
4+
import { getUserScores, getUserRankingAcrossAllRankings, getUserSeasonRanking, getPageNumber, getPageNav } from '../utils';
55
import NodeCache from 'node-cache';
66

77
export const setupProfileRoutes = function(app: Express, prisma: PrismaClient): void {
@@ -56,24 +56,39 @@ export const setupProfileRoutes = function(app: Express, prisma: PrismaClient):
5656
},
5757
});
5858

59-
const latestBigScores = await prisma.codamCoalitionScore.findMany({
59+
// Get user ranking across all rankings
60+
const userRankings = await getUserRankingAcrossAllRankings(prisma, profileUser.id);
61+
62+
return res.render('profile.njk', {
63+
profileUser,
64+
latestScores,
65+
userScores,
66+
totalScore,
67+
ranking,
68+
userRankings,
69+
});
70+
});
71+
72+
app.get('/profile/:login/scores', async (req, res) => {
73+
const profileUser = await prisma.intraUser.findFirst({
74+
where: {
75+
login: req.params.login,
76+
},
77+
});
78+
if (!profileUser) {
79+
return res.status(404).send('User not found');
80+
}
81+
82+
const itemsPerPage = 100;
83+
const totalPages = await prisma.codamCoalitionScore.count({
84+
where: {
85+
user_id: profileUser.id,
86+
},
87+
}).then(count => Math.ceil(count / itemsPerPage));
88+
const pageNum = getPageNumber(req, totalPages);
89+
const scores = await prisma.codamCoalitionScore.findMany({
6090
where: {
6191
user_id: profileUser.id,
62-
OR: [
63-
{
64-
NOT: {
65-
fixed_type_id: {
66-
in: SMALL_CONTRIBUTION_TYPES, // Exclude usually low individual scores
67-
}
68-
},
69-
},
70-
{
71-
fixed_type_id: null, // Do include scores that are not fixed types
72-
}
73-
],
74-
amount: {
75-
gt: 0,
76-
},
7792
},
7893
orderBy: {
7994
created_at: 'desc',
@@ -90,20 +105,21 @@ export const setupProfileRoutes = function(app: Express, prisma: PrismaClient):
90105
}
91106
},
92107
},
93-
take: 25,
108+
take: itemsPerPage,
109+
skip: (pageNum - 1) * itemsPerPage,
94110
});
95111

96-
// Get user ranking across all rankings
97-
const userRankings = await getUserRankingAcrossAllRankings(prisma, profileUser.id);
112+
// Create a list of pages for the pagination nav
113+
const pageNav = getPageNav(pageNum, totalPages);
98114

99-
return res.render('profile.njk', {
100-
profileUser,
101-
latestScores,
102-
latestBigScores,
103-
userScores,
104-
totalScore,
105-
ranking,
106-
userRankings,
115+
return res.render('history.njk', {
116+
historyTitle: `Score History for ${profileUser.usual_full_name}`,
117+
scores,
118+
pageNum,
119+
totalPages,
120+
pageNav,
121+
coalitionColored: true,
122+
multipleUsers: false,
107123
});
108124
});
109125

templates/base.njk

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,29 @@
22
{# Refer to interface PageNav in utils.ts for an overview of what a PageNav item contains #}
33
<nav aria-label="Page navigation" class="d-flex justify-content-center">
44
<ul class="pagination">
5+
{% for pageNavItem in pageNav %}
6+
{% if pageNavItem.active %}
7+
<!-- Previous page link -->
8+
<li class="page-item{{ ' disabled' if pageNavItem.num == 1 }}">
9+
<a class="page-link" href="?page={{ pageNavItem.num - 1 }}" aria-label="Previous page">
10+
<span aria-hidden="true">&laquo;</span>
11+
</a>
12+
</li>
13+
{% endif %}
14+
{% endfor %}
515
{% for pageNavItem in pageNav %}
616
<li class="page-item{{ ' active' if pageNavItem.active }}" {{ 'aria-current="page"' if pageNavItem.active }}><a class="page-link" href="?page={{ pageNavItem.num }}">{{ pageNavItem.text }}</a></li>
717
{% endfor %}
18+
{% for pageNavItem in pageNav %}
19+
{% if pageNavItem.active %}
20+
<!-- Next page link -->
21+
<li class="page-item{{ ' disabled' if pageNavItem.num == totalPages }}">
22+
<a class="page-link" href="?page={{ pageNavItem.num + 1 }}" aria-label="Next page">
23+
<span aria-hidden="true">&raquo;</span>
24+
</a>
25+
</li>
26+
{% endif %}
27+
{% endfor %}
828
</ul>
929
</nav>
1030
{% endmacro %}

templates/coalition.njk

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -144,45 +144,18 @@
144144
<td style="white-space: normal; word-wrap: break-word;"><small>{{ score.reason | striptags(true) | escape }}</small></td>
145145
</tr>
146146
{% endfor %}
147-
</tbody>
148-
</table>
149-
</div>
150-
</div>
151-
</div>
152-
</div>
153-
154-
<!-- point history table (last 50 contributions) -->
155-
<!--
156-
<div class="row ms-0 me-0 mb-4">
157-
<div class="col">
158-
<div class="card h-100">
159-
<div class="card-header">
160-
<h4 class="card-title mb-0">Most recent contributions</h4>
161-
</div>
162-
<div class="card-body p-0">
163-
<table class="table table-striped mb-0">
164-
<thead>
165-
<th scope="col">Date</th>
166-
<th scope="col">Student</th>
167-
<th scope="col">Points</th>
168-
<th scope="col">Reason</th>
169-
</thead>
170-
<tbody>
171-
{% for score in latestScores %}
147+
{% if latestBigScores | length == 0 %}
172148
<tr>
173-
<td title="{{ score.created_at }}">{{ score.created_at | timeAgo }}</td>
174-
<td><a href="/profile/{{ score.user.intra_user.login | striptags(true) | escape }}">{{ score.user.intra_user.login | striptags(true) | escape }}</a></td>
175-
<td>{{ score.amount }}</td>
176-
<td style="white-space: normal; word-wrap: break-word;"><small>{{ score.reason | striptags(true) | escape }}</small></td>
149+
<td colspan="4" class="text-center text-muted">No big contributions yet</td>
177150
</tr>
178-
{% endfor %}
151+
{% endif %}
179152
</tbody>
180153
</table>
154+
<p class="m-2 text-end"><a href="/coalitions/{{ coalition.id }}/scores">View full score history</a></p>
181155
</div>
182156
</div>
183157
</div>
184158
</div>
185-
-->
186159
{% endif %}
187160

188161
<!-- assistant overview for this coalition -->

templates/history.njk

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{% extends "base.njk" %}
2+
{% set nomargin = false %}
3+
{% set page_title = historyTitle %}
4+
5+
{% block content %}
6+
<!-- content -->
7+
<div class="container-md mt-4 mw-900">
8+
<div class="row ms-0 me-0 mb-4">
9+
<div class="col">
10+
<div class="card h-100">
11+
<div class="card-header">
12+
<h4 class="card-title mb-0">{{ historyTitle | striptags(true) | escape }}</h4>
13+
</div>
14+
<div class="card-body p-0">
15+
{% if historyDescription %}
16+
<p class="p-4 mb-0"><b>{{ historyDescription | striptags(true) | escape | nl2br }}</b></p>
17+
{% endif %}
18+
{# point history table #}
19+
<table class="table {{ 'table-striped' if not coalitionColored else 'coalition-colored' }} mb-0">
20+
<thead>
21+
<tr>
22+
<th scope="col">Date</th>
23+
{% if multipleUsers %}
24+
<th scope="col">Student</th>
25+
{% endif %}
26+
<th scope="col">Points</th>
27+
<th scope="col">Reason</th>
28+
</tr>
29+
</thead>
30+
<tbody>
31+
{% for score in scores %}
32+
<tr style="background: {{ score.coalition.intra_coalition.color | rgba(0.25) }};">
33+
<td title="{{ score.created_at }}">{{ score.created_at | timeAgo }}</td>
34+
{% if multipleUsers %}
35+
<td><a href="/profile/{{ score.user.intra_user.login | striptags(true) | escape }}">{{ score.user.intra_user.login | striptags(true) | escape }}</a></td>
36+
{% endif %}
37+
<td>{{ score.amount | thousands }}</td>
38+
<td style="white-space: normal; word-wrap: break-word;">{{ score.reason | striptags(true) | escape }}</td>
39+
</tr>
40+
{% endfor %}
41+
{% if scores | length == 0 %}
42+
<tr>
43+
<td colspan="{{ 4 if multipleUsers else 3 }}" class="text-center text-muted">No contributions yet</td>
44+
</tr>
45+
{% endif %}
46+
</tbody>
47+
</table>
48+
</div>
49+
</div>
50+
</div>
51+
</div>
52+
</div>
53+
54+
{{ pagination(pageNav) }}
55+
56+
{% endblock %}

0 commit comments

Comments
 (0)