New page for all concerts

This commit is contained in:
2024-10-12 19:40:12 +02:00
parent 3204e4a658
commit 60a9cea147
56 changed files with 531 additions and 405 deletions

View File

@@ -1,6 +1,6 @@
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.17 Chrome/128.0.6613.36 Electron/32.0.1 Safari/537.36" version="24.7.17"> <mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.17 Chrome/128.0.6613.36 Electron/32.0.1 Safari/537.36" version="24.7.17">
<diagram name="Page-1" id="WevClHWmhzPAQ7FDN5po"> <diagram name="Page-1" id="WevClHWmhzPAQ7FDN5po">
<mxGraphModel dx="3437" dy="1720" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"> <mxGraphModel dx="3676" dy="1858" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root> <root>
<mxCell id="0" /> <mxCell id="0" />
<mxCell id="1" parent="0" /> <mxCell id="1" parent="0" />
@@ -140,7 +140,7 @@
<mxCell id="EQeajuEG8KHzwlrw_xps-44" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;name: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="EQeajuEG8KHzwlrw_xps-44" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;name: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
<mxGeometry x="-597.97" y="450" width="160" height="30" as="geometry" /> <mxGeometry x="-597.97" y="450" width="160" height="30" as="geometry" />
</mxCell> </mxCell>
<mxCell id="EQeajuEG8KHzwlrw_xps-47" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;image: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="EQeajuEG8KHzwlrw_xps-47" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;imageIndoor: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
<mxGeometry x="-597.97" y="510" width="160" height="30" as="geometry" /> <mxGeometry x="-597.97" y="510" width="160" height="30" as="geometry" />
</mxCell> </mxCell>
<mxCell id="EQeajuEG8KHzwlrw_xps-48" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;address: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="EQeajuEG8KHzwlrw_xps-48" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;address: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
@@ -562,8 +562,8 @@
<mxPoint x="-114.13999999999999" y="240" as="sourcePoint" /> <mxPoint x="-114.13999999999999" y="240" as="sourcePoint" />
<mxPoint x="-1638.2900000000004" y="280" as="targetPoint" /> <mxPoint x="-1638.2900000000004" y="280" as="targetPoint" />
<Array as="points"> <Array as="points">
<mxPoint x="-1598.1399999999999" y="-85" /> <mxPoint x="-1598" y="-85" />
<mxPoint x="-1598.1399999999999" y="275" /> <mxPoint x="-1598" y="115" />
</Array> </Array>
</mxGeometry> </mxGeometry>
</mxCell> </mxCell>
@@ -584,7 +584,7 @@
<mxGeometry x="-457.08000000000004" y="285" width="9.43" height="20" as="geometry" /> <mxGeometry x="-457.08000000000004" y="285" width="9.43" height="20" as="geometry" />
</mxCell> </mxCell>
<mxCell id="XtTKw7a9ly-3XTzy1tri-23" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;cityId: Number&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="XtTKw7a9ly-3XTzy1tri-23" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;cityId: Number&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
<mxGeometry x="-597.97" y="540" width="160" height="30" as="geometry" /> <mxGeometry x="-597.97" y="600" width="160" height="30" as="geometry" />
</mxCell> </mxCell>
<mxCell id="XtTKw7a9ly-3XTzy1tri-24" value="&lt;blockquote style=&quot;margin: 0px; border: none; padding: 0px;&quot;&gt;&lt;b&gt;&lt;u&gt;Cities&lt;/u&gt;&lt;/b&gt;&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=center;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="XtTKw7a9ly-3XTzy1tri-24" value="&lt;blockquote style=&quot;margin: 0px; border: none; padding: 0px;&quot;&gt;&lt;b&gt;&lt;u&gt;Cities&lt;/u&gt;&lt;/b&gt;&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=center;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="-358.1399999999999" y="470" width="160" height="30" as="geometry" /> <mxGeometry x="-358.1399999999999" y="470" width="160" height="30" as="geometry" />
@@ -599,15 +599,15 @@
<mxGeometry x="-217.86000000000004" y="505" width="9.43" height="20" as="geometry" /> <mxGeometry x="-217.86000000000004" y="505" width="9.43" height="20" as="geometry" />
</mxCell> </mxCell>
<mxCell id="XtTKw7a9ly-3XTzy1tri-28" value="" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.security.key_permissions;fillColor=#CCCCCC;" parent="1" vertex="1"> <mxCell id="XtTKw7a9ly-3XTzy1tri-28" value="" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.security.key_permissions;fillColor=#CCCCCC;" parent="1" vertex="1">
<mxGeometry x="-457.69" y="545" width="9.43" height="20" as="geometry" /> <mxGeometry x="-457.69" y="605" width="9.43" height="20" as="geometry" />
</mxCell> </mxCell>
<mxCell id="XtTKw7a9ly-3XTzy1tri-29" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="XtTKw7a9ly-3XTzy1tri-23" target="XtTKw7a9ly-3XTzy1tri-26" edge="1"> <mxCell id="XtTKw7a9ly-3XTzy1tri-29" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="XtTKw7a9ly-3XTzy1tri-23" target="XtTKw7a9ly-3XTzy1tri-26" edge="1">
<mxGeometry x="389.35999999999996" y="350" as="geometry"> <mxGeometry x="389.35999999999996" y="350" as="geometry">
<mxPoint x="-779.74" y="570" as="sourcePoint" /> <mxPoint x="-779.74" y="570" as="sourcePoint" />
<mxPoint x="-779.74" y="240" as="targetPoint" /> <mxPoint x="-779.74" y="240" as="targetPoint" />
<Array as="points"> <Array as="points">
<mxPoint x="-398.14" y="555" /> <mxPoint x="-398" y="615" />
<mxPoint x="-398.14" y="515" /> <mxPoint x="-398" y="515" />
</Array> </Array>
</mxGeometry> </mxGeometry>
</mxCell> </mxCell>
@@ -618,7 +618,7 @@
</mxCell> </mxCell>
<mxCell id="XtTKw7a9ly-3XTzy1tri-31" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="XtTKw7a9ly-3XTzy1tri-29" connectable="0" vertex="1"> <mxCell id="XtTKw7a9ly-3XTzy1tri-31" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="XtTKw7a9ly-3XTzy1tri-29" connectable="0" vertex="1">
<mxGeometry x="1" relative="1" as="geometry"> <mxGeometry x="1" relative="1" as="geometry">
<mxPoint x="16" y="-30" as="offset" /> <mxPoint x="-12" y="-35" as="offset" />
</mxGeometry> </mxGeometry>
</mxCell> </mxCell>
<mxCell id="XtTKw7a9ly-3XTzy1tri-32" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;country: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="XtTKw7a9ly-3XTzy1tri-32" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;country: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
@@ -645,8 +645,8 @@
<mxCell id="6UZwBTI8mqE8-S_CcwA3-35" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;name: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="6UZwBTI8mqE8-S_CcwA3-35" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;name: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
<mxGeometry x="-376.94999999999993" y="697" width="160" height="30" as="geometry" /> <mxGeometry x="-376.94999999999993" y="697" width="160" height="30" as="geometry" />
</mxCell> </mxCell>
<mxCell id="6UZwBTI8mqE8-S_CcwA3-36" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;seatSchema: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="6UZwBTI8mqE8-S_CcwA3-36" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;imageOutdoor: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
<mxGeometry x="-597.97" y="570" width="160" height="30" as="geometry" /> <mxGeometry x="-597.97" y="540" width="160" height="30" as="geometry" />
</mxCell> </mxCell>
<mxCell id="6UZwBTI8mqE8-S_CcwA3-37" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;locationId: Number&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1"> <mxCell id="6UZwBTI8mqE8-S_CcwA3-37" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;locationId: Number&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
<mxGeometry x="-376.94999999999993" y="667" width="160" height="30" as="geometry" /> <mxGeometry x="-376.94999999999993" y="667" width="160" height="30" as="geometry" />
@@ -838,6 +838,9 @@
<mxCell id="q2-4bsxMYXESQ5WjL4G4-1" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;name: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1"> <mxCell id="q2-4bsxMYXESQ5WjL4G4-1" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;name: String&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
<mxGeometry x="-597.97" y="130" width="160" height="30" as="geometry" /> <mxGeometry x="-597.97" y="130" width="160" height="30" as="geometry" />
</mxCell> </mxCell>
<mxCell id="q2-4bsxMYXESQ5WjL4G4-3" value="&lt;blockquote style=&quot;margin: 0px 0px 0px 8px; border: none; padding: 0px;&quot;&gt;layout: Number&lt;/blockquote&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
<mxGeometry x="-597.97" y="570" width="160" height="30" as="geometry" />
</mxCell>
</root> </root>
</mxGraphModel> </mxGraphModel>
</diagram> </diagram>

