Added trainer and center admin views
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
0dfbc69f96
commit
016a01614a
@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div class="popup" @click="$emit('closePopup')">
|
||||||
|
<div class="Center" @click.stop>
|
||||||
|
<label for="name">Navn:</label>
|
||||||
|
<input type="text" v-model="center.name" name="name">
|
||||||
|
<label for="city">By:</label>
|
||||||
|
<input type="text" v-model="center.city" name="city">
|
||||||
|
<label for="zip_code">Postnummer:</label>
|
||||||
|
<input type="text" v-model="center.zip_code" name="zip_code">
|
||||||
|
<label for="address">Adresse:</label>
|
||||||
|
<input type="text" v-model="center.address" name="address">
|
||||||
|
<div class="updateButton" @click="updateCenter">Opdater</div>
|
||||||
|
<div class="closeButton" @click="$emit('closePopup')">x</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.popup {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #000a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Center {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 50%;
|
||||||
|
position: relative;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updateButton {
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.25em;
|
||||||
|
width: fit-content;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updateButton:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 20px;
|
||||||
|
font-size: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Center } from '@/interfaces/center';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "CenterPopup",
|
||||||
|
emits: ["closePopup", "fetchCenters"],
|
||||||
|
props: {
|
||||||
|
center: { type: Object as PropType<Center>, required: true }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updateCenter() {
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/center/${this.center.id}`, {
|
||||||
|
credentials: import.meta.env.DEV ? "include" : undefined,
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.center.name,
|
||||||
|
city: this.center.city,
|
||||||
|
zip_code: this.center.zip_code,
|
||||||
|
address: this.center.address
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "Application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
this.$router.push({ path: "/login", query: { ref: this.$route.path } });
|
||||||
|
}
|
||||||
|
else if (res.status === 200) {
|
||||||
|
this.$emit("fetchCenters");
|
||||||
|
this.$emit("closePopup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<div class="popup" @click="$emit('closePopup')">
|
||||||
|
<div class="Trainer" @click.stop>
|
||||||
|
<label for="price">Timepris (øre):</label>
|
||||||
|
<input type="number" min="0" step="100" v-model="trainer.hourly_price" name="price">
|
||||||
|
<label for="center">Center:</label>
|
||||||
|
<select v-model="trainer.center_id" name="center">
|
||||||
|
<option v-for="center of centers" :value="center.id">{{ center.name }}</option>
|
||||||
|
</select>
|
||||||
|
<div class="updateButton" @click="updateTrainer">Opdater</div>
|
||||||
|
<div class="closeButton" @click="$emit('closePopup')">x</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.popup {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #000a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Trainer {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 50%;
|
||||||
|
position: relative;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updateButton {
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.25em;
|
||||||
|
width: fit-content;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updateButton:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 20px;
|
||||||
|
font-size: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Center } from '@/interfaces/center';
|
||||||
|
import type { TrainerWithHourlyPrice as Trainer } from '@/interfaces/trainer';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TrainerPopup",
|
||||||
|
emits: ["closePopup", "fetchTrainers"],
|
||||||
|
props: {
|
||||||
|
trainer: { type: Object as PropType<Trainer>, required: true },
|
||||||
|
centers: { type: Object as PropType<Center[]>, required: true },
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updateTrainer() {
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/trainer/${this.trainer.id}`, {
|
||||||
|
credentials: import.meta.env.DEV ? "include" : undefined,
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
center_id: this.trainer.center_id,
|
||||||
|
hourly_price: this.trainer.hourly_price
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "Application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
this.$router.push({ path: "/login", query: { ref: this.$route.path } });
|
||||||
|
}
|
||||||
|
else if (res.status === 200) {
|
||||||
|
this.$emit("fetchTrainers");
|
||||||
|
this.$emit("closePopup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<div class="AdminCenters">
|
||||||
|
<div class="centers">
|
||||||
|
<div class="center" v-for="center of centers" :v-key="center.id" @click="showPopup(center)">
|
||||||
|
<div class="name">{{ center.name }}</div>
|
||||||
|
<div class="city">{{ center.city }}, {{ center.zip_code }}</div>
|
||||||
|
<div class="address">{{ center.address }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="newCenter">
|
||||||
|
<label for="name">Navn:</label>
|
||||||
|
<input type="text" v-model="name" name="name">
|
||||||
|
<label for="city">By:</label>
|
||||||
|
<input type="text" v-model="city" name="city">
|
||||||
|
<label for="zip_code">Postnummer:</label>
|
||||||
|
<input type="text" v-model="zip_code" name="zip_code">
|
||||||
|
<label for="address">Adresse:</label>
|
||||||
|
<input type="text" v-model="address" name="address">
|
||||||
|
<input type="submit" value="Tilføj" @click="addCenter">
|
||||||
|
</div>
|
||||||
|
<CenterPopup :center="selectedCenter" @fetchCenters="fetchCenters" @closePopup="popupShown = false"
|
||||||
|
v-if="popupShown"></CenterPopup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.AdminCenters {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newCenter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: fit-content;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centers {
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center:hover {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import CenterPopup from '@/components/CenterPopup.vue'
|
||||||
|
import type { Center } from '@/interfaces/center';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AdminCenters",
|
||||||
|
components: { CenterPopup },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: "",
|
||||||
|
city: "",
|
||||||
|
zip_code: "",
|
||||||
|
address: "",
|
||||||
|
popupShown: false,
|
||||||
|
selectedCenter: {} as Center,
|
||||||
|
centers: [] as Center[]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
await this.fetchCenters();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showPopup(center: Center) {
|
||||||
|
this.selectedCenter = { ...center };
|
||||||
|
this.popupShown = true;
|
||||||
|
},
|
||||||
|
async addCenter() {
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/center`, {
|
||||||
|
credentials: import.meta.env.DEV ? "include" : undefined,
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.name,
|
||||||
|
city: this.city,
|
||||||
|
zip_code: this.zip_code,
|
||||||
|
address: this.address
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "Application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
this.$router.push({ path: "/login", query: { ref: this.$route.path } });
|
||||||
|
}
|
||||||
|
else if (res.status === 200) {
|
||||||
|
this.fetchCenters();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchCenters() {
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/center`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
this.centers = await res.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<div class="AdminTrainers">
|
||||||
|
<div class="trainers">
|
||||||
|
<div class="trainer" v-for="trainer of trainers" :v-key="trainer.id" @click="showPopup(trainer)">
|
||||||
|
<div class="name">{{ trainer.first_name }} {{ trainer.last_name }}</div>
|
||||||
|
<div class="center">{{ trainer.center_name }}</div>
|
||||||
|
<div class="price">{{ formatPrice(trainer.hourly_price) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="newTrainer">
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="text" v-model="email" name="email">
|
||||||
|
<label for="price">Timepris (øre):</label>
|
||||||
|
<input type="number" min="0" step="100" v-model="hourly_price" name="price">
|
||||||
|
<label for="center">Center:</label>
|
||||||
|
<select v-model="center" name="center">
|
||||||
|
<option v-for="center of centers" :value="center.id">{{ center.name }}</option>
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Tilføj" @click="addTrainer">
|
||||||
|
</div>
|
||||||
|
<TrainerPopup :centers="centers" :trainer="selectedTrainer" @fetchTrainers="fetchTrainers"
|
||||||
|
@closePopup="popupShown = false" v-if="popupShown"></TrainerPopup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.AdminTrainers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newTrainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: fit-content;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainers {
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainer {
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainer:hover {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import TrainerPopup from '@/components/TrainerPopup.vue'
|
||||||
|
import type { Center } from '@/interfaces/center';
|
||||||
|
import type { TrainerWithHourlyPrice as Trainer } from '@/interfaces/trainer';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AdminCenters",
|
||||||
|
components: { TrainerPopup },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
popupShown: false,
|
||||||
|
selectedTrainer: {} as Trainer,
|
||||||
|
trainers: [] as Trainer[],
|
||||||
|
centers: [] as Center[],
|
||||||
|
center: -1,
|
||||||
|
email: "",
|
||||||
|
hourly_price: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
this.fetchCenters();
|
||||||
|
await this.fetchTrainers();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showPopup(trainer: Trainer) {
|
||||||
|
this.selectedTrainer = { ...trainer };
|
||||||
|
this.popupShown = true;
|
||||||
|
},
|
||||||
|
async addTrainer() {
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/trainer`, {
|
||||||
|
credentials: import.meta.env.DEV ? "include" : undefined,
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
center_id: this.center,
|
||||||
|
email: this.email,
|
||||||
|
hourly_price: this.hourly_price
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "Application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
this.$router.push({ path: "/login", query: { ref: this.$route.path } });
|
||||||
|
}
|
||||||
|
else if (res.status === 204) {
|
||||||
|
this.fetchTrainers();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchTrainers() {
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/admin/trainer`, {
|
||||||
|
credentials: import.meta.env.DEV ? "include" : undefined
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
this.trainers = await res.json();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchCenters() {
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_BASE_API_URL}/center`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
this.centers = await res.json();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatPrice(price: number): string {
|
||||||
|
const DanishKrone = new Intl.NumberFormat('dk', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'DKK',
|
||||||
|
});
|
||||||
|
|
||||||
|
return DanishKrone.format(price / 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
Reference in New Issue