Skip to content

Commit

Permalink
Add score and rank to header
Browse files Browse the repository at this point in the history
  • Loading branch information
Evert-R committed May 28, 2024
1 parent a551e3d commit c61a977
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 14 deletions.
15 changes: 14 additions & 1 deletion backend/experiment/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ def serialize_experiment_collection_group(group: ExperimentCollectionGroup, part
next_experiment = get_upcoming_experiment(
grouped_experiments, participant, group.dashboard)

total_score = get_total_score(grouped_experiments, participant)

if not next_experiment:
return None

return {
'dashboard': [serialize_experiment(experiment.experiment, participant) for experiment in grouped_experiments] if group.dashboard else [],
'next_experiment': next_experiment
'next_experiment': next_experiment,
'total_score': total_score
}


Expand Down Expand Up @@ -93,3 +96,13 @@ def get_finished_session_count(experiment, participant):
count = Session.objects.filter(
experiment=experiment, participant=participant, finished_at__isnull=False).count()
return count


def get_total_score(grouped_experiments, participant):
'''Calculate total score of all experiments on the dashboard'''
total_score = 0
for grouped_experiment in grouped_experiments:
sessions = Session.objects.filter(experiment=grouped_experiment.experiment, participant=participant)
for session in sessions:
total_score += session.final_score
return total_score
7 changes: 5 additions & 2 deletions backend/theme/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ def serialize_header(header: HeaderConfig) -> dict:
return {
'nextExperimentButtonText': _('Next experiment'),
'aboutButtonText': _('About us'),
'showScore': header.show_score
}
'showScore': header.show_score,
'scoreClass': 'gold',
'scoreLabel': _('Points'),
'noScoreLabel': _('No points yet!')
}


def serialize_theme(theme: ThemeConfig) -> dict:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => {
const nextExperiment = experimentCollection?.next_experiment;
const displayDashboard = experimentCollection?.dashboard.length;
const showConsent = experimentCollection?.consent;

const totalScore = experimentCollection?.total_score
const scoreClass = experimentCollection?.score_class
const onNext = () => {
setHasShownConsent(true);
}
Expand Down Expand Up @@ -72,7 +73,7 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => {
<div className="aha__collection">
<Switch>
<Route path={URLS.experimentCollectionAbout} component={() => <ExperimentCollectionAbout content={experimentCollection?.aboutContent} slug={experimentCollection.slug} />} />
<Route path={URLS.experimentCollection} exact component={() => <ExperimentCollectionDashboard experimentCollection={experimentCollection} participantIdUrl={participantIdUrl} />} />
<Route path={URLS.experimentCollection} exact component={() => <ExperimentCollectionDashboard experimentCollection={experimentCollection} participantIdUrl={participantIdUrl} totalScore={totalScore} scoreClass={scoreClass} />} />
</Switch>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ interface ExperimentCollectionDashboardProps {
participantIdUrl: string | null;
}

export const ExperimentCollectionDashboard: React.FC<ExperimentCollectionDashboardProps> = ({ experimentCollection, participantIdUrl }) => {

export const ExperimentCollectionDashboard: React.FC<ExperimentCollectionDashboardProps> = ({ experimentCollection, participantIdUrl, totalScore }) => {
const dashboard = experimentCollection.dashboard;
const nextExperimentSlug = experimentCollection.nextExperiment?.slug;
const nextExperimentSlug = experimentCollection.nextExperiment?.slug;

const headerProps = experimentCollection.theme?.header? {
nextExperimentSlug,
nextExperimentSlug,
collectionSlug: experimentCollection.slug,
... experimentCollection.theme.header
...experimentCollection.theme.header,
totalScore: totalScore

} : undefined;

const getExperimentHref = (slug: string) => `/${slug}${participantIdUrl ? `?participant_id=${participantIdUrl}` : ""}`;

return (
Expand Down
88 changes: 86 additions & 2 deletions frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,84 @@
import React from "react";
import React, { useEffect, useState, useRef } from "react";
import { Link } from "react-router-dom";

import Rank from "../Rank/Rank";
import Social from "../Social/Social"

interface HeaderProps {
nextExperimentSlug: string | undefined;
nextExperimentButtonText: string;
collectionSlug: string;
aboutButtonText: string;
showScore: boolean;
totalScore: BigInteger;
scoreClass: string;
scoreLabel: string;
noScoreLabel: string;
}

export const Header: React.FC<HeaderProps> = ({ nextExperimentSlug, nextExperimentButtonText, collectionSlug, aboutButtonText, showScore }) => {
export const Header: React.FC<HeaderProps> = ({ nextExperimentSlug, nextExperimentButtonText, collectionSlug, aboutButtonText, showScore, totalScore, scoreClass, scoreLabel, noScoreLabel }) => {

const social = {
'apps': ['facebook', 'twitter'],
'message': `I scored ${totalScore} points`,
'url': 'wwww.amsterdammusiclab.nl',
'hashtags': ["amsterdammusiclab", "citizenscience"]
}

const useAnimatedScore = (targetScore) => {
const [score, setScore] = useState(0);

const scoreValue = useRef(0);

useEffect(() => {
if (targetScore === 0) {
return;
}

let id = -1;

const nextStep = () => {
// Score step
const scoreStep = Math.max(
1,
Math.min(10, Math.ceil(Math.abs(scoreValue.current - targetScore) / 10))
);

// Scores are equal, stop
if (targetScore === scoreValue.current) {
return;
}

// Add / subtract score
scoreValue.current += Math.sign(targetScore - scoreValue.current) * scoreStep;
setScore(scoreValue.current);

id = setTimeout(nextStep, 50);
};
id = setTimeout(nextStep, 50);

return () => {
window.clearTimeout(id);
};
}, [targetScore]);

return score;
};

const Score = ({ score, label, scoreClass }) => {
const currentScore = useAnimatedScore(score);

return (
<div className="score">
<Rank rank={{ class: scoreClass }} />
<h3>
{currentScore ? currentScore + " " : ""}
{label}
</h3>
</div>
);
};

return (
<div className="hero aha__header">
<div className="intro">
Expand All @@ -18,6 +87,21 @@ export const Header: React.FC<HeaderProps> = ({ nextExperimentSlug, nextExperime
{aboutButtonText && <Link className="btn btn-lg btn-outline-primary" to={`/collection/${collectionSlug}/about`}>{aboutButtonText}</Link>}
</nav>
</div>
{showScore, totalScore !== 0 && (
<div className="results">
<Score
score={totalScore}
scoreClass={scoreClass}
label={scoreLabel}
/>
<Social
social={social}
/>
</div>
)}
{showScore, totalScore === 0 && (
<h3>{noScoreLabel}</h3>
)}
</div>
);
}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/types/Theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface Header {
nextExperimentButtonText: string;
aboutButtonText: string;
showScore: boolean;
totalScore: BigInteger;
};

export default interface Theme {
Expand All @@ -13,4 +14,4 @@ export default interface Theme {
name: string;
footer: null;
header: Header | null;
}
}

0 comments on commit c61a977

Please sign in to comment.