View File

@@ -43,7 +43,7 @@ export const sequelize = new Sequelize({
}) })
export function startDatabase() { export function startDatabase() {
let recreateDb = true let recreateDb = false
// Create database and tables // Create database and tables
sequelize.sync({ force: recreateDb }) sequelize.sync({ force: recreateDb })

View File

@@ -8,16 +8,16 @@ import { Concert } from "./concert.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class Band extends Model { export class Band extends Model {
@Column @Column
name: String name: string
@Column @Column
foundingYear: Number foundingYear: number
@Column @Column
descriptionEn: String descriptionEn: string
@Column @Column
descriptionDe: String descriptionDe: string
@Column({ @Column({
type: DataType.STRING, type: DataType.STRING,
@@ -28,13 +28,13 @@ export class Band extends Model {
this.setDataValue('images', value.join(';')) this.setDataValue('images', value.join(';'))
} }
}) })
images: Array<String> images: Array<string>
@Column @Column
imageMembers: String imageMembers: string
@Column @Column
logo: String logo: string
// Relations // Relations

View File

@@ -6,34 +6,37 @@ import { Band } from "./band.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class Concert extends Model { export class Concert extends Model {
@Column @Column
date: String date: string
@Column @Column
name: String name: string
@Column @Column
price: Number price: number
@Column @Column
image: String image: string
@Column @Column
inStock: Number inStock: number
@Column @Column
offered: Boolean offered: boolean
@ForeignKey(() => Band) @ForeignKey(() => Band)
@Column @Column
bandId: Number bandId: number
@ForeignKey(() => Location) @ForeignKey(() => Location)
@Column @Column
locationId: Number locationId: number
// Relations // Relations
@BelongsTo(() => Band)
band: Band
@BelongsTo(() => Location) @BelongsTo(() => Location)
location: Location location: Location

View File

@@ -5,7 +5,7 @@ import { BandGenre } from "./bandGenre.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class Genre extends Model { export class Genre extends Model {
@Column @Column
name: String name: string
// Relations // Relations

View File

@@ -4,14 +4,14 @@ import { Band } from "./band.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class Member extends Model { export class Member extends Model {
@Column @Column
name: String name: string
@ForeignKey(() => Band) @ForeignKey(() => Band)
@Column @Column
bandId: Number bandId: number
@Column @Column
image: String image: string
// Relations // Relations

View File

@@ -7,14 +7,14 @@ export class Rating extends Model {
@ForeignKey(() => Account) @ForeignKey(() => Account)
@Column @Column
accountId: Number accountId: number
@Column @Column
rating: Number rating: number
@ForeignKey(() => Band) @ForeignKey(() => Band)
@Column @Column
bandId: Number bandId: number
// Relations // Relations

View File

@@ -4,26 +4,26 @@ import { ExerciseGroup } from "./exerciseGroup.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class Exercise extends Model { export class Exercise extends Model {
@Column @Column
nameDe: String nameDe: string
@Column @Column
nameEn: String nameEn: string
@Column @Column
exerciseNr: Number exerciseNr: number
@Column @Column
descriptionDe: String descriptionDe: string
@Column @Column
descriptionEn: String descriptionEn: string
@Column @Column
solved: Boolean solved: boolean
@ForeignKey(() => ExerciseGroup) @ForeignKey(() => ExerciseGroup)
@Column @Column
exerciseGroupId: Number exerciseGroupId: number
// Relations // Relations

View File

@@ -4,13 +4,13 @@ import { Exercise } from "./exercise.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class ExerciseGroup extends Model { export class ExerciseGroup extends Model {
@Column @Column
nameDe: String nameDe: string
@Column @Column
nameEn: String nameEn: string
@Column @Column
groupNr: Number groupNr: number
// Relations // Relations

View File

@@ -4,10 +4,10 @@ import { Location } from "./location.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class City extends Model { export class City extends Model {
@Column @Column
name: String name: string
@Column @Column
country: String country: string
// Relations // Relations

View File

@@ -9,20 +9,20 @@ export class Location extends Model {
urlName: string urlName: string
@Column @Column
name: String name: string
@Column @Column
address: String address: string
@ForeignKey(() => City) @ForeignKey(() => City)
@Column @Column
cityId: Number cityId: number
@Column @Column
imageIndoor: String imageIndoor: string
@Column @Column
imageOutdoor: String imageOutdoor: string
/** /**
* Layout identifier of the location * Layout identifier of the location
@@ -31,7 +31,7 @@ export class Location extends Model {
* 3 = Stage in the middle of the stay area, seat places all around * 3 = Stage in the middle of the stay area, seat places all around
*/ */
@Column @Column
layout: Number layout: number
// Relations // Relations

View File

@@ -9,7 +9,7 @@ export class Seat extends Model {
@ForeignKey(() => SeatRow) @ForeignKey(() => SeatRow)
@Column @Column
seatRowId: Number seatRowId: number
// Relations // Relations

View File

@@ -5,21 +5,21 @@ import { SeatRow } from "./seatRow.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class SeatGroup extends Model { export class SeatGroup extends Model {
@Column @Column
name: String name: string
@Column @Column
surcharge: Number surcharge: number
@Column @Column
capacity: Number capacity: number
@Default(false) @Default(false)
@Column @Column
standingArea: Boolean standingArea: boolean
@ForeignKey(() => Location) @ForeignKey(() => Location)
@Column @Column
locationId: Number locationId: number
// Relations // Relations

View File

@@ -7,6 +7,7 @@ import { Concert } from "../models/acts/concert.model";
import { Location } from "../models/locations/location.model"; import { Location } from "../models/locations/location.model";
import { City } from "../models/locations/city.model"; import { City } from "../models/locations/city.model";
import { Op } from "sequelize"; import { Op } from "sequelize";
import { calcOverallRating, calcRatingValues } from "../scripts/calcScripts";
export const band = Router() export const band = Router()
@@ -14,37 +15,33 @@ export const band = Router()
band.get("/", (req: Request, res: Response) => { band.get("/", (req: Request, res: Response) => {
Band.findAll({ Band.findAll({
include: [ include: [
{
model: Member,
attributes: {
exclude: [ "id", "bandId" ]
}
},
{ {
model: Rating, model: Rating,
attributes: {
exclude: [ "id", "bandId" ]
}
}, },
{ {
model: Concert, model: Genre,
include: [
{
model: Location,
include: [ City ],
attributes: {
exclude: [ "id" ]
}
}
],
attributes: { attributes: {
exclude: [ "id", "tourId", "locationId" ] exclude: [ "id" ]
} }
}, },
Genre Concert
] ]
}) })
.then(bands => { .then(bands => {
for (let band of bands) {
band.dataValues["nrOfConcerts"] = band.dataValues.concerts.length
band.dataValues["rating"] = calcOverallRating(band.dataValues.ratings)
// Delete unnecessary Arrays
delete band.dataValues.ratings
delete band.dataValues.concerts
for (let genre of band.dataValues.genres) {
delete genre.dataValues.BandGenre
}
}
res.status(200).json(bands) res.status(200).json(bands)
}) })
}) })
@@ -90,6 +87,16 @@ band.get("/band/:name", (req: Request, res: Response) => {
} }
}) })
.then(band => { .then(band => {
band.dataValues["rating"] = calcOverallRating(band.dataValues.ratings)
band.dataValues["ratingValues"] = calcRatingValues(band.dataValues.ratings)
// Delete unnecessary Arrays
delete band.dataValues.ratings
for (let genre of band.dataValues.genres) {
delete genre.dataValues.BandGenre
}
res.status(200).json(band) res.status(200).json(band)
}) })
}) })

