diff --git a/src/commons/XMLParser/XMLParserHelper.ts b/src/commons/XMLParser/XMLParserHelper.ts index 6f1d6e5c61..7b57f9bdae 100644 --- a/src/commons/XMLParser/XMLParserHelper.ts +++ b/src/commons/XMLParser/XMLParserHelper.ts @@ -87,7 +87,8 @@ const makeAssessmentOverview = (result: any, maxXpVal: number): AssessmentOvervi isGradingPublished: false, xp: 0, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }; }; diff --git a/src/commons/application/actions/__tests__/SessionActions.ts b/src/commons/application/actions/__tests__/SessionActions.ts index 199cd32b56..cf65144525 100644 --- a/src/commons/application/actions/__tests__/SessionActions.ts +++ b/src/commons/application/actions/__tests__/SessionActions.ts @@ -484,7 +484,8 @@ test('updateAssessmentOverviews generates correct action object', () => { xp: 0, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 } ]; const action = SessionActions.updateAssessmentOverviews(overviews); diff --git a/src/commons/application/reducers/__tests__/SessionReducer.ts b/src/commons/application/reducers/__tests__/SessionReducer.ts index 5a4e3261ef..8a589c4aad 100644 --- a/src/commons/application/reducers/__tests__/SessionReducer.ts +++ b/src/commons/application/reducers/__tests__/SessionReducer.ts @@ -335,7 +335,8 @@ const assessmentOverviewsTest1: AssessmentOverview[] = [ xp: 0, isGradingPublished: false, maxTeamSize: 5, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 } ]; @@ -358,7 +359,8 @@ const assessmentOverviewsTest2: AssessmentOverview[] = [ xp: 1, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 } ]; diff --git a/src/commons/assessment/Assessment.tsx b/src/commons/assessment/Assessment.tsx index d458dbf602..2fbfb5526c 100644 --- a/src/commons/assessment/Assessment.tsx +++ b/src/commons/assessment/Assessment.tsx @@ -25,6 +25,7 @@ import { useDispatch } from 'react-redux'; import { Navigate, useLoaderData, useParams } from 'react-router'; import { NavLink } from 'react-router-dom'; import { numberRegExp } from 'src/features/academy/AcademyTypes'; +import classes from 'src/styles/Academy.module.scss'; import defaultCoverImage from '../../assets/default_cover_image.jpg'; import SessionActions from '../application/actions/SessionActions'; @@ -38,7 +39,7 @@ import Markdown from '../Markdown'; import NotificationBadge from '../notificationBadge/NotificationBadge'; import { filterNotificationsByAssessment } from '../notificationBadge/NotificationBadgeHelper'; import Constants from '../utils/Constants'; -import { beforeNow, getPrettyDate } from '../utils/DateHelper'; +import { beforeNow, getPrettyDate, getPrettyDateAfterHours } from '../utils/DateHelper'; import { useResponsive, useSession } from '../utils/Hooks'; import { assessmentTypeLink, convertParamToInt } from '../utils/ParamParseHelper'; import AssessmentNotFound from './AssessmentNotFound'; @@ -179,12 +180,19 @@ const Assessment: React.FC = () => {
{makeOverviewCardTitle(overview, index, renderGradingTooltip)} -
+
{overview.isGradingPublished ? `XP: ${overview.xp} / ${overview.maxXp}` : `Max XP: ${overview.maxXp}`}
+ {overview.earlySubmissionXp > 0 && ( + + + + )}
diff --git a/src/commons/assessment/AssessmentTypes.ts b/src/commons/assessment/AssessmentTypes.ts index 09bf818ed4..c3d5cecbf4 100644 --- a/src/commons/assessment/AssessmentTypes.ts +++ b/src/commons/assessment/AssessmentTypes.ts @@ -81,6 +81,7 @@ export type AssessmentOverview = { title: string; xp: number; maxTeamSize: number; // For team assessment + hoursBeforeEarlyXpDecay: number; }; /* @@ -259,7 +260,8 @@ export const overviewTemplate = (): AssessmentOverview => { isGradingPublished: false, xp: 0, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }; }; diff --git a/src/commons/mocks/AssessmentMocks.ts b/src/commons/mocks/AssessmentMocks.ts index 4a3c8ad5dd..deda0f8cba 100644 --- a/src/commons/mocks/AssessmentMocks.ts +++ b/src/commons/mocks/AssessmentMocks.ts @@ -127,7 +127,8 @@ const mockUnopenedAssessmentsOverviews: AssessmentOverview[] = [ xp: 0, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 } ]; @@ -162,7 +163,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ xp: 1, isGradingPublished: false, maxTeamSize: 4, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }, { type: 'Missions', @@ -182,7 +184,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ xp: 2, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }, { type: 'Quests', @@ -202,7 +205,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ xp: 3, isGradingPublished: false, maxTeamSize: 2, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }, { type: 'Paths', @@ -222,7 +226,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ xp: 0, isGradingPublished: false, maxTeamSize: 2, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }, { type: 'Others', @@ -243,7 +248,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ private: true, maxTeamSize: 1, isGradingPublished: false, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 } ]; @@ -266,7 +272,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ xp: 800, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }, { type: 'Quests', @@ -286,7 +293,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ xp: 500, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }, { type: 'Quests', @@ -306,7 +314,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ xp: 150, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 }, { type: 'Paths', @@ -326,7 +335,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ xp: 100, isGradingPublished: false, maxTeamSize: 1, - hasVotingFeatures: false + hasVotingFeatures: false, + hoursBeforeEarlyXpDecay: 0 } ]; diff --git a/src/commons/utils/DateHelper.ts b/src/commons/utils/DateHelper.ts index b33667cff0..79454affba 100644 --- a/src/commons/utils/DateHelper.ts +++ b/src/commons/utils/DateHelper.ts @@ -41,3 +41,10 @@ export const getStandardDate = (dateString: string): string => { const prettyDate = date.format('MMMM Do YYYY'); return prettyDate; }; + +export const getPrettyDateAfterHours = (dateString: string, hours: number): string => { + const date = moment(dateString).add(hours, 'hours'); + const absolutePrettyDate = date.format('Do MMMM, HH:mm'); + const relativePrettyDate = date.fromNow(); + return `${absolutePrettyDate} (${relativePrettyDate})`; +}; diff --git a/src/styles/Academy.module.scss b/src/styles/Academy.module.scss index 88adf782b2..c261c29ff2 100644 --- a/src/styles/Academy.module.scss +++ b/src/styles/Academy.module.scss @@ -15,3 +15,8 @@ align-items: center; justify-content: center; } + +.listing-xp { + display: flex; + gap: 0.5rem; +}