Added trainer create/delete weeklytimeslot view and endpoint
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
5eacfe7167
commit
0f8d73b4cc
@ -1,4 +1,11 @@
|
||||
export interface Timeslot {
|
||||
startDate: Date
|
||||
endDate: Date
|
||||
}
|
||||
|
||||
export interface WeeklyTimeslot {
|
||||
id: number
|
||||
day_of_week: number
|
||||
start_time: string
|
||||
end_time: string
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div class="Schedule">
|
||||
<div class="days">
|
||||
<div class="day" v-for="day in days">
|
||||
{{ day.name }}
|
||||
<div class="timeslots">
|
||||
<div class="timeslot" v-for="timeslot in day.timeslots" @click="deleteTimeslot(timeslot.id)">
|
||||
{{ timeslot.start_time.split(":").slice(0, 2).join(":") }} -
|
||||
{{ timeslot.end_time.split(":").slice(0, 2).join(":") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="newTimeslot">
|
||||
<label class="startLabel" for="startHour">Start</label>
|
||||
<input class="startHour" type="number" placeholder="HH" name="startHour" min="0" max="23"
|
||||
v-model="newDayInput[day.day_of_week - 1].startHour" />
|
||||
<input class="startMinute" type="number" placeholder="MM" name="startMinute" min="0" max="59"
|
||||
v-model="newDayInput[day.day_of_week - 1].startMinute" />
|
||||
<label class="endLabel" for="endHour">Slut</label>
|
||||
<input class="endHour" type="number" placeholder="HH" name="endHour" min="0" max="23"
|
||||
v-model="newDayInput[day.day_of_week - 1].endHour" />
|
||||
<input class="endMinute" type="number" placeholder="MM" name="endMinute" min="0" max="59"
|
||||
v-model="newDayInput[day.day_of_week - 1].endMinute" />
|
||||
<input class="submit" type="submit" name="submit" value="Submit" @click="addTimeslot(day.day_of_week)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.Schedule {
|
||||
margin: 30px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 0.5rem;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.newTimeslot {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(5, 1fr);
|
||||
grid-column-gap: 0px;
|
||||
grid-row-gap: 0px;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.startLabel {
|
||||
grid-area: 1 / 1 / 2 / 3;
|
||||
}
|
||||
|
||||
.startHour {
|
||||
grid-area: 2 / 1 / 3 / 2;
|
||||
}
|
||||
|
||||
.startMinute {
|
||||
grid-area: 2 / 2 / 3 / 3;
|
||||
}
|
||||
|
||||
.endLabel {
|
||||
grid-area: 3 / 1 / 4 / 3;
|
||||
}
|
||||
|
||||
.endHour {
|
||||
grid-area: 4 / 1 / 5 / 2;
|
||||
}
|
||||
|
||||
.endMinute {
|
||||
grid-area: 4 / 2 / 5 / 3;
|
||||
}
|
||||
|
||||
.submit {
|
||||
grid-area: 5 / 1 / 6 / 3;
|
||||
}
|
||||
|
||||
.timeslots {
|
||||
border-top: 1px solid #cbd5e1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.timeslot {
|
||||
padding: 5px;
|
||||
border: 1px solid #cbd5e1;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.timeslot:hover {
|
||||
border-color: lightcoral;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import LocalizedFormat from "dayjs/plugin/localizedFormat"
|
||||
import utc from "dayjs/plugin/utc"
|
||||
import { } from "dayjs/locale/da";
|
||||
import type { WeeklyTimeslot } from '@/interfaces/timeslot';
|
||||
|
||||
dayjs.extend(LocalizedFormat);
|
||||
dayjs.extend(utc);
|
||||
|
||||
interface Day {
|
||||
name: string
|
||||
timeslots: WeeklyTimeslot[]
|
||||
day_of_week: number
|
||||
}
|
||||
interface NewDayInput {
|
||||
startHour: number | undefined
|
||||
startMinute: number | undefined
|
||||
endHour: number | undefined
|
||||
endMinute: number | undefined
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "TrainerSchedule",
|
||||
data() {
|
||||
return {
|
||||
newDayInput: Array.from({ length: 7 }, () => {
|
||||
return {
|
||||
startHour: undefined,
|
||||
startMinute: undefined,
|
||||
endHour: undefined,
|
||||
endMinute: undefined
|
||||
}
|
||||
}) as NewDayInput[],
|
||||
weeklyTimeslots: [] as WeeklyTimeslot[]
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.fetchSchedule();
|
||||
},
|
||||
computed: {
|
||||
days(): Day[] {
|
||||
const days: Day[] = [
|
||||
{ day_of_week: 1, name: "Mandag", timeslots: [] },
|
||||
{ day_of_week: 2, name: "Tirsdag", timeslots: [] },
|
||||
{ day_of_week: 3, name: "Onsdag", timeslots: [] },
|
||||
{ day_of_week: 4, name: "Torsdag", timeslots: [] },
|
||||
{ day_of_week: 5, name: "Fredag", timeslots: [] },
|
||||
{ day_of_week: 6, name: "Lørdag", timeslots: [] },
|
||||
{ day_of_week: 7, name: "Søndag", timeslots: [] }
|
||||
];
|
||||
|
||||
this.weeklyTimeslots.forEach((timeslot: WeeklyTimeslot) => {
|
||||
days[timeslot.day_of_week - 1].timeslots.push(timeslot);
|
||||
});
|
||||
|
||||
return days;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async addTimeslot(day_of_week: number) {
|
||||
console.log(this.newDayInput[day_of_week - 1]);
|
||||
const input: NewDayInput = this.newDayInput[day_of_week - 1];
|
||||
const start_time = `${input.startHour}:${input.startMinute ? input.startMinute : 0}`;
|
||||
const end_time = `${input.endHour}:${input.endMinute ? input.endMinute : 0}`;
|
||||
|
||||
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/trainer/timeslot/`, {
|
||||
credentials: import.meta.env.DEV ? "include" : undefined,
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
day_of_week,
|
||||
start_time,
|
||||
end_time
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "Application/json"
|
||||
}
|
||||
});
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
this.$router.push({ path: "/login", query: { ref: this.$route.path } });
|
||||
}
|
||||
|
||||
this.fetchSchedule();
|
||||
},
|
||||
async deleteTimeslot(id: number) {
|
||||
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/trainer/timeslot/${id}`, {
|
||||
credentials: import.meta.env.DEV ? "include" : undefined,
|
||||
method: "DELETE"
|
||||
});
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
this.$router.push({ path: "/login", query: { ref: this.$route.path } });
|
||||
}
|
||||
|
||||
this.fetchSchedule();
|
||||
},
|
||||
async fetchSchedule() {
|
||||
// TODO: remove these credentials
|
||||
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/trainer/timeslot`, import.meta.env.DEV ? { credentials: "include" } : {});
|
||||
if (res.status === 200) {
|
||||
this.weeklyTimeslots = await res.json();
|
||||
} else if (res.status === 401 || res.status === 403) {
|
||||
this.$router.push({ path: "/login", query: { ref: this.$route.path } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,10 @@
|
||||
import express, { Express, Router } from "express";
|
||||
|
||||
const router: Router = express.Router();
|
||||
import trainer from "./trainer";
|
||||
import weeklyTimeslot from "./weeklyTimeslot";
|
||||
|
||||
router.use(weeklyTimeslot);
|
||||
router.use(trainer);
|
||||
|
||||
export default router;
|
||||
@ -1,12 +1,12 @@
|
||||
import express, { Router, Request, Response } from "express";
|
||||
import Joi from "joi"
|
||||
|
||||
import { client } from "../db";
|
||||
import { client } from "../../db";
|
||||
import { DatabaseError } from "pg";
|
||||
import Trainer from "../interfaces/trainer";
|
||||
import { AdminAuth } from "../middlewares/auth";
|
||||
import { trainerExists } from "../middlewares/trainer";
|
||||
import { idSchema } from "../schemas";
|
||||
import Trainer from "../../interfaces/trainer";
|
||||
import { AdminAuth } from "../../middlewares/auth";
|
||||
import { trainerExists } from "../../middlewares/trainer";
|
||||
import { idSchema } from "../../schemas";
|
||||
|
||||
const router: Router = express.Router();
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
import express, { Router, Response } from "express";
|
||||
import Joi from "joi"
|
||||
|
||||
import { client } from "../../db";
|
||||
import { DatabaseError } from "pg";
|
||||
import { TrainerAuth } from "../../middlewares/auth";
|
||||
import { timeSchema } from "../../schemas";
|
||||
import { AuthedRequest } from "../../interfaces/auth";
|
||||
import { WeeklyTimeslot } from "../../interfaces/timeslot";
|
||||
|
||||
const router: Router = express.Router();
|
||||
|
||||
async function getTrainerIdFromUserId(userId: number | undefined): Promise<number | undefined> {
|
||||
const trainerLookupResult = await client.query(`
|
||||
SELECT id FROM trainers
|
||||
WHERE user_id = $1;
|
||||
`, [
|
||||
userId
|
||||
]);
|
||||
return trainerLookupResult.rows[0]?.id;
|
||||
}
|
||||
|
||||
router.get("/trainer/timeslot", TrainerAuth, async (req: AuthedRequest, res: Response) => {
|
||||
try {
|
||||
const trainerId: number | undefined = await getTrainerIdFromUserId(req.user?.userId);
|
||||
if (trainerId === undefined)
|
||||
return res.sendStatus(403);
|
||||
|
||||
const databaseResult = await client.query(`
|
||||
SELECT id, day_of_week, start_time, end_time FROM weekly_timeslots
|
||||
WHERE trainer_id = $1;
|
||||
`, [
|
||||
trainerId
|
||||
]);
|
||||
|
||||
const timeslots: WeeklyTimeslot[] = databaseResult.rows;
|
||||
|
||||
return res.status(200).send(timeslots);
|
||||
} catch (error: DatabaseError | Error | any) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
})
|
||||
|
||||
const weeklyTimeslotSchema = Joi.object({
|
||||
day_of_week: Joi.number().integer().min(1).max(7).required(),
|
||||
start_time: timeSchema.required(),
|
||||
end_time: timeSchema.required()
|
||||
});
|
||||
|
||||
router.post("/trainer/timeslot", TrainerAuth, async (req: AuthedRequest, res: Response) => {
|
||||
try {
|
||||
const trainerId: number | undefined = await getTrainerIdFromUserId(req.user?.userId);
|
||||
if (trainerId === undefined)
|
||||
return res.sendStatus(403);
|
||||
|
||||
const validation = weeklyTimeslotSchema.validate(req.body, { abortEarly: false });
|
||||
if (validation.error !== undefined) {
|
||||
return res.status(400).send(validation.error.details);
|
||||
}
|
||||
|
||||
const weeklyTimeslot: WeeklyTimeslot = validation.value;
|
||||
|
||||
const databaseResult = await client.query(`
|
||||
INSERT INTO weekly_timeslots (trainer_id, day_of_week, start_time, end_time)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *;
|
||||
`, [
|
||||
trainerId,
|
||||
weeklyTimeslot.day_of_week,
|
||||
weeklyTimeslot.start_time,
|
||||
weeklyTimeslot.end_time
|
||||
]);
|
||||
|
||||
const timeslots: WeeklyTimeslot = databaseResult.rows[0];
|
||||
|
||||
return res.status(200).send(timeslots);
|
||||
} catch (error: DatabaseError | Error | any) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
})
|
||||
|
||||
router.delete("/trainer/timeslot/:id", TrainerAuth, async (req: AuthedRequest, res: Response) => {
|
||||
try {
|
||||
const trainerId: number | undefined = await getTrainerIdFromUserId(req.user?.userId);
|
||||
if (trainerId === undefined)
|
||||
return res.sendStatus(403);
|
||||
|
||||
const databaseResult = await client.query(`
|
||||
DELETE FROM weekly_timeslots
|
||||
WHERE trainer_id = $1
|
||||
AND id = $2;
|
||||
`, [
|
||||
trainerId,
|
||||
req.params.id
|
||||
]);
|
||||
|
||||
if (databaseResult.rowCount < 1)
|
||||
return res.sendStatus(404);
|
||||
|
||||
return res.sendStatus(204);
|
||||
} catch (error: DatabaseError | Error | any) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
})
|
||||
|
||||
export default router;
|
||||
Loading…
Reference in New Issue