View File

@@ -1,26 +1,11 @@
import { Location } from "../models/locations/location.model";
import { City } from "../models/locations/city.model"; import { City } from "../models/locations/city.model";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { Concert } from "../models/acts/concert.model";
export const city = Router() export const city = Router()
city.get("/", (req: Request, res: Response) => { city.get("/", (req: Request, res: Response) => {
City.findAll({ City.findAll()
include: [
{
model: Location,
include: [ Concert ]
}
]
})
.then(cities => { .then(cities => {
// for (let city of cities) {
// for (let location of city.dataValues.locations) {
// location.dataValues.nrOfConcerts = location.dataValues.concerts.length
// delete location.dataValues.concerts
// }
// }
res.status(200).json(cities) res.status(200).json(cities)
}) })
}) })

View File

@@ -7,10 +7,23 @@ import { SeatRow } from "../models/locations/seatRow.model";
import { Seat } from "../models/locations/seat.model"; import { Seat } from "../models/locations/seat.model";
import { Ticket } from "../models/ordering/ticket.model"; import { Ticket } from "../models/ordering/ticket.model";
import { Band } from "../models/acts/band.model"; import { Band } from "../models/acts/band.model";
import { Op } from "sequelize";
export const concert = Router() export const concert = Router()
concert.get("/", (req: Request, res: Response) => {
Concert.findAll({
include: [ Band, Location ],
order: [
[ 'date', 'ASC' ]
]
})
.then(concerts => {
res.status(200).json(concerts)
})
})
concert.get("/concert/:id", (req: Request, res: Response) => { concert.get("/concert/:id", (req: Request, res: Response) => {
Concert.findByPk(req.params.id, { Concert.findByPk(req.params.id, {
include: [ include: [

View File

@@ -20,15 +20,6 @@ location.get("/", (req: Request, res: Response) => {
{ {
model: Concert, model: Concert,
include: [ Band ], include: [ Band ],
},
{
model: SeatGroup,
include: [
{
model: SeatRow,
include: [ Seat ]
}
]
} }
], ],
attributes: { attributes: {
@@ -46,6 +37,12 @@ location.get("/", (req: Request, res: Response) => {
}) })
} }
for (let location of locations) {
location.dataValues["nrOfConcerts"] = location.dataValues.concerts.length
delete location.dataValues.concerts
}
// Limit number of items
if (count != undefined) { if (count != undefined) {
locations.splice(Number(count)) locations.splice(Number(count))
} }
@@ -86,7 +83,6 @@ location.get("/location/:urlName", (req: Request, res: Response) => {
} }
} }
res.status(200).json(location) res.status(200).json(location)
}) })
}) })

View File

@@ -0,0 +1,41 @@
import { Rating } from "../models/acts/rating.model";
/**
* Calculate the average of an Array of ratings
*
* @param ratings Array of ratings
*
* @returns Average rating as number
*/
export function calcOverallRating(ratings: Array<Rating>): number {
let sum = 0
for (let rating of ratings) {
sum += rating.dataValues.rating
}
return sum / ratings.length
}
/**
* Classifies a bunch of ratings to groups from 1 to 5 stars
*
* @param ratings Array of RatingModels
*
* @returns Array of Objects: { value: number[1-5], count: number }
*/
export function calcRatingValues(ratings: Array<Rating>) {
let ratingValues = [
{ value: 1, count: 0 },
{ value: 2, count: 0 },
{ value: 3, count: 0 },
{ value: 4, count: 0 },
{ value: 5, count: 0 }
]
for (let rating of ratings) {
ratingValues[rating.dataValues.rating - 1].count += 1
}
return ratingValues
}

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

@@ -10,19 +10,29 @@
<v-divider vertical /> <v-divider vertical />
<v-btn <v-btn
to="/events" to="/bands"
prepend-icon="mdi-guitar-electric"
height="100%"
:rounded="false"
>
{{ $t('allBands', 2) }}
</v-btn>
<v-divider vertical />
<v-btn
to="/concerts"
prepend-icon="mdi-ticket" prepend-icon="mdi-ticket"
height="100%" height="100%"
:rounded="false" :rounded="false"
> >
{{ $t('allEvents', 2) }} {{ $t('allConcerts', 2) }}
</v-btn> </v-btn>
<v-divider vertical /> <v-divider vertical />
<v-btn <v-btn
variant="text"
to="/locations" to="/locations"
prepend-icon="mdi-city" prepend-icon="mdi-city"
height="100%" height="100%"

View File

