Creating Band edit page

This commit is contained in:
2024-10-26 14:35:33 +02:00
parent fedb821a72
commit cdb3f02156
16 changed files with 377 additions and 41 deletions

View File

@@ -43,7 +43,7 @@ account.post("/login", (req: Request, res: Response) => {
}) })
// Creating a new user // Creating a new user
account.post("/", (req: Request, res: Response) => { account.post("/", async (req: Request, res: Response) => {
// Check if username is valid // Check if username is valid
if (!validateString(req.body.username, 4)) if (!validateString(req.body.username, 4))
{ {
@@ -65,6 +65,15 @@ account.post("/", (req: Request, res: Response) => {
} }
// Create account // Create account
await AccountRole.findOne({
where: {
name: "User"
}
})
.then(role => {
req.body["accountRoleId"] = role.id
})
Account.create(req.body) Account.create(req.body)
.then(account => { .then(account => {
// Status: 201 Created // Status: 201 Created

View File

@@ -134,3 +134,25 @@ band.get("/search", (req: Request, res: Response) => {
res.status(200).json(bands) res.status(200).json(bands)
}) })
}) })
// Edit band
band.patch("/", (req: Request, res: Response) => {
Band.update(req.body, {
where: {
id: req.body.id
}
})
.then(result => {
res.status(200).json(result)
})
})
// New band
band.post("/", (req: Request, res: Response) => {
Band.create(req.body)
.then(result => {
res.status(200).json(result)
})
})

View File

@@ -29,9 +29,9 @@ const path = require('path')
app.use('/static', express.static(path.join(__dirname, 'images'))) app.use('/static', express.static(path.join(__dirname, 'images')))
// Add delay for more realistic response times // Add delay for more realistic response times
// app.use((req, res, next) => { app.use((req, res, next) => {
// setTimeout(next, Math.floor((Math.random () * 2000) + 100)) setTimeout(next, Math.floor((Math.random () * 2000) + 100))
// }) })
// Routes // Routes
app.use("/api", api) app.use("/api", api)

View File

@@ -44,7 +44,7 @@ defineProps({
<v-container> <v-container>
<v-row> <v-row>
<v-col > <v-col>
<slot></slot> <slot></slot>
</v-col> </v-col>
</v-row> </v-row>

View File

@@ -22,6 +22,14 @@ const basketStore = useBasketStore()
</v-badge> </v-badge>
</div> </div>
<v-btn
v-if="accountStore.userAccount.accountRole != null &&
accountStore.userAccount.accountRole.privilegeAdminPanel"
variant="plain"
icon="mdi-table-cog"
to="/admin"
/>
<v-btn variant="plain" icon="mdi-help" to="/help" /> <v-btn variant="plain" icon="mdi-help" to="/help" />
<v-btn variant="plain" icon="mdi-cog" to="/preferences"/> <v-btn variant="plain" icon="mdi-cog" to="/preferences"/>
</template> </template>

View File

@@ -1,4 +1,6 @@
import axios from "axios" import axios from "axios"
import { BandDetailsApiModel } from "../models/acts/bandDetailsApiModel"
import { BandModel } from "../models/acts/bandModel"
let BASE_URL = "http://localhost:3000/bands" let BASE_URL = "http://localhost:3000/bands"
@@ -13,3 +15,11 @@ export async function fetchBandByName(bandName: string) {
export async function fetchBandsBySearchTerm(searchTerm: string) { export async function fetchBandsBySearchTerm(searchTerm: string) {
return await axios.get(BASE_URL + '/search?value=' + searchTerm) return await axios.get(BASE_URL + '/search?value=' + searchTerm)
} }
export async function postBand(band: BandModel) {
return await axios.post(BASE_URL, band)
}
export async function patchBand(band: BandModel) {
return await axios.patch(BASE_URL, band)
}

View File

@@ -76,17 +76,13 @@ export enum BannerStateEnum {
ORDERPLACESUCCESSFUL, ORDERPLACESUCCESSFUL,
////////// API Endpoint /products ////////// ////////// API Endpoint /bands //////////
// Status: 201 Created BANDSAVEDSUCCESSFUL,
PRODUCTCREATESUCCESSFUL,
// Status: 400 Bad request BANDSAVEDERROR,
PRODUCTCREATEERROR,
// Status: 200 OK BANDDELETESUCCESSFUL,
PRODUCTDELETESUCCESSFUL,
// Status: 400 Bad request BANDDELETEERROR
PRODUCTDELETEERROR
} }

View File

@@ -1,5 +1,5 @@
export class BandModel { export class BandModel {
id: number = -1 id: number
name: string = "" name: string = ""
foundingYear: number = 1900 foundingYear: number = 1900
descriptionEn: string = "" descriptionEn: string = ""

View File

@@ -32,7 +32,15 @@
"rating": "Bewertung | Bewertungen", "rating": "Bewertung | Bewertungen",
"bandMember": "Band Mitglieder", "bandMember": "Band Mitglieder",
"image": "Foto | Fotos", "image": "Foto | Fotos",
"genre": "Genre | Genres" "genre": "Genre | Genres",
"name": "Bandname",
"editBand": "Band bearbeiten",
"foundingYear": "Gründungsjahr",
"descriptionDe": "Beschreibung Deutsch",
"descriptionEn": "Beschreibung Englisch",
"addNewBand": "Neue Band hinzufügen",
"logo": "Band Logo",
"imageMember": "Bilder Mitglieder"
}, },
"ticket": { "ticket": {
"tickets": "Ticket | Tickets" "tickets": "Ticket | Tickets"
@@ -136,7 +144,11 @@
"orderPlaceSuccessfull": "Bestellung erfolgreich aufgegeben", "orderPlaceSuccessfull": "Bestellung erfolgreich aufgegeben",
"basketTicketAdded": "Ticket zum Warenkorb hinzugefügt", "basketTicketAdded": "Ticket zum Warenkorb hinzugefügt",
"basketTicketRemoved": "Ticket aus Warenkorb entfernt", "basketTicketRemoved": "Ticket aus Warenkorb entfernt",
"exerciseSolvedNr": "Aufgabe {0}.{1} gelöst!" "exerciseSolvedNr": "Aufgabe {0}.{1} gelöst!",
"bandDeleteError": "Fehler beim Löschen der Band",
"bandDeleteSuccessful": "Band erfolgreich gelöscht",
"bandSavedError": "Fehler beim Speichern der Band",
"bandSavedSuccessful": "Band erfolgreich gespeichert"
}, },
"misc": { "misc": {
"sortBy": "Sortieren nach", "sortBy": "Sortieren nach",

View File

@@ -32,7 +32,15 @@
"rating": "Rating | Ratings", "rating": "Rating | Ratings",
"bandMember": "Band Member", "bandMember": "Band Member",
"image": "Photo | Photos", "image": "Photo | Photos",
"genre": "Genre | Genres" "genre": "Genre | Genres",
"name": "Name of band",
"editBand": "Edit band",
"foundingYear": "Founding Year",
"descriptionDe": "Description German",
"descriptionEn": "Description English",
"addNewBand": "Add new Band",
"logo": "Band logo",
"imageMember": "Images Members"
}, },
"ticket": { "ticket": {
"tickets": "Ticket | Tickets" "tickets": "Ticket | Tickets"
@@ -136,7 +144,11 @@
"orderPlaceSuccessfull": "Order successfully placed", "orderPlaceSuccessfull": "Order successfully placed",
"basketTicketAdded": "Added ticket to basket", "basketTicketAdded": "Added ticket to basket",
"basketTicketRemoved": "Removed ticket from basket", "basketTicketRemoved": "Removed ticket from basket",
"exerciseSolvedNr": "Exercise {0}.{1} solved!" "exerciseSolvedNr": "Exercise {0}.{1} solved!",
"bandDeleteError": "Error on deleting band",
"bandDeleteSuccessful": "Band successfully deleted",
"bandSavedError": "Error on saving band",
"bandSavedSuccessful": "Band successfully saved"
}, },
"misc": { "misc": {
"sortBy": "Sort by", "sortBy": "Sort by",

View File

@@ -13,7 +13,7 @@ async function registerAccount() {
accountStore.registerAccount() accountStore.registerAccount()
.then(result => { .then(result => {
if (result) { if (result) {
router.push("/account/home") showRegisterCard.value = false
} }
}) })
} }

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
import OutlinedButton from '@/components/basics/outlinedButton.vue';
import { GenreModel } from '@/data/models/acts/genreModel';
import { useBandStore } from '@/stores/band.store';
const bandStore = useBandStore()
function itemProps(item: GenreModel) {
return {
title: item.name
}
}
</script>
<template>
<action-dialog
v-model="bandStore.showBandEditDialog"
:title="$t('band.editBand')"
icon="mdi-pencil"
>
<v-container>
<v-row>
<v-col>
<v-text-field
:label="$t('band.name')"
v-model="bandStore.band.name"
variant="outlined"
hide-details
/>
</v-col>
<v-col>
<v-text-field
:label="$t('band.foundingYear')"
v-model="bandStore.band.foundingYear"
variant="outlined"
hide-details
/>
</v-col>
<v-col>
<v-select
:label="$t('band.genre', 2)"
v-model="bandStore.band.genres"
:items="bandStore.availableGenres"
:item-props="itemProps"
variant="outlined"
hide-details
chips
multiple
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-textarea
:label="$t('band.descriptionDe')"
v-model="bandStore.band.descriptionDe"
variant="outlined"
hide-details
/>
</v-col>
<v-col>
<v-textarea
:label="$t('band.descriptionEn')"
v-model="bandStore.band.descriptionEn"
variant="outlined"
hide-details
/>
</v-col>
</v-row>
</v-container>
<template #actions>
<outlined-button
color="green"
@click="bandStore.saveBand"
:loading="bandStore.fetchInProgress"
>
{{ $t('misc.actions.save') }}
</outlined-button>
</template>
</action-dialog>
</template>

View File

@@ -1,6 +1,102 @@
<script setup lang="ts"> <script setup lang="ts">
import { useBandStore } from '@/stores/band.store';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import bandEditDialog from './bandEditDialog.vue';
import { useRouter } from 'vue-router';
import { useFeedbackStore } from '@/stores/feedback.store';
const bandStore = useBandStore()
const router = useRouter()
const feedbackStore = useFeedbackStore()
const headers = [
{ title: feedbackStore.i18n.t('band.name'), value: "name" },
{ title: feedbackStore.i18n.t('band.foundingYear'), value: "foundingYear" },
{ title: feedbackStore.i18n.t('band.genre', 2), value: "genres" },
{ title: feedbackStore.i18n.t('band.logo', 2), value: "logo" },
{ title: feedbackStore.i18n.t('band.imageMember', 2), value: "imageMembers" },
{ title: feedbackStore.i18n.t('band.image', 2), value: "images" },
{ title: feedbackStore.i18n.t('concert.concert', 2), value: "nrOfConcerts" },
{ title: "", value: "edit", width: 130 },
]
bandStore.getBands()
</script> </script>
<template> <template>
Bands Admin Page <v-container>
<v-row>
<v-col>
<outlined-button
prepend-icon="mdi-arrow-left"
@click="router.go(-1)"
>
{{ $t('misc.onePageBack') }}
</outlined-button>
</v-col>
<v-col class="text-end">
<outlined-button
prepend-icon="mdi-plus"
color="green"
:disabled="bandStore.fetchInProgress"
@click="bandStore.newBand"
>
{{ $t('band.addNewBand') }}
</outlined-button>
</v-col>
</v-row>
<v-row>
<v-col>
<v-data-table
:items="bandStore.bands"
:headers="headers"
:loading="bandStore.fetchInProgress"
>
<template #item.genres="{ item }">
<v-chip v-for="genre of item.genres" class="mx-1">
{{ genre.name }}
</v-chip>
</template>
<template #item.logo="{ item }">
<v-icon
:icon="item.logo != '' ? 'mdi-check' : 'mdi-close'"
:color="item.logo != '' ? 'green' : 'red'"
/>
</template>
<template #item.imageMembers="{ item }">
<v-icon
:icon="item.imageMembers != '' ? 'mdi-check' : 'mdi-close'"
:color="item.imageMembers != '' ? 'green' : 'red'"
/>
</template>
<template #item.images="{ item }">
{{ item.images.length }} {{ $t('band.image', item.images.length) }}
</template>
<template #item.edit="{ item }">
<v-btn
icon="mdi-pencil"
variant="plain"
color="orange"
@click="bandStore.editBand(item.name)"
/>
<v-btn
icon="mdi-delete"
variant="plain"
color="red"
@click="bandStore.deleteBand(item.id)"
/>
</template>
</v-data-table>
</v-col>
</v-row>
</v-container>
<band-edit-dialog />
</template> </template>

View File

@@ -95,11 +95,7 @@ export const useAccountStore = defineStore("accountStore", {
password: this.registerData.password password: this.registerData.password
} }
await this.login()
.then(result => {
this.fetchInProgress = false this.fetchInProgress = false
return true
})
}) })
.catch((error) => { .catch((error) => {
if (error.status == 400) { if (error.status == 400) {

View File

@@ -1,10 +1,12 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
import { BandApiModel } from "../data/models/acts/bandApiModel"; import { BandApiModel } from "../data/models/acts/bandApiModel";
import { fetchAllBands, fetchBandByName } from "../data/api/bandApi"; import { fetchAllBands, fetchBandByName, patchBand, postBand } from "../data/api/bandApi";
import { BandDetailsApiModel } from "../data/models/acts/bandDetailsApiModel"; import { BandDetailsApiModel } from "../data/models/acts/bandDetailsApiModel";
import { GenreModel } from "@/data/models/acts/genreModel"; import { GenreModel } from "@/data/models/acts/genreModel";
import { fetchAllGenres } from "@/data/api/genreApi"; import { fetchAllGenres } from "@/data/api/genreApi";
import { useFeedbackStore } from "./feedback.store";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
export const useBandStore = defineStore("bandStore", { export const useBandStore = defineStore("bandStore", {
state: () => ({ state: () => ({
@@ -21,7 +23,10 @@ export const useBandStore = defineStore("bandStore", {
availableGenres: ref<Array<GenreModel>>([]), availableGenres: ref<Array<GenreModel>>([]),
/** Request to server sent, waiting for data response */ /** Request to server sent, waiting for data response */
fetchInProgress: ref(false) fetchInProgress: ref(false),
/** Show or hide the edit dialog for edit a band */
showBandEditDialog: ref(false)
}), }),
actions: { actions: {
@@ -66,11 +71,74 @@ export const useBandStore = defineStore("bandStore", {
async getBand(name: string) { async getBand(name: string) {
this.fetchInProgress = true this.fetchInProgress = true
fetchBandByName(name) await fetchBandByName(name)
.then(result => { .then(result => {
this.band = result.data this.band = result.data
this.fetchInProgress = false this.fetchInProgress = false
}) })
},
/**
* Prepare edit dialog for new band, opens it
*/
newBand() {
this.band = new BandDetailsApiModel()
this.showBandEditDialog = true
},
/**
* Edit a band. Fetch all information about the band, opens the edit dialog
*
* @param name Name of band to edit
*/
async editBand(name: string) {
await this.getBand(name)
this.showBandEditDialog = true
},
/**
* Save band in this store to the database
*/
saveBand() {
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
if (this.band.id == undefined) {
postBand(this.band)
.then(result => {
if (result.status == 200) {
feedbackStore.changeBanner(BannerStateEnum.BANDSAVEDSUCCESSFUL)
this.getBands()
this.showBandEditDialog = false
} else {
feedbackStore.changeBanner(BannerStateEnum.BANDSAVEDERROR)
}
})
} else {
patchBand(this.band)
.then(result => {
if (result.status == 200) {
feedbackStore.changeBanner(BannerStateEnum.BANDSAVEDSUCCESSFUL)
this.getBands()
this.showBandEditDialog = false
} else {
feedbackStore.changeBanner(BannerStateEnum.BANDSAVEDERROR)
}
})
}
},
/**
* Delete a band by it's identifier
*
* @param id Id of the band in the database
*/
deleteBand(id: number) {
// todo
} }
} }
}) })

View File

@@ -131,6 +131,25 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.ORDERPLACESUCCESSFUL: { case BannerStateEnum.ORDERPLACESUCCESSFUL: {
this.title = this.i18n.t('bannerMessages.orderPlaceSuccessfull'); break; this.title = this.i18n.t('bannerMessages.orderPlaceSuccessfull'); break;
} }
////////// API Endpoint /bands //////////
case BannerStateEnum.BANDDELETEERROR: {
this.title = this.i18n.t('bannerMessages.bandDeleteError'); break;
}
case BannerStateEnum.BANDDELETESUCCESSFUL: {
this.title = this.i18n.t('bannerMessages.bandDeleteSuccessful'); break;
}
case BannerStateEnum.BANDSAVEDERROR: {
this.title = this.i18n.t('bannerMessages.bandSavedError'); break;
}
case BannerStateEnum.BANDSAVEDSUCCESSFUL: {
this.title = this.i18n.t('bannerMessages.bandSavedSuccessful'); break;
}
} }
@@ -142,8 +161,8 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN: case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN:
case BannerStateEnum.ACCOUNTREGISTERERROR: case BannerStateEnum.ACCOUNTREGISTERERROR:
case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE: case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE:
case BannerStateEnum.PRODUCTDELETESUCCESSFUL: case BannerStateEnum.BANDDELETEERROR:
case BannerStateEnum.PRODUCTDELETEERROR: case BannerStateEnum.BANDSAVEDERROR:
this.color = "red" this.color = "red"
break; break;
@@ -154,8 +173,9 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL: case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL: case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL:
case BannerStateEnum.ORDERPLACESUCCESSFUL: case BannerStateEnum.ORDERPLACESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATESUCCESSFUL: case BannerStateEnum.BANDDELETESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATEERROR: case BannerStateEnum.BANDSAVEDSUCCESSFUL:
case BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL:
this.color = "green" this.color = "green"
break; break;
@@ -192,14 +212,6 @@ export const useFeedbackStore = defineStore("feedbackStore", {
this.icon = "mdi-account" this.icon = "mdi-account"
break; break;
case BannerStateEnum.PRODUCTDELETESUCCESSFUL:
case BannerStateEnum.PRODUCTDELETEERROR:
case BannerStateEnum.PRODUCTCREATESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATEERROR:
this.icon = "mdi-store"
break;
case BannerStateEnum.EXERCISESOLVED01: case BannerStateEnum.EXERCISESOLVED01:
case BannerStateEnum.EXERCISESOLVED02: case BannerStateEnum.EXERCISESOLVED02:
case BannerStateEnum.EXERCISESOLVED11: case BannerStateEnum.EXERCISESOLVED11:
@@ -214,6 +226,7 @@ export const useFeedbackStore = defineStore("feedbackStore", {
this.icon = "mdi-check-circle-outline" this.icon = "mdi-check-circle-outline"
break; break;
case BannerStateEnum.DATABASERESETSUCCESSFUL: case BannerStateEnum.DATABASERESETSUCCESSFUL:
this.icon = "mdi-database-refresh" this.icon = "mdi-database-refresh"
break; break;
@@ -242,6 +255,13 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL: case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
this.icon = "mdi-account-reactivate" this.icon = "mdi-account-reactivate"
break; break;
case BannerStateEnum.BANDDELETEERROR:
case BannerStateEnum.BANDDELETESUCCESSFUL:
case BannerStateEnum.BANDSAVEDERROR:
case BannerStateEnum.BANDSAVEDSUCCESSFUL:
this.icon = "mdi-guitar-electric"
break;
} }
this.showBanner = true this.showBanner = true