Creating Band edit page

This commit is contained in:
2024-10-26 14:35:33 +02:00
parent db72a94468
commit 76a98b8a16
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
account.post("/", (req: Request, res: Response) => {
account.post("/", async (req: Request, res: Response) => {
// Check if username is valid
if (!validateString(req.body.username, 4))
{
@@ -65,6 +65,15 @@ account.post("/", (req: Request, res: Response) => {
}
// Create account
await AccountRole.findOne({
where: {
name: "User"
}
})
.then(role => {
req.body["accountRoleId"] = role.id
})
Account.create(req.body)
.then(account => {
// Status: 201 Created

View File

@@ -134,3 +134,25 @@ band.get("/search", (req: Request, res: Response) => {
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')))
// Add delay for more realistic response times
// app.use((req, res, next) => {
// setTimeout(next, Math.floor((Math.random () * 2000) + 100))
// })
app.use((req, res, next) => {
setTimeout(next, Math.floor((Math.random () * 2000) + 100))
})
// Routes
app.use("/api", api)

View File

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

View File

@@ -22,6 +22,14 @@ const basketStore = useBasketStore()
</v-badge>
</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-cog" to="/preferences"/>
</template>

View File

@@ -1,4 +1,6 @@
import axios from "axios"
import { BandDetailsApiModel } from "../models/acts/bandDetailsApiModel"
import { BandModel } from "../models/acts/bandModel"
let BASE_URL = "http://localhost:3000/bands"
@@ -13,3 +15,11 @@ export async function fetchBandByName(bandName: string) {
export async function fetchBandsBySearchTerm(searchTerm: string) {
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,
////////// API Endpoint /products //////////
////////// API Endpoint /bands //////////
// Status: 201 Created
PRODUCTCREATESUCCESSFUL,
BANDSAVEDSUCCESSFUL,
// Status: 400 Bad request
PRODUCTCREATEERROR,
BANDSAVEDERROR,
// Status: 200 OK
PRODUCTDELETESUCCESSFUL,
BANDDELETESUCCESSFUL,
// Status: 400 Bad request
PRODUCTDELETEERROR
BANDDELETEERROR
}

View File

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

View File

@@ -32,7 +32,15 @@
"rating": "Bewertung | Bewertungen",
"bandMember": "Band Mitglieder",
"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": {
"tickets": "Ticket | Tickets"
@@ -136,7 +144,11 @@
"orderPlaceSuccessfull": "Bestellung erfolgreich aufgegeben",
"basketTicketAdded": "Ticket zum Warenkorb hinzugefügt",
"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": {
"sortBy": "Sortieren nach",

View File

@@ -32,7 +32,15 @@
"rating": "Rating | Ratings",
"bandMember": "Band Member",
"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": {
"tickets": "Ticket | Tickets"
@@ -136,7 +144,11 @@
"orderPlaceSuccessfull": "Order successfully placed",
"basketTicketAdded": "Added ticket to 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": {
"sortBy": "Sort by",

View File

@@ -13,7 +13,7 @@ async function registerAccount() {
accountStore.registerAccount()
.then(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">
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>
<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>

View File

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

View File

@@ -1,10 +1,12 @@
import { defineStore } from "pinia";
import { ref } from "vue";
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 { GenreModel } from "@/data/models/acts/genreModel";
import { fetchAllGenres } from "@/data/api/genreApi";
import { useFeedbackStore } from "./feedback.store";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
export const useBandStore = defineStore("bandStore", {
state: () => ({
@@ -21,7 +23,10 @@ export const useBandStore = defineStore("bandStore", {
availableGenres: ref<Array<GenreModel>>([]),
/** 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: {
@@ -66,11 +71,74 @@ export const useBandStore = defineStore("bandStore", {
async getBand(name: string) {
this.fetchInProgress = true
fetchBandByName(name)
await fetchBandByName(name)
.then(result => {
this.band = result.data
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: {
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.ACCOUNTREGISTERERROR:
case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE:
case BannerStateEnum.PRODUCTDELETESUCCESSFUL:
case BannerStateEnum.PRODUCTDELETEERROR:
case BannerStateEnum.BANDDELETEERROR:
case BannerStateEnum.BANDSAVEDERROR:
this.color = "red"
break;
@@ -154,8 +173,9 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL:
case BannerStateEnum.ORDERPLACESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATEERROR:
case BannerStateEnum.BANDDELETESUCCESSFUL:
case BannerStateEnum.BANDSAVEDSUCCESSFUL:
case BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL:
this.color = "green"
break;
@@ -192,14 +212,6 @@ export const useFeedbackStore = defineStore("feedbackStore", {
this.icon = "mdi-account"
break;
case BannerStateEnum.PRODUCTDELETESUCCESSFUL:
case BannerStateEnum.PRODUCTDELETEERROR:
case BannerStateEnum.PRODUCTCREATESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATEERROR:
this.icon = "mdi-store"
break;
case BannerStateEnum.EXERCISESOLVED01:
case BannerStateEnum.EXERCISESOLVED02:
case BannerStateEnum.EXERCISESOLVED11:
@@ -214,6 +226,7 @@ export const useFeedbackStore = defineStore("feedbackStore", {
this.icon = "mdi-check-circle-outline"
break;
case BannerStateEnum.DATABASERESETSUCCESSFUL:
this.icon = "mdi-database-refresh"
break;
@@ -242,6 +255,13 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
this.icon = "mdi-account-reactivate"
break;
case BannerStateEnum.BANDDELETEERROR:
case BannerStateEnum.BANDDELETESUCCESSFUL:
case BannerStateEnum.BANDSAVEDERROR:
case BannerStateEnum.BANDSAVEDSUCCESSFUL:
this.icon = "mdi-guitar-electric"
break;
}
this.showBanner = true