@@ -1,11 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { BandModel } from '@/data/models/acts/bandModel'; import { BandModel } from '@/data/models/acts/bandModel';
import { lowestTicketPrice, lowestTicketPriceEvents } from '@/scripts/concertScripts'; import { lowestTicketPrice } from '@/scripts/concertScripts';
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue'; import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
import { EventModel } from '@/data/models/acts/eventModel';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { GenreModel } from '@/data/models/acts/genreModel'; import { GenreModel } from '@/data/models/acts/genreModel';
import { EventApiModel } from '@/data/models/acts/eventApiModel'; import { ConcertModel } from '@/data/models/acts/concertModel';
const router = useRouter() const router = useRouter()
@@ -16,9 +15,8 @@ defineProps({
required: true required: true
}, },
/** Events where the band participate */ concerts: {
events: { type: Array<ConcertModel>,
type: Array<EventApiModel>,
required: true required: true
}, },
@@ -37,7 +35,7 @@ defineProps({
v-if="!loading" v-if="!loading"
:title="band.name" :title="band.name"
:image="'http://localhost:3000/static/' + band.logo" :image="'http://localhost:3000/static/' + band.logo"
@click="router.push('/bands/' + band.name.replaceAll(' ', '-').toLowerCase())" @click="router.push('/bands/details/' + band.name.replaceAll(' ', '-').toLowerCase())"
> >
<template #content> <template #content>
<div> <div>
@@ -54,12 +52,12 @@ defineProps({
<template #append> <template #append>
<div> <div>
<div class="text-secondary font-weight-medium text-h6 pb-1"> <div class="text-secondary font-weight-medium text-h6 pb-1">
{{ $t('from') + ' ' + lowestTicketPriceEvents(events) + ' €' }} {{ $t('from') + ' ' + lowestTicketPrice(concerts) + ' €' }}
</div> </div>
<div> <div>
<v-btn variant="flat" color="secondary"> <v-btn variant="flat" color="secondary">
{{ events.length }} {{ $t('event', events.length) }} {{ concerts.length }} {{ $t('event', concerts.length) }}
</v-btn> </v-btn>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue'; import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
import { BandModel } from '@/data/models/acts/bandModel';
import { ConcertModel } from '@/data/models/acts/concertModel'; import { ConcertModel } from '@/data/models/acts/concertModel';
import { LocationModel } from '@/data/models/locations/locationModel';
import { useRouter } from 'vue-router';
const router = useRouter()
defineProps({ defineProps({
/** Concert to display */ /** Concert to display */
@@ -9,8 +14,15 @@ defineProps({
required: true required: true
}, },
/** Card title */ band: {
title: String, type: BandModel,
required: true
},
location: {
type: LocationModel,
required: true
},
/** Display text parts as skeleton */ /** Display text parts as skeleton */
loading: Boolean, loading: Boolean,
@@ -19,19 +31,16 @@ defineProps({
showButton: { showButton: {
type: Boolean, type: Boolean,
default: true default: true
}, }
/** Behaviour if user clicks on button or card */
onClick: Function
}) })
</script> </script>
<template> <template>
<card-view-horizontal <card-view-horizontal
:title="title" :title="concert.name"
v-if="!loading" v-if="!loading"
:link="showButton && concert.inStock > 0" :link="showButton && concert.inStock > 0"
@click="showButton && concert.inStock > 0 ? onClick() : () => {}" @click="showButton && concert.inStock > 0 ? router.push('/concerts/booking/' + concert.id) : () => {}"
> >
<template #prepend> <template #prepend>
<div> <div>
@@ -50,7 +59,13 @@ defineProps({
</template> </template>
<template #content> <template #content>
<slot name="description" /> <div>
{{ band.name }}
</div>
<div>
{{ location.name }}
</div>
</template> </template>
<template #append> <template #append>

View File

@@ -1,86 +0,0 @@
<script setup lang="ts">
import { createDateRangeString, lowestTicketPrice } from '@/scripts/concertScripts';
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
import { EventModel } from '@/data/models/acts/eventModel';
import { useRouter } from 'vue-router';
import { BandModel } from '@/data/models/acts/bandModel';
import { ConcertModel } from '@/data/models/acts/concertModel';
const router = useRouter()
defineProps({
/** Event to display */
event: {
type: EventModel,
required: true
},
/** Band which plays the event */
band: {
type: BandModel,
required: true
},
/** All concerts in the event */
concerts: {
type: Array<ConcertModel>,
required: true
},
/** Display text parts as skeleton */
loading: Boolean
})
</script>
<template>
<card-view-horizontal
v-if="!loading"
:title="band.name + ' - ' + event.name"
:image="'http://localhost:3000/static/' + event.image"
@click="router.push('/bands/' + band.name.replaceAll(' ', '-').toLowerCase())"
>
<template #content>
<div class="oneLine my-2 pr-4 text-disabled" >
{{ band.descriptionDe }}
<!-- todo: Englisch text -->
</div>
<div class="text-disabled oneLine">
{{ createDateRangeString(concerts) }} - {{ concerts.length }}
{{ $t('concert', concerts.length) }}
</div>
</template>
<template #append>
<div>
<div class="text-secondary font-weight-medium text-h6 pb-1">
{{ $t('from') + ' ' + lowestTicketPrice(concerts) + ' €' }}
</div>
<div>
<v-btn variant="flat" color="secondary">
{{ concerts.length }} {{ $t('concert', concerts.length) }}
</v-btn>
</div>
</div>
</template>
</card-view-horizontal>
<card-view-horizontal
:loading="loading"
v-else
>
<v-skeleton-loader
type="text" />
</card-view-horizontal>
</template>
<style scoped>
.oneLine {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1; /* number of lines to show */
line-clamp: 1;
-webkit-box-orient: vertical;
}
</style>

View File

@@ -2,7 +2,6 @@
import cardViewTopImage from '../basics/cardViewTopImage.vue'; import cardViewTopImage from '../basics/cardViewTopImage.vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { LocationModel } from '@/data/models/locations/locationModel'; import { LocationModel } from '@/data/models/locations/locationModel';
import { ConcertModel } from '@/data/models/acts/concertModel';
const router = useRouter() const router = useRouter()
@@ -11,9 +10,8 @@ defineProps({
type: LocationModel, type: LocationModel,
required: true required: true
}, },
concerts: { nrOfConcerts: {
type: Array<ConcertModel>, type: Number
required: true
} }
}) })
</script> </script>
@@ -22,10 +20,10 @@ defineProps({
<card-view-top-image <card-view-top-image
:image="location.imageOutdoor" :image="location.imageOutdoor"
:title="location.name" :title="location.name"
@click="router.push('locations/' + location.name.replaceAll(' ', '-').toLowerCase())" @click="router.push('locations/details/' + location.name.replaceAll(' ', '-').toLowerCase())"
> >
<div> <div>
{{ concerts.length }} {{ $t('concert', concerts.length) }} {{ nrOfConcerts }} {{ $t('concert', nrOfConcerts) }}
</div> </div>
</card-view-top-image> </card-view-top-image>
</template> </template>

View File

@@ -2,7 +2,6 @@
import { ConcertModel } from '@/data/models/acts/concertModel'; import { ConcertModel } from '@/data/models/acts/concertModel';
import cardWithLeftImage from '../basics/cardViewHorizontal.vue'; import cardWithLeftImage from '../basics/cardViewHorizontal.vue';
import { dateStringToHumanReadableString } from '@/scripts/dateTimeScripts'; import { dateStringToHumanReadableString } from '@/scripts/dateTimeScripts';
import { EventModel } from '@/data/models/acts/eventModel';
import { BandModel } from '@/data/models/acts/bandModel'; import { BandModel } from '@/data/models/acts/bandModel';
import { LocationModel } from '@/data/models/locations/locationModel'; import { LocationModel } from '@/data/models/locations/locationModel';
import { CityModel } from '@/data/models/locations/cityModel'; import { CityModel } from '@/data/models/locations/cityModel';
@@ -13,11 +12,6 @@ defineProps({
required: true required: true
}, },
event: {
type: EventModel,
required: true
},
band: { band: {
type: BandModel, type: BandModel,
required: true required: true
@@ -54,7 +48,7 @@ defineProps({
:image="'http://localhost:3000/static/' + image" :image="'http://localhost:3000/static/' + image"
:link="false" :link="false"
color-header="primary" color-header="primary"
:title="band.name + ' - ' + event.name" :title="band.name + ' - ' + concert.name"
> >
<template #content> <template #content>
<div class="text-caption"> <div class="text-caption">

View File

@@ -2,10 +2,14 @@
import { SeatGroupModel } from '@/data/models/locations/seatGroupModel'; import { SeatGroupModel } from '@/data/models/locations/seatGroupModel';
import seatGroupSheet from './seatGroupSheet.vue'; import seatGroupSheet from './seatGroupSheet.vue';
import { ConcertModel } from '@/data/models/acts/concertModel'; import { ConcertModel } from '@/data/models/acts/concertModel';
import { LocationApiModel } from '@/data/models/locations/locationApiModel'; import { LocationModel } from '@/data/models/locations/locationModel';
let props = defineProps({ let props = defineProps({
location: LocationApiModel, location: LocationModel,
seatGroups: {
type: Array<SeatGroupModel>,
required: true
},
concert: { concert: {
type: ConcertModel, type: ConcertModel,
default: new ConcertModel() default: new ConcertModel()
@@ -13,7 +17,7 @@ let props = defineProps({
}) })
function findSeatCategory(name: string): SeatGroupModel { function findSeatCategory(name: string): SeatGroupModel {
return props.location.seatGroups.find(category => return props.seatGroups.find(category =>
category.name == name category.name == name
) )
} }

View File

@@ -2,6 +2,10 @@ import axios from "axios"
let BASE_URL = "http://localhost:3000/concerts" let BASE_URL = "http://localhost:3000/concerts"
export async function fetchConcerts() {
return await axios.get(BASE_URL)
}
export async function getConcert(id: number) { export async function getConcert(id: number) {
if (id != undefined) { if (id != undefined) {
return await axios.get(BASE_URL + "/concert/" + id) return await axios.get(BASE_URL + "/concert/" + id)
@@ -9,3 +13,7 @@ export async function getConcert(id: number) {
return null return null
} }
} }
export async function searchConcert(searchTerm: string) {
return await axios.get(BASE_URL + '/search?value=' + searchTerm)
}

View File

@@ -2,11 +2,12 @@ import axios from "axios"
const BASE_URL = "http://localhost:3000/locations" const BASE_URL = "http://localhost:3000/locations"
export async function getAllLocations() { export async function fetchAllLocations() {
return await axios.get(BASE_URL) return await axios.get(BASE_URL)
} }
export async function getLocation(locationName: string) { export async function getLocation(locationName: string) {
console.log(locationName)
return await axios.get(BASE_URL + "/location/" + locationName) return await axios.get(BASE_URL + "/location/" + locationName)
} }

View File

@@ -1,12 +1,11 @@
import { BandModel } from "./bandModel"; import { BandModel } from "./bandModel";
import { EventApiModel } from "./eventApiModel";
import { GenreModel } from "./genreModel" import { GenreModel } from "./genreModel"
import { MemberModel } from "./memberModel"
import { RatingModel } from "./ratingModel"
/**
* Replica of the API endpoint /bands
*/
export class BandApiModel extends BandModel { export class BandApiModel extends BandModel {
ratings: Array<RatingModel> = []
members: Array<MemberModel> = []
genres: Array<GenreModel> = [] genres: Array<GenreModel> = []
events: Array<EventApiModel> = [] rating: number = 0
nrOfConcerts: number = 0
} }

View File

@@ -0,0 +1,15 @@
import { BandModel } from "./bandModel";
import { ConcertApiModel } from "./concertApiModel";
import { GenreModel } from "./genreModel"
import { MemberModel } from "./memberModel";
import { RatingModel } from "./ratingModel"
/**
* Replica of the API endpoint /bands/band/:name
*/
export class BandDetailsApiModel extends BandModel {
members: Array<MemberModel> = []
ratingValues: Array<RatingModel> = []
genres: Array<GenreModel> = []
concerts: Array<ConcertApiModel> = []
}

View File

@@ -7,4 +7,5 @@ export class BandModel {
images: Array<string> = [] images: Array<string> = []
imageMembers: string = "" imageMembers: string = ""
logo: string = "" logo: string = ""
rating: number = 0
} }

View File

@@ -1,8 +1,8 @@
import { LocationApiModel } from "../locations/locationApiModel" import { LocationApiModel } from "../locations/locationApiModel"
import { BandModel } from "./bandModel"
import { ConcertModel } from "./concertModel" import { ConcertModel } from "./concertModel"
import { EventApiModel } from "./eventApiModel"
export class ConcertApiModel extends ConcertModel { export class ConcertApiModel extends ConcertModel {
location: LocationApiModel = new LocationApiModel() location: LocationApiModel = new LocationApiModel()
event: EventApiModel = new EventApiModel() band: BandModel = new BandModel()
} }

View File

@@ -1,6 +1,9 @@
export class ConcertModel { export class ConcertModel {
id: number = -1 id: number = -1
inStock: number = 0
date: string = "" date: string = ""
name: string = ""
price: number = 0 price: number = 0
image: string = ""
inStock: number = 0
offered: boolean = true
} }

View File

@@ -1,8 +0,0 @@
import { EventModel } from "./eventModel";
import { BandModel } from "./bandModel"
import { ConcertApiModel } from "./concertApiModel";
export class EventApiModel extends EventModel {
concerts: Array<ConcertApiModel> = []
band: BandModel = new BandModel()
}

View File

@@ -1,6 +0,0 @@
export class EventModel {
id: number = -1
name: string = ""
offered: boolean = true
image: string = ""
}

View File

@@ -1,4 +1,4 @@
export class RatingModel { export class RatingModel {
id: number = -1 value: number = 0
rating: number = 1 count: number = 0
} }

View File

@@ -1,13 +1,10 @@
import { ConcertApiModel } from "../acts/concertApiModel"
import { CityModel } from "./cityModel" import { CityModel } from "./cityModel"
import { LocationModel } from "./locationModel" import { LocationModel } from "./locationModel"
import { SeatGroupModel } from "./seatGroupModel"
/** /**
* Replica of the API endpoint /locations * Replica of the API endpoint /locations
*/ */
export class LocationApiModel extends LocationModel { export class LocationApiModel extends LocationModel {
city: CityModel = new CityModel() city: CityModel = new CityModel()
concerts: Array<ConcertApiModel> = [] nrOfConcerts: number = 0
seatGroups: Array<SeatGroupModel> = []
} }

View File

@@ -0,0 +1,13 @@
import { ConcertApiModel } from "../acts/concertApiModel"
import { CityModel } from "./cityModel"
import { LocationModel } from "./locationModel"
import { SeatGroupModel } from "./seatGroupModel"
/**
* Replica of the API endpoint /locations/location/:name
*/
export class LocationDetailsApiModel extends LocationModel {
city: CityModel = new CityModel()
concerts: Array<ConcertApiModel> = []
seatGroups: Array<SeatGroupModel> = []
}

View File

@@ -8,7 +8,6 @@ import { PaymentModel } from "../models/user/paymentModel";
import { ref } from "vue"; import { ref } from "vue";
import { SelectedSeatModel } from "../models/ordering/selectedSeatModel"; import { SelectedSeatModel } from "../models/ordering/selectedSeatModel";
import { calcPrice } from "@/scripts/concertScripts"; import { calcPrice } from "@/scripts/concertScripts";
import { EventModel } from "../models/acts/eventModel";
import { BandModel } from "../models/acts/bandModel"; import { BandModel } from "../models/acts/bandModel";
export const useBasketStore = defineStore('basketStore', { export const useBasketStore = defineStore('basketStore', {
@@ -51,28 +50,29 @@ export const useBasketStore = defineStore('basketStore', {
) )
}, },
moveSeatSelectionsToBasket(event: EventModel, band: BandModel) { moveSeatSelectionsToBasket(event, band: BandModel) {
for (let selectedSeat of this.selectedSeats) { // todo
let itemInBasket: BasketItemModel = this.itemsInBasket.find((basketItem: BasketItemModel) => { // for (let selectedSeat of this.selectedSeats) {
return basketItem.concert.id == selectedSeat.concert.id // let itemInBasket: BasketItemModel = this.itemsInBasket.find((basketItem: BasketItemModel) => {
}) // return basketItem.concert.id == selectedSeat.concert.id
// })
if (itemInBasket != undefined) { // if (itemInBasket != undefined) {
itemInBasket.seats.push(selectedSeat.seat) // itemInBasket.seats.push(selectedSeat.seat)
} else { // } else {
this.itemsInBasket.push( // this.itemsInBasket.push(
new BasketItemModel( // new BasketItemModel(
selectedSeat.concert, // selectedSeat.concert,
event, // event,
band, // band,
selectedSeat.seat, // selectedSeat.seat,
selectedSeat.concert.price // selectedSeat.concert.price
) // )
) // )
} // }
} // }
this.selectedSeats = [] // this.selectedSeats = []
}, },
/** /**

View File

@@ -0,0 +1,24 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { ConcertApiModel } from "../models/acts/concertApiModel";
import { useFeedbackStore } from "./feedbackStore";
import { fetchConcerts } from "../api/concertApi";
export const useConcertStore = defineStore("concertStore", {
state: () => ({
concerts: ref<Array<ConcertApiModel>>([])
}),
actions: {
async getConcerts() {
const feedbackStore = useFeedbackStore()
feedbackStore.fetchDataFromServerInProgress = true
await fetchConcerts()
.then(result => {
this.concerts = result.data
feedbackStore.fetchDataFromServerInProgress = false
})
}
}
})

View File

@@ -0,0 +1,36 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { useFeedbackStore } from "./feedbackStore";
import { fetchAllLocations } from "../api/locationApi";
import { LocationApiModel } from "../models/locations/locationApiModel";
import { CityModel } from "../models/locations/cityModel";
import { fetchAllCities } from "../api/cityApi";
export const useLocationStore = defineStore("locationStore", {
state: () => ({
locations: ref<Array<LocationApiModel>>([]),
cities: ref<Array<CityModel>>([])
}),
actions: {
async getLocations() {
const feedbackStore = useFeedbackStore()
feedbackStore.fetchDataFromServerInProgress = true
await fetchAllLocations()
.then(result => {
this.locations = result.data
})
await fetchAllCities()
.then(result => {
this.cities = result.data
feedbackStore.fetchDataFromServerInProgress = false
})
},
getLocationsByCity(city: string): Array<LocationApiModel> {
return this.locations.filter(location => location.city.name == city)
}
},
})

View File

@@ -0,0 +1,60 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { ConcertApiModel } from "../models/acts/concertApiModel";
import { BandApiModel } from "../models/acts/bandApiModel";
import { CityApiModel } from "../models/locations/cityApiModel";
import { GenreApiModel } from "../models/acts/genreApiModel";
import { searchBand } from "../api/bandApi";
import { searchLocation } from "../api/locationApi";
import { fetchConcerts, searchConcert } from "../api/concertApi";
import { useFeedbackStore } from "./feedbackStore";
export const useShopStore = defineStore("shopStore", {
state: () => ({
concertsFiltered: ref<Array<ConcertApiModel>>([]),
bandsFiltered: ref<Array<BandApiModel>>([]),
cities: ref<Array<CityApiModel>>([]),
cityFilterName: ref<string>(),
genreFilterName: ref<string>(),
genres: ref<Array<GenreApiModel>>([]),
alreadySearched: ref(false),
searchInProgress: ref(false)
}),
actions: {
/**
* Search for the termin in all bands, locations, events
*/
async startSearch() {
this.alreadySearched = true
this.searchInProgress = true
await searchBand(this.searchTerm)
.then(result => {
this.bands = result.data
})
await searchLocation(this.searchTerm)
.then(result => {
this.locations = result.data
})
await searchConcert(this.searchTerm)
.then(result => {
this.concerts = result.data
})
this.searchInProgress = false
},
async getConcerts() {
const feedbackStore = useFeedbackStore()
feedbackStore.fetchDataFromServerInProgress = true
await fetchConcerts()
.then(result => {
this.concerts = result.data
})
}
}
})

View File

@@ -5,12 +5,10 @@ import { fetchAllCities } from "../api/cityApi";
import { fetchAllGenres } from "../api/genreApi"; import { fetchAllGenres } from "../api/genreApi";
import { useFeedbackStore } from "./feedbackStore"; import { useFeedbackStore } from "./feedbackStore";
import { CityApiModel } from "../models/locations/cityApiModel"; import { CityApiModel } from "../models/locations/cityApiModel";
import { EventApiModel } from "../models/acts/eventApiModel";
import { GenreApiModel } from "../models/acts/genreApiModel"; import { GenreApiModel } from "../models/acts/genreApiModel";
export const useShoppingStore = defineStore("shoppingStore", { export const useShoppingStore = defineStore("shoppingStore", {
state: () => ({ state: () => ({
events: ref<Array<EventApiModel>>([]),
cities: ref<Array<CityApiModel>>([]), cities: ref<Array<CityApiModel>>([]),
genres: ref<Array<GenreApiModel>>([]), genres: ref<Array<GenreApiModel>>([]),
cityFilterName: ref<string>(), cityFilterName: ref<string>(),

View File

@@ -173,5 +173,7 @@
"selectedConcert": "Ausgewähltes Konzert", "selectedConcert": "Ausgewähltes Konzert",
"enterSomeKeywords": "Füge Schlagworte ein um nach Bands, Events, Konzerten und Veranstaltungsorten zu suchen", "enterSomeKeywords": "Füge Schlagworte ein um nach Bands, Events, Konzerten und Veranstaltungsorten zu suchen",
"noBandFound": "Keine Band gefunden", "noBandFound": "Keine Band gefunden",
"noLocationsFound": "Keine Veranstaltungsorte gefunden" "noLocationsFound": "Keine Veranstaltungsorte gefunden",
"allBands": "Alle Bands",
"allConcerts": "Alle Konzerte"
} }

View File

@@ -173,5 +173,7 @@
"selectedConcert": "Selected Concert", "selectedConcert": "Selected Concert",
"enterSomeKeywords": "Enter keywords to search for bands, events, concerts and locations", "enterSomeKeywords": "Enter keywords to search for bands, events, concerts and locations",
"noBandFound": "No band found", "noBandFound": "No band found",
"noLocationsFound": "No location found" "noLocationsFound": "No location found",
"allBands": "All Bands",
"allConcerts": "All Concerts"
} }

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { BandApiModel } from '@/data/models/acts/bandApiModel'; import { BandApiModel } from '@/data/models/acts/bandApiModel';
import { RatingModel } from '@/data/models/acts/ratingModel'; import { RatingModel } from '@/data/models/acts/ratingModel';
import { calcRating, calcRatingValues } from '@/scripts/concertScripts';
defineProps({ defineProps({
rating: Number,
ratings: { ratings: {
type: Array<RatingModel>, type: Array<RatingModel>,
required: true required: true
@@ -16,12 +16,12 @@ defineProps({
<v-col> <v-col>
<div class="d-flex align-center justify-center flex-column" style="height: 100%;"> <div class="d-flex align-center justify-center flex-column" style="height: 100%;">
<div class="text-h2 mt-5"> <div class="text-h2 mt-5">
{{ calcRating(ratings).toFixed(1) }} {{ rating.toFixed(1) }}
<span class="text-h6 ml-n3">/5</span> <span class="text-h6 ml-n3">/5</span>
</div> </div>
<v-rating <v-rating
:model-value="calcRating(ratings)" :model-value="rating"
color="yellow-darken-3" color="yellow-darken-3"
half-increments half-increments
size="x-large" size="x-large"
@@ -34,7 +34,7 @@ defineProps({
<v-col> <v-col>
<v-list> <v-list>
<v-list-item v-for="ratingValue in calcRatingValues(ratings)"> <v-list-item v-for="ratingValue in ratings">
<template v-slot:prepend> <template v-slot:prepend>
<span>{{ ratingValue.value }}</span> <span>{{ ratingValue.value }}</span>
<v-icon class="ml-3 mr-n3" icon="mdi-star" /> <v-icon class="ml-3 mr-n3" icon="mdi-star" />

View File

@@ -2,5 +2,5 @@
</script> </script>
<template> <template>
Bands
</template> </template>

View File

@@ -50,7 +50,7 @@ getConcert(Number(router.currentRoute.value.params.id))
> >
<template #description> <template #description>
<p>{{ concertModel.location.name }}</p> <p>{{ concertModel.location.name }}</p>
<p>{{ concertModel.event.band.name }} - {{ concertModel.event.name }}</p> <!-- todo <p>{{ concertModel.event.band.name }} - {{ concertModel.event.name }}</p> -->
</template> </template>
</concert-list-item> </concert-list-item>
</v-col> </v-col>
@@ -102,7 +102,7 @@ getConcert(Number(router.currentRoute.value.params.id))
</v-row> </v-row>
<v-row class="pb-5"> <v-row class="pb-5">
<outlined-button <!-- <outlined-button todo
prepend-icon="mdi-basket-plus" prepend-icon="mdi-basket-plus"
@click="basketStore.moveSeatSelectionsToBasket(concertModel.event, concertModel.event.band); @click="basketStore.moveSeatSelectionsToBasket(concertModel.event, concertModel.event.band);
router.push('/basket')" router.push('/basket')"
@@ -110,7 +110,7 @@ getConcert(Number(router.currentRoute.value.params.id))
block block
> >
{{ $t('addToBasket') }} {{ $t('addToBasket') }}
</outlined-button> </outlined-button> -->
</v-row> </v-row>
</v-col> </v-col>

View File

@@ -1,6 +1,69 @@
<script setup lang="ts"> <script setup lang="ts">
import { useConcertStore } from '@/data/stores/concertStore';
import concertListItem from '@/components/pageParts/concertListItem.vue';
import { useFeedbackStore } from '@/data/stores/feedbackStore';
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
import sectionDivider from '@/components/basics/sectionDivider.vue';
const concertStore = useConcertStore()
const feedbackStore = useFeedbackStore()
concertStore.getConcerts()
</script> </script>
<template> <template>
Concerts <v-container>
<v-row>
<v-spacer />
<v-col cols="10">
<v-row>
<v-col>
Filterbar
<!-- todo: Filterbar? -->
</v-col>
</v-row>
<v-row
v-if="feedbackStore.fetchDataFromServerInProgress"
v-for="i in 3"
>
<v-col>
<card-view-horizontal :loading="true" />
</v-col>
</v-row>
<div
v-else-if="concertStore.concerts.length > 0"
v-for="(concert, index) of concertStore.concerts"
>
<v-row
v-if="index == 0 ||
new Date(concertStore.concerts[index - 1].date).getMonth() !=
new Date(concertStore.concerts[index].date).getMonth()"
>
<v-col>
<section-divider
:title="new Date(concert.date).toLocaleString('default', { month: 'long' }) + ' ' + new Date(concert.date).getFullYear()"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<concert-list-item
:concert="concert"
:band="concert.band"
:location="concert.location"
/>
</v-col>
</v-row>
</div>
</v-col>
<v-spacer />
</v-row>
</v-container>
</template> </template>

View File

@@ -3,7 +3,6 @@ import filterBar from './filterBar.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useShoppingStore } from '@/data/stores/shoppingStore'; import { useShoppingStore } from '@/data/stores/shoppingStore';
import { useFeedbackStore } from '@/data/stores/feedbackStore'; import { useFeedbackStore } from '@/data/stores/feedbackStore';
import eventListItem from '../../../components/pageParts/eventListItem.vue';
const route = useRoute() const route = useRoute()
const shoppingStore = useShoppingStore() const shoppingStore = useShoppingStore()

View File

@@ -7,29 +7,25 @@ import OutlinedButton from '@/components/basics/outlinedButton.vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useFeedbackStore } from '@/data/stores/feedbackStore'; import { useFeedbackStore } from '@/data/stores/feedbackStore';
import { ref } from 'vue'; import { ref } from 'vue';
import { EventModel } from '@/data/models/acts/eventModel';
import { getTopEvents } from '@/data/api/eventApi';
import { getTopLocations } from '@/data/api/locationApi'; import { getTopLocations } from '@/data/api/locationApi';
import { LocationApiModel } from '@/data/models/locations/locationApiModel'; import { LocationApiModel } from '@/data/models/locations/locationApiModel';
import { EventApiModel } from '@/data/models/acts/eventApiModel';
const router = useRouter() const router = useRouter()
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
const topEvents = ref<Array<EventApiModel>>(Array.from({length: 4}, () => new EventApiModel()))
const topLocations = ref<Array<LocationApiModel>>(Array.from({length: 8}, () => new LocationApiModel())) const topLocations = ref<Array<LocationApiModel>>(Array.from({length: 8}, () => new LocationApiModel()))
feedbackStore.fetchDataFromServerInProgress = true feedbackStore.fetchDataFromServerInProgress = true
getTopEvents(4) // todo getTopEvents(4)
.then(events => { // .then(events => {
topEvents.value = events.data // topEvents.value = events.data
getTopLocations(8) // getTopLocations(8)
.then(locations => { // .then(locations => {
topLocations.value = locations.data // topLocations.value = locations.data
feedbackStore.fetchDataFromServerInProgress = false // feedbackStore.fetchDataFromServerInProgress = false
}) // })
}) // })
</script> </script>
<template> <template>
@@ -46,7 +42,7 @@ getTopEvents(4)
</v-col> </v-col>
</v-row> </v-row>
<v-row> <!-- <v-row> todo
<v-col v-for="i in 4" cols="3"> <v-col v-for="i in 4" cols="3">
<card-with-top-image <card-with-top-image
:image="topEvents[i - 1].image" :image="topEvents[i - 1].image"
@@ -58,7 +54,7 @@ getTopEvents(4)
ab {{ lowestTicketPrice(topEvents[i - 1].concerts) }} ab {{ lowestTicketPrice(topEvents[i - 1].concerts) }}
</card-with-top-image> </card-with-top-image>
</v-col> </v-col>
</v-row> </v-row> -->
<v-row> <v-row>
<v-col> <v-col>

View File

@@ -7,15 +7,15 @@ import { ref } from 'vue';
import { useFeedbackStore } from '@/data/stores/feedbackStore'; import { useFeedbackStore } from '@/data/stores/feedbackStore';
import heroImage from '@/components/pageParts/heroImage.vue'; import heroImage from '@/components/pageParts/heroImage.vue';
import concertListItem from '@/components/pageParts/concertListItem.vue'; import concertListItem from '@/components/pageParts/concertListItem.vue';
import { LocationApiModel } from '@/data/models/locations/locationApiModel'; import { LocationDetailsApiModel } from '@/data/models/locations/locationDetailsApiModel';
const router = useRouter() const router = useRouter()
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
const location = ref<LocationApiModel>(new LocationApiModel()) const location = ref<LocationDetailsApiModel>(new LocationDetailsApiModel())
feedbackStore.fetchDataFromServerInProgress = true feedbackStore.fetchDataFromServerInProgress = true
getLocation(String(router.currentRoute.value.params.locationName)) getLocation(String(router.currentRoute.value.params.name))
.then(result => { .then(result => {
location.value = result.data location.value = result.data
feedbackStore.fetchDataFromServerInProgress = false feedbackStore.fetchDataFromServerInProgress = false
@@ -61,11 +61,11 @@ getLocation(String(router.currentRoute.value.params.locationName))
<v-col> <v-col>
<concert-list-item <concert-list-item
:concert="concert" :concert="concert"
:title="concert.event.name" :title="concert.name"
:onClick="() => router.push('/bands/' + concert.event.band.name.replaceAll(' ', '-').toLowerCase())" :onClick="() => router.push('/bands/' + concert.band.name.replaceAll(' ', '-').toLowerCase())"
> >
<template #description> <template #description>
{{ concert.event.band.name }} {{ concert.band.name }}
</template> </template>
</concert-list-item> </concert-list-item>
</v-col> </v-col>
@@ -97,6 +97,7 @@ getLocation(String(router.currentRoute.value.params.locationName))
<v-col> <v-col>
<seat-plan-map <seat-plan-map
:location="location" :location="location"
:seat-groups="location.seatGroups"
/> />
</v-col> </v-col>
</v-row> </v-row>

View File

@@ -1,14 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import sectionDivider from '@/components/basics/sectionDivider.vue'; import sectionDivider from '@/components/basics/sectionDivider.vue';
import cardWithTopImage from '@/components/basics/cardViewTopImage.vue'; import cardWithTopImage from '@/components/basics/cardViewTopImage.vue';
import { useShoppingStore } from '@/data/stores/shoppingStore';
import { useFeedbackStore } from '@/data/stores/feedbackStore'; import { useFeedbackStore } from '@/data/stores/feedbackStore';
import locationListItem from '@/components/pageParts/locationListItem.vue'; import locationListItem from '@/components/pageParts/locationListItem.vue';
import { useLocationStore } from '@/data/stores/locationStore';
const shoppingStore = useShoppingStore() const locationStore = useLocationStore()
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
shoppingStore.getCities() locationStore.getLocations()
</script> </script>
<template> <template>
@@ -37,23 +37,31 @@ shoppingStore.getCities()
<!-- When all data are downloaded --> <!-- When all data are downloaded -->
<div <div
v-else v-else-if="locationStore.locations.length > 0"
v-for="city in shoppingStore.cities" v-for="city in locationStore.cities"
> >
<v-row> <v-row>
<v-col> <v-col>
<section-divider <v-row>
:title="city.name" <v-col>
/> <section-divider
</v-col> :title="city.name"
</v-row> />
</v-col>
</v-row>
<v-row>
<v-col
v-for="location in locationStore.getLocationsByCity(city.name)"
cols="3"
>
<location-list-item
:location="location"
:nrOfConcerts="location.nrOfConcerts"
/>
</v-col>
</v-row>
<v-row>
<v-col v-for="location in city.locations" cols="3">
<location-list-item
:location="location"
:concerts="location.concerts"
/>
</v-col> </v-col>
</v-row> </v-row>
</div> </div>
@@ -61,8 +69,5 @@ shoppingStore.getCities()
<v-spacer /> <v-spacer />
</v-row> </v-row>
</v-container> </v-container>
</template> </template>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import searchBar from './searchBar.vue'; import searchBar from './searchBar.vue';
import eventListItem from '@/components/pageParts/eventListItem.vue';
import sectionDivider from '@/components/basics/sectionDivider.vue'; import sectionDivider from '@/components/basics/sectionDivider.vue';
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue'; import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
import locationListItem from '@/components/pageParts/locationListItem.vue'; import locationListItem from '@/components/pageParts/locationListItem.vue';
@@ -45,7 +44,7 @@ const searchStore = useSearchStore()
<v-col> <v-col>
<band-list-item <band-list-item
:band="band" :band="band"
:events="band.events" :concerts="band.concerts"
:genres="band.genres" :genres="band.genres"
:loading="searchStore.searchInProgress" :loading="searchStore.searchInProgress"
/> />

View File

@@ -30,7 +30,7 @@ const routes = [
// Bands // Bands
{ path: '/bands', component: BandsPage }, { path: '/bands', component: BandsPage },
{ path: '/bands/detail/:name', component: BandDetailPage }, { path: '/bands/details/:name', component: BandDetailPage },
// Concerts // Concerts
{ path: '/concerts', component: ConcertsPage }, { path: '/concerts', component: ConcertsPage },
@@ -38,7 +38,7 @@ const routes = [
// Locations // Locations
{ path: '/locations', component: LocationsPage }, { path: '/locations', component: LocationsPage },
{ path: '/locations/detail/:name', name: 'locationDetails', component: LocationDetailPage }, { path: '/locations/details/:name', name: 'locationDetails', component: LocationDetailPage },
// Misc // Misc
{ path: '/search', component: SearchPage }, { path: '/search', component: SearchPage },

View File

@@ -1,8 +1,5 @@
import { RatingModel } from "@/data/models/acts/ratingModel"
import { dateToHumanReadableString } from "./dateTimeScripts" import { dateToHumanReadableString } from "./dateTimeScripts"
import { ConcertModel } from "@/data/models/acts/concertModel" import { ConcertModel } from "@/data/models/acts/concertModel"
import { EventModel } from "@/data/models/acts/eventModel"
import { EventApiModel } from "@/data/models/acts/eventApiModel"
/** /**
* Calculate a price based on parameters * Calculate a price based on parameters
@@ -18,48 +15,6 @@ export function calcPrice(price: number, quantity: number = 1): number {
} }
/**
* Calculate the average of an Array of ratings
*
* @param ratings Array of ratings
*
* @returns Average rating as number
*/
export function calcRating(ratings: Array<RatingModel>) {
let sum = 0
for (let rating of ratings) {
sum += rating.rating
}
return sum / ratings.length
}
/**
* Classifies a bunch of ratings to groups from 1 to 5 stars
*
* @param ratings Array of RatingModels
*
* @returns Array of Objects: { value: number[1-5], count: number }
*/
export function calcRatingValues(ratings: Array<RatingModel>) {
let ratingValues = [
{ value: 1, count: 0 },
{ value: 2, count: 0 },
{ value: 3, count: 0 },
{ value: 4, count: 0 },
{ value: 5, count: 0 }
]
for (let rating of ratings) {
ratingValues[rating.rating - 1].count += 1
}
return ratingValues
}
/** /**
* Create a date range string of all concerts from an Event * Create a date range string of all concerts from an Event
* *
@@ -110,21 +65,3 @@ export function lowestTicketPrice(concerts: Array<ConcertModel>): string {
return "0" return "0"
} }
} }
export function lowestTicketPriceEvents(events: Array<EventApiModel>) {
const priceArray : Array<number> = []
for (let event of events) {
for (let concert of event.concerts) {
priceArray.push(concert.price)
}
}
priceArray.sort()
try {
return priceArray[0].toFixed(2)
} catch(e) {
return "0"
}
}