Skip to content

Commit 27463c4

Browse files
committed
Add a toggle for the scoreboard, to show rolling BP
1 parent d4331e7 commit 27463c4

File tree

5 files changed

+104
-33
lines changed

5 files changed

+104
-33
lines changed

todoqueue_backend/accounts/serializers.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from rest_framework.validators import UniqueValidator
44

55
from logging import getLogger
6+
67
logger = getLogger(__name__)
78

9+
810
class CustomUserSerializer(serializers.ModelSerializer):
911
class Meta:
1012
model = get_user_model()
@@ -18,6 +20,13 @@ class Meta:
1820
)
1921

2022

23+
class CustomUserWithBrowniePointsSerializer(CustomUserSerializer):
24+
rolling_brownie_points = serializers.IntegerField(read_only=True, default=0)
25+
26+
class Meta(CustomUserSerializer.Meta):
27+
fields = CustomUserSerializer.Meta.fields + ("rolling_brownie_points",)
28+
29+
2130
class CustomUserRegistrationSerializer(serializers.ModelSerializer):
2231
email = serializers.EmailField(
2332
validators=[UniqueValidator(queryset=get_user_model().objects.all())]
@@ -46,6 +55,8 @@ class ResetPasswordSerializer(serializers.Serializer):
4655
confirm_new_password = serializers.CharField(write_only=True)
4756

4857
def validate(self, data):
49-
if data['new_password'] != data['confirm_new_password']:
50-
raise serializers.ValidationError({"confirm_new_password": "Passwords do not match"})
51-
return data
58+
if data["new_password"] != data["confirm_new_password"]:
59+
raise serializers.ValidationError(
60+
{"confirm_new_password": "Passwords do not match"}
61+
)
62+
return data

todoqueue_backend/tasks/models.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
from django.contrib.contenttypes.models import ContentType
1111
from django.core.validators import MaxValueValidator, MinValueValidator
1212
from django.db import models
13-
from django.db.models.signals import m2m_changed, pre_delete
13+
from django.db.models.signals import m2m_changed
1414
from django.dispatch import receiver
1515
from django.utils import timezone
1616
from rest_framework import serializers
17-
from rest_framework.renderers import JSONRenderer
1817
from rest_framework.response import Response
1918
from rest_framework.validators import UniqueValidator
2019

todoqueue_backend/tasks/views.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
from datetime import timedelta
12
from logging import INFO, basicConfig, getLogger
23

3-
from accounts.serializers import CustomUserSerializer
4+
from accounts.serializers import CustomUserWithBrowniePointsSerializer
45
from django.contrib.auth import get_user_model
6+
from django.db.models import Sum, Q
7+
from django.db.models.functions import Coalesce
58
from django.shortcuts import get_object_or_404
9+
from django.utils import timezone
10+
611
from rest_framework import status, viewsets
712
from rest_framework.decorators import (
813
action,
@@ -217,8 +222,22 @@ def list_users(self, request, pk=None):
217222
{"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN
218223
)
219224

225+
# Date calculations
226+
start_datetime = timezone.now() - timedelta(minutes=5)
227+
228+
# Prefetch worklogs from the last 7 days and annotate the sum of brownie points
229+
users = household.users.annotate(
230+
rolling_brownie_points=Coalesce(
231+
Sum(
232+
"worklog__brownie_points",
233+
filter=Q(worklog__timestamp__gte=start_datetime),
234+
),
235+
0,
236+
)
237+
)
238+
220239
# Serialize the users of the household
221-
user_serializer = CustomUserSerializer(household.users.all(), many=True)
240+
user_serializer = CustomUserWithBrowniePointsSerializer(users, many=True)
222241

223242
return Response(user_serializer.data)
224243

@@ -393,7 +412,7 @@ def award_brownie_points(request, pk):
393412
logger.info(f"Awarding brownie points")
394413
logger.info(f"Household PK: {pk}")
395414
# Retrieve brownie_points from query parameters
396-
brownie_points = request.GET.get('brownie_points')
415+
brownie_points = request.GET.get("brownie_points")
397416
logger.info(f"Brownie points: {brownie_points}")
398417
if brownie_points is None:
399418
return Response(
@@ -405,17 +424,22 @@ def award_brownie_points(request, pk):
405424
brownie_points = int(brownie_points)
406425
except ValueError:
407426
return Response(
408-
{"error": "Invalid brownie_points value"}, status=status.HTTP_400_BAD_REQUEST
427+
{"error": "Invalid brownie_points value"},
428+
status=status.HTTP_400_BAD_REQUEST,
409429
)
410430

411431
user = request.user
412-
household = get_object_or_404(Household, pk=pk) # It'll return 404 if the household does not exist
432+
household = get_object_or_404(
433+
Household, pk=pk
434+
) # It'll return 404 if the household does not exist
413435

414436
if user not in household.users.all():
415437
return Response({"error": "Not allowed"}, status=status.HTTP_403_FORBIDDEN)
416438

417439
try:
418-
user.brownie_point_credit.setdefault(str(household.id), 0) # This ensures the key exists
440+
user.brownie_point_credit.setdefault(
441+
str(household.id), 0
442+
) # This ensures the key exists
419443
user.brownie_point_credit[str(household.id)] += brownie_points
420444
user.save()
421445
except:
@@ -424,6 +448,4 @@ def award_brownie_points(request, pk):
424448
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
425449
)
426450

427-
return Response(
428-
{"success": "Credited brownie points"}, status=status.HTTP_200_OK
429-
)
451+
return Response({"success": "Credited brownie points"}, status=status.HTTP_200_OK)

todoqueue_frontend/src/App.css

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,25 @@ h1 {
567567
background-color: rgba(255, 0, 0, 0.145);
568568
}
569569

570+
.toggle-switch {
571+
position: absolute;
572+
left: -130px;
573+
top: 50%;
574+
transform: translateY(-50%);
575+
}
576+
577+
.toggle-switch input[type="checkbox"] {
578+
display: none;
579+
}
580+
581+
.toggle-switch label {
582+
cursor: pointer;
583+
background-color: #224d76;
584+
width: 7em;
585+
padding: 5px 10px;
586+
border-radius: 15px;
587+
}
588+
570589
.user-stats-container {
571590
position: absolute;
572591
bottom: 15px;
@@ -580,7 +599,6 @@ h1 {
580599

581600
.user-stats-container:hover {
582601
background-color: #333;
583-
transform: translateX(-50%) scale(1.05); /* combined transform */
584602
cursor: pointer;
585603
}
586604

@@ -598,6 +616,11 @@ h1 {
598616
margin: 0 1rem;
599617
}
600618

619+
.user-stats-flex:hover {
620+
transform: scale(1.05);
621+
cursor: pointer;
622+
}
623+
601624
.user-name {
602625
font-size: 1.5rem;
603626
font-weight: bold;

todoqueue_frontend/src/components/Tasks.js

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const Tasks = ({ selectedHousehold, setShowHouseholdSelector }) => {
2929
const isInitialRender = useRef(true);
3030
const [browniePoints, setBrowniePoints] = useState(0);
3131
const [showFlipAnimation, setShowFlipAnimation] = useState(false);
32+
const [viewMode, setViewMode] = useState('total'); // Toggle scoreboard between 'total' or 'rolling'
3233

3334
// Define an enumeration for the popups
3435
const PopupType = {
@@ -63,7 +64,6 @@ const Tasks = ({ selectedHousehold, setShowHouseholdSelector }) => {
6364
// Fetch tasks, and users at regular intervals
6465
// TODO: Is it wise to make these requests so often? Can it be offloaded to the client?
6566
useEffect(() => {
66-
// run immediately, then start a timer that runs every 1000ms
6767
try {
6868
fetchSetTasks();
6969
fetchSetUsers();
@@ -132,7 +132,7 @@ const Tasks = ({ selectedHousehold, setShowHouseholdSelector }) => {
132132
setUsers([]);
133133
return;
134134
}
135-
console.log("Setting users: ", users);
135+
console.log("Setting users: ", data);
136136
setUsers(data);
137137
};
138138

@@ -370,30 +370,46 @@ const Tasks = ({ selectedHousehold, setShowHouseholdSelector }) => {
370370

371371

372372
{selectedHousehold ? (
373-
<div
374-
className="user-stats-container"
375-
onClick={handleOpenAwardBrowniePointsPopup}
376-
>
377-
<div className="user-stats-flex">
378-
{
373+
<div className="user-stats-container">
374+
<div className="toggle-switch">
375+
<input
376+
type="checkbox"
377+
id="viewModeSwitch"
378+
checked={viewMode === 'rolling'}
379+
onChange={() => setViewMode(prevMode => prevMode === 'total' ? 'rolling' : 'total')}
380+
/>
381+
<label htmlFor="viewModeSwitch">
382+
{viewMode === "total" ? "All Time" : "Last 7 days"}
383+
</label>
384+
</div>
385+
<div className="user-stats-flex" onClick={handleOpenAwardBrowniePointsPopup}>
386+
{viewMode === 'total' ?
379387
users.sort((a, b) =>
380388
(b.brownie_point_credit[selectedHousehold] - b.brownie_point_debit[selectedHousehold])
381389
- (a.brownie_point_credit[selectedHousehold] - a.brownie_point_debit[selectedHousehold])
382390
)
383391
.slice(0, 5)
384-
.map((user, index) => {
385-
return (
386-
<div key={index} className="user-row">
387-
<span className="user-name">{user.username}</span>
388-
<SimpleFlipper
389-
value={user.brownie_point_credit[selectedHousehold] - user.brownie_point_debit[selectedHousehold]}
390-
/>
391-
</div>
392-
);
393-
})
392+
.map((user, index) => (
393+
<div key={index} className="user-row">
394+
<span className="user-name">{user.username}</span>
395+
<SimpleFlipper
396+
value={user.brownie_point_credit[selectedHousehold] - user.brownie_point_debit[selectedHousehold]}
397+
/>
398+
</div>
399+
))
400+
:
401+
users.sort((a, b) => b.rolling_brownie_points - a.rolling_brownie_points)
402+
.slice(0, 5)
403+
.map((user, index) => (
404+
<div key={index} className="user-row">
405+
<span className="user-name">{user.username}</span>
406+
<SimpleFlipper value={user.rolling_brownie_points} />
407+
</div>
408+
))
394409
}
395410
</div>
396411
</div>
412+
397413
) : null}
398414

399415

0 commit comments

Comments
 (0)