Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(teams): add jacket nr #62

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/backend/src/models/team.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export class Team extends BaseEntity {
@Column({ unique: true })
name!: string;

@Column()
jacketNr: string;

/**
* List of counted laps for the team.
* This does not include duplicate counts!
Expand Down Expand Up @@ -42,7 +45,7 @@ export class Team extends BaseEntity {
@AfterUpdate()
@AfterInsert()
async calculateLapsCount() {
this.lapsCount = await Lap.countBy({team: { id: this.id }})
this.lapsCount = await Lap.countBy({ team: { id: this.id } })
}

/**
Expand All @@ -53,7 +56,7 @@ export class Team extends BaseEntity {
@AfterUpdate()
@AfterInsert()
async calculateLapsLastTimestamp() {
const lastLap = await Lap.findOne({ where: { team: {id: this.id} }, order: { timestamp: "DESC" } });
const lastLap = await Lap.findOne({ where: { team: { id: this.id } }, order: { timestamp: "DESC" } });

this.lapsLastTimestamp = lastLap?.timestamp || 0;
}
Expand Down
14 changes: 7 additions & 7 deletions packages/backend/src/services/team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ export class TeamService {

/**
* Moves a team at an oldId to his new id based on the new teamList
* @param teams
* @param oldId
* @param teamName
* @return {boolean} true if the new team at oldid is not moved
*/
private async moveTeamsAtId(oldId: number, teamName: string): Promise<boolean> {
// fetch data of stored @ old id
Expand Down Expand Up @@ -90,14 +86,16 @@ export class TeamService {

/**
* Checks if the team's id is updated, if so it moves the other teams around to make sure we can update this team
* @param team
* @return {boolean} true if the team needs to be created
*/
private async clearIdForTeam(team: TelraamTeam) {
// Search if a team with this name already exists
const existingTeam = await Team.findOneBy({ id: team.id });
if (existingTeam) {
if (existingTeam.name === team.name) {
if (existingTeam.jacketNr !== team.jacketNr) {
server.log.debug(`clearIdForTeam: Team ${team.name} already exists with different jacket number`);
Team.update({ id: team.id }, { jacketNr: team.jacketNr });
}
server.log.debug(`clearIdForTeam: Team ${team.name} already exists`);
// Do nothing
return false;
Expand Down Expand Up @@ -137,6 +135,7 @@ export class TeamService {
const DbTeam = new Team();
DbTeam.id = team.id;
DbTeam.name = team.name;
DbTeam.jacketNr = team.jacketNr;

// Attempt to save the team.
await DbTeam.save();
Expand All @@ -148,7 +147,8 @@ export class TeamService {
}
}

public async fetch() {
// Uses a arrow function to properly handle the `this` context in the interval function
public fetch = async () => {
server.log.info(`Fetching teams from telraam at ${config.TELRAAM_ENDPOINT}`);
try {
const response = await AxiosService.getInstance().request<TelraamTeam[]>("get", `/team`, {
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/types/team.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// region API
export interface TeamsRoute {}
export interface TeamsRoute { }

export interface TeamsCreateRoute {
Body: {
Expand Down Expand Up @@ -28,5 +28,6 @@ export interface TeamsLapsRoute {
export interface TelraamTeam {
id: number;
name: string;
jacketNr: string;
}
// endregion
191 changes: 93 additions & 98 deletions packages/frontend/src/components/teams/TeamCard.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
<template>
<div
:class="`team-card card card-content ${team.disabled ? 'disabled' : ''}`"
:style="{
backgroundColor: cardColor,
}"
@click="teamStore.addLap(team.id)"
v-wave
>
<div :class="`team-card card card-content ${team.disabled ? 'disabled' : ''}`" :style="{
backgroundColor: cardColor,
}" @click="teamStore.addLap(team.id)" v-wave>
<!-- Team number -->
<div class="team-card-number">
{{ team.id }}
{{ team.jacketNr }}
</div>

<!-- Team name -->
Expand All @@ -29,97 +24,97 @@
</template>

<script lang="ts" setup>
import { PropType, computed } from "vue";
import { intervalToDuration } from "date-fns";
import { Team } from "../../types/models/team.model";
import { useTimeStore } from "../../stores/time.store";
import { useTeamsStore } from "../../stores/teams.store";
import { isMobile } from "../../helpers/util";
import config from "../../config";

const props = defineProps({
// Team to display
team: {
type: Object as PropType<Team>,
required: true,
},
});

const TARGET_COLOR = "#ff7575";

const timeStore = useTimeStore();
const teamStore = useTeamsStore();

const cardColor = computed(() => {
const timeInterval = Math.round((timeStore.clientTime - props.team.lapsLastTimestamp) / 1000);
if (timeInterval < config.teams.minIntervalSec) {
return "inherit";
}
if (timeInterval > config.teams.minIntervalSec * 2) {
return TARGET_COLOR;
}
let stepSize = timeInterval - config.teams.minIntervalSec;
let redStep = (255 - parseInt(TARGET_COLOR.slice(1, 3), 16)) / config.teams.minIntervalSec;
let blueStep = (255 - parseInt(TARGET_COLOR.slice(3, 5), 16)) / config.teams.minIntervalSec;
let greenStep = (255 - parseInt(TARGET_COLOR.slice(5, 7), 16)) / config.teams.minIntervalSec;

let redOutput = Math.round(255 - redStep * stepSize).toString(16);
let blueOutput = Math.round(255 - blueStep * stepSize).toString(16);
let greenOutput = Math.round(255 - greenStep * stepSize).toString(16);

return `#${redOutput}${blueOutput}${greenOutput}`;
});

// Get the time since the last lap.
const timeSinceLastLap = computed(() => {
const interval = intervalToDuration({ start: props.team.lapsLastTimestamp, end: timeStore.clientTime });

// Convert the interval into a human readable format (e.g. "1h 10m 15s")
let intervalFormatted = "";
intervalFormatted += interval.days && interval.days > 0 ? `${interval.days}d ` : "";
intervalFormatted += interval.hours && interval.hours > 0 ? `${interval.hours}h ` : "";
intervalFormatted += interval.minutes && interval.minutes > 0 ? `${interval.minutes}m ` : "";
intervalFormatted += interval.seconds ? `${interval.seconds}s` : "0s";
intervalFormatted = intervalFormatted.trim();

return intervalFormatted;
});
import { PropType, computed } from "vue";
import { intervalToDuration } from "date-fns";
import { Team } from "../../types/models/team.model";
import { useTimeStore } from "../../stores/time.store";
import { useTeamsStore } from "../../stores/teams.store";
import { isMobile } from "../../helpers/util";
import config from "../../config";

const props = defineProps({
// Team to display
team: {
type: Object as PropType<Team>,
required: true,
},
});

const TARGET_COLOR = "#ff7575";

const timeStore = useTimeStore();
const teamStore = useTeamsStore();

const cardColor = computed(() => {
const timeInterval = Math.round((timeStore.clientTime - props.team.lapsLastTimestamp) / 1000);
if (timeInterval < config.teams.minIntervalSec) {
return "inherit";
}
if (timeInterval > config.teams.minIntervalSec * 2) {
return TARGET_COLOR;
}
let stepSize = timeInterval - config.teams.minIntervalSec;
let redStep = (255 - parseInt(TARGET_COLOR.slice(1, 3), 16)) / config.teams.minIntervalSec;
let blueStep = (255 - parseInt(TARGET_COLOR.slice(3, 5), 16)) / config.teams.minIntervalSec;
let greenStep = (255 - parseInt(TARGET_COLOR.slice(5, 7), 16)) / config.teams.minIntervalSec;

let redOutput = Math.round(255 - redStep * stepSize).toString(16);
let blueOutput = Math.round(255 - blueStep * stepSize).toString(16);
let greenOutput = Math.round(255 - greenStep * stepSize).toString(16);

return `#${redOutput}${blueOutput}${greenOutput}`;
});

// Get the time since the last lap.
const timeSinceLastLap = computed(() => {
const interval = intervalToDuration({ start: props.team.lapsLastTimestamp, end: timeStore.clientTime });

// Convert the interval into a human readable format (e.g. "1h 10m 15s")
let intervalFormatted = "";
intervalFormatted += interval.days && interval.days > 0 ? `${interval.days}d ` : "";
intervalFormatted += interval.hours && interval.hours > 0 ? `${interval.hours}h ` : "";
intervalFormatted += interval.minutes && interval.minutes > 0 ? `${interval.minutes}m ` : "";
intervalFormatted += interval.seconds ? `${interval.seconds}s` : "0s";
intervalFormatted = intervalFormatted.trim();

return intervalFormatted;
});
</script>

<style lang="scss" scoped>
.team-card {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
overflow-wrap: anywhere;
background-color: white;
color: inherit;
cursor: pointer;
font-size: 0.8rem;
height: 100%;
transition: background 0.3s ease-in-out;
padding: v-bind('isMobile() ? ".75rem" : "1.5rem"');

&.disabled {
cursor: not-allowed;
pointer-events: none;
background-color: #ababab !important;
}

&-number {
font-size: v-bind("isMobile() ? '1.3rem' : '2rem'");
font-weight: 600;
}

&-name {
font-size: v-bind("isMobile() ? '.8rem' : '1.1rem'");
font-weight: 500;
}

&-details {
font-size: v-bind("isMobile() ? '.7rem' : 'inherit'");
margin-top: v-bind("isMobile() ? '.25rem' : '0.5rem'");
}
.team-card {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
overflow-wrap: anywhere;
background-color: white;
color: inherit;
cursor: pointer;
font-size: 0.8rem;
height: 100%;
transition: background 0.3s ease-in-out;
padding: v-bind('isMobile() ? ".75rem" : "1.5rem"');

&.disabled {
cursor: not-allowed;
pointer-events: none;
background-color: #ababab !important;
}

&-number {
font-size: v-bind("isMobile() ? '1.3rem' : '2rem'");
font-weight: 600;
}

&-name {
font-size: v-bind("isMobile() ? '.8rem' : '1.1rem'");
font-weight: 500;
}

&-details {
font-size: v-bind("isMobile() ? '.7rem' : 'inherit'");
margin-top: v-bind("isMobile() ? '.25rem' : '0.5rem'");
}
}
</style>
1 change: 1 addition & 0 deletions packages/frontend/src/types/models/team.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface Team {
id: number;
name: string;
jacketNr: string;
disabled: boolean;
lapsCount: number;
lapsLastTimestamp: number;
Expand Down