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