瀏覽代碼

Ergebnisse eingeben

tags/v0.9.1
akimmig 4 年之前
父節點
當前提交
2cfb9fd43e
共有 5 個檔案被更改,包括 457 行新增14 行删除
  1. +326
    -12
      client/src/views/components/event/event.vue
  2. +30
    -0
      server/schema.gql
  3. +62
    -0
      server/src/event/models/Result.ts
  4. +4
    -0
      server/src/event/models/Timeslot.ts
  5. +35
    -2
      server/src/event/resolver/event.mutation.ts

+ 326
- 12
client/src/views/components/event/event.vue 查看文件

@@ -19,9 +19,9 @@
Zeitslots
</v-tab>
<v-tab
href="#result"
href="#ranking"
>
Ergebnisse
Rangliste
</v-tab>
</v-tabs>
<v-tabs-items
@@ -37,7 +37,9 @@
<v-card-title class="text-h3">
Geräte
<v-spacer />
<v-menu>
<v-menu
v-if="!hasresults"
>
<template #activator="{on, attrs}">
<v-btn
icon
@@ -68,6 +70,7 @@
class="row row-dense"
v-bind="dragOptions"
@end="orderApparatus"
:disabled="hasresults"
>
<v-col
v-for="(a) in (Event || {}).apparatus"
@@ -78,7 +81,10 @@
>
<v-card>
<v-card-title class="text-h4">
<v-icon class="handle mr-4">far fa-bars</v-icon>
<v-icon
v-if="!hasresults"
class="handle mr-4"
>far fa-bars</v-icon>
{{ a.apparatus.name }}
</v-card-title>
<v-card-text>
@@ -86,7 +92,9 @@
<p><b>Bonus: </b> {{ a.bonus | float2_0 }}</p>
<p><b>Malus: </b> {{ a.malus | float2_0 }}</p>
</v-card-text>
<v-card-actions>
<v-card-actions
v-if="!hasresults"
>
<v-spacer />
<v-btn icon>
<v-icon>far fa-pencil</v-icon>
@@ -145,7 +153,7 @@
>
<template #header.select>
<v-checkbox
@change="(v) => !v ? ts.selected = [] : ts.selected = timeslots.map(t => t._id)"
@change="(v) => !v ? ts.selected = [] : ts.selected = timeslots.filter(t => !t.team).map(t => t._id)"
/>
</template>
<template #item.select="{item}">
@@ -180,6 +188,20 @@
@input="(v) => addTeam(item._id, v)"
/>
</template>
<template #item.result="{item}">
<v-btn
v-if="item.team"
text
x-small
style="float: left;"
@click="resultopen(item._id)"
>
<v-icon small>
far fa-play
</v-icon>
</v-btn>
{{ item.result ? item.result.calctime + 's' : '' }}
</template>
</v-data-table>
</v-card>
<base-material-dialog
@@ -300,11 +322,155 @@
</template>
</v-data-table>
</base-material-dialog>
<base-material-dialog
v-model="result.open"
title="Ergebnis eingeben"
color="rgb(255, 4, 29)"
:actions="['save','cancel']"
@save="saveresult"
@close="resultclose"
@esc="resultclose"
>
<v-row
class="mt-4"
>
<v-col
cols="12"
class="mb-6"
>
<base-material-card
title="Zeitmessung"
color="rgb(150,150,150)"
>
<v-row>
<v-col
cols="12"
md="4"
>
<v-text-field
readonly
:value="result.time | float2_0"
label="gemessene Zeit"
class="zeitmessung"
/>
</v-col>
<v-col
cols="12"
md="4"
>
<v-text-field
readonly
:value="`${bonusmalus}s`"
:label="bonusmalus <= 0 ? 'Bonus' : 'Malus'"
class="zeitmessung"
/>
</v-col>
<v-col
cols="12"
md="4"
>
<v-text-field
readonly
:value="result.time + bonusmalus | float2_0"
label="berechnete Zeit"
class="zeitmessung"
/>
</v-col>
<v-col
cols="6"
md="3"
order-md="0"
>
<v-btn
block
@click="start(5)"
>
5s Countdown
</v-btn>
</v-col>
<v-col
cols="6"
md="3"
order-md="4"
>
<v-btn
block
@click="reset()"
>
Reset
</v-btn>
</v-col>
<v-col
cols="6"
md="3"
order-md="1"
>
<v-btn
block
color="green"
@click="start(0)"
>
Start
</v-btn>
</v-col>
<v-col
cols="6"
md="3"
order-md="2"
>
<v-btn
block
color="primary"
@click="stop()"
>
Stop
</v-btn>
</v-col>
</v-row>
</base-material-card>
</v-col>
<v-col
v-for="(g, i) in apparatus"
:key="g._apparatus"
cols="12"
sm="6"
md="3"
>
<base-material-card
:title="g.apparatus.name"
color="rgb(150,150,150)"
>
<v-checkbox
v-model="results(g._apparatus).teile"
:label="`Teile OK? (max ${g.elements}, Malus +${g.malus}s)`"
/>
<v-checkbox
v-model="results(g._apparatus).bonus"
:label="`Bonus? (Bonus -${g.bonus}s)`"
/>
<v-checkbox
v-if="i !== apparatus.length -1"
v-model="results(g._apparatus).wechsel"
:label="`Wechsel OK? (Malus +${g.malus}s)`"
/>
</base-material-card>
</v-col>
</v-row>
</base-material-dialog>
</v-tab-item>
<v-tab-item
value="result"
value="ranking"
>
Ergebnisse
<v-data-table
:headers="ergebnis.headers"
:items="ergebnisse"
sort-by="time"
:items-per-page="-1"
>
<template #item.time="{item}">
{{ item.time | float2_0 }}s
</template>
</v-data-table>
</v-tab-item>
</v-tabs-items>
</v-container>
@@ -314,7 +480,8 @@
import gql from 'graphql-tag'
import moment from 'moment'

const query = '_id name date _organizer apparatus { _apparatus apparatus { name logo } elements bonus malus } timeslots { _id time duration team { _id name } }'
const timeslotquery = '_id time duration team { _id name } result { bonus runtime calctime results { _id teile bonus wechsel } }'
const query = `_id name date _organizer apparatus { _apparatus apparatus { name logo } elements bonus malus } timeslots { ${timeslotquery} }`

export default {
name: 'Event',
@@ -329,7 +496,7 @@ export default {
data: () => ({
Event: null,
ApparatusFind: [],
tab: 'timeslot',
tab: 0,
ts: {
headers: [
{
@@ -381,6 +548,35 @@ export default {
}
]
}
},
result: {
open: false,
id: null,
results: [],
start: null,
stop: null,
time: null,
running: false,
interval: null
},
ergebnis: {
headers: [
{
text: 'Platz',
value: 'platz',
sortable: false
},
{
text: 'Mannschaft',
value: 'team',
sortable: false
},
{
text: 'Zeit',
value: 'time',
sortable: false
}
]
}
}),

@@ -395,6 +591,9 @@ export default {
geraete () {
return this.ApparatusFind?.slice()?.sort((a, b) => a.name < b.name ? -1 : 1)?.filter(g => !this.Event?.apparatus.find(a => a._apparatus === g._id)) || []
},
apparatus () {
return this.Event?.apparatus || []
},
timeslots () {
return this.Event?.timeslots || []
},
@@ -408,6 +607,33 @@ export default {
}

return neu.filter(n => !this.timeslots.find(o => o.time === n && o.duration === parseInt(this.ts.dialog.duration))).map(t => ({ time: t, duration: this.ts.dialog.duration }))
},
bonus () {
return this.apparatus.reduce((acc, curr) => {
if (this.results(curr._apparatus).bonus) acc += parseInt(curr.bonus)
return acc
}, 0)
},
malus () {
return this.apparatus.reduce((acc, curr, i, arr) => {
if (!this.results(curr._apparatus).teile) acc += parseInt(curr.malus)
if (i !== arr.length - 1 && !this.results(curr._apparatus).wechsel) acc += parseInt(curr.malus)
return acc
}, 0)
},
bonusmalus () {
return this.malus - this.bonus
},
hasresults () {
return !!this.Event?.timeslots?.find(t => !!t.result)
},
ergebnisse () {
return this.Event?.timeslots?.reduce((acc, curr) => {
if (curr.team && curr.result) {
acc.push({ team: curr.team.name, time: curr.result.calctime })
}
return acc
}, []).sort((a, b) => a.time < b.time ? -1 : 1).map((m, i) => ({ ...m, platz: i + 1 }))
}
},

@@ -454,7 +680,7 @@ export default {
})
},
addTimeslots () {
if (this.timeslots.length > 0) {
if (this.newtimeslots.length > 0) {
this.$apollo.mutate({
mutation: gql`mutation($id: UUID!, $timeslots: [ITimeslot!]!) {
EventAddTimeslots(id: $id, timeslots: $timeslots) { ${query} }
@@ -503,7 +729,7 @@ export default {
})
},
async deleteTeam (timeslot) {
if (!this.timeslots.find(t => t._id === timeslot)?.team?.ergebnis || await this.$root.$children[0].$refs.confirm.open('Löschen', 'Bei diesem Team sind bereits Ergebnisse hinterlegt, wirklich löschen?')) {
if (!this.timeslots.find(t => t._id === timeslot)?.result || await this.$root.$children[0].$refs.confirm.open('Löschen', 'Bei diesem Team sind bereits Ergebnisse hinterlegt, wirklich löschen?')) {
this.$apollo.mutate({
mutation: gql`mutation($id: UUID!, $timeslot: UUID!) {
EventDeleteTeam(id: $id, timeslot: $timeslot) { ${query} }
@@ -517,6 +743,87 @@ export default {
this.ts.selected = []
})
}
},
resultopen (timeslot) {
const result = this.Event?.timeslots?.find(t => t._id === timeslot)?.result
const results = result?.results

this.result.time = result?.runtime

this.result.open = true
this.result.id = timeslot
this.result.results = this.apparatus.map((a, i, arr) => {
const data = results?.find(r => r._id === a._apparatus)

const ret = {
_id: a._apparatus,
teile: data?.teile !== undefined ? data.teile : true,
bonus: data?.bonus !== undefined ? data.bonus : false
}
if (i !== arr.length - 1) {
ret.wechsel = data?.wechsel !== undefined ? data.wechsel : true
}
return ret
})
},
resultclose () {
this.result.open = false
this.result.id = null
this.result.results = []
this.reset()
},
results (apparatus) {
return this.result?.results?.find(a => a._id === apparatus) || {}
},
start (countdown) {
if (this.result.running) return

this.result.start = Date.now() + (countdown || 0) * 1000
this.result.running = true
this.result.interval = setInterval(() => {
this.result.time = (Date.now() - this.result.start) / 1000
}, 10)
},
stop () {
this.result.stop = Date.now()
this.result.running = false
if (this.result.interval !== null) {
clearInterval(this.result.interval)
this.result.interval = null
}
},
reset () {
this.result.start = null
this.result.stop = null
this.result.time = null
this.result.running = false
if (this.result.interval !== null) {
clearInterval(this.result.interval)
this.result.interval = null
}
},
saveresult () {
const variables = {
id: this.id,
timeslot: this.result.id,
result: {
runtime: Math.round((this.result.time) * 100) / 100,
bonus: this.bonusmalus,
calctime: Math.round((this.result.time + this.bonusmalus) * 100) / 100,
results: this.result.results
}
}

this.$apollo.mutate({
mutation: gql`mutation($id: UUID!, $timeslot: UUID!, $result: IResult!) {
EventSetResult(id: $id, timeslot: $timeslot, result: $result) { ${timeslotquery} }
}`,
variables
}).then(() => {
this.ts.selected = []
})

this.resultclose()
}
},

@@ -556,4 +863,11 @@ export default {
content: '';
clear: both;
}

.v-text-field.zeitmessung >>> input {
font-size: 3em !important;
font-weight: bold !important;
max-height: none;
}

</style>

+ 30
- 0
server/schema.gql 查看文件

@@ -48,11 +48,26 @@ type Team {
name: String!
}

type Result {
bonus: Int!
runtime: Float!
calctime: Float!
results: [ApparatusResult!]!
}

type ApparatusResult {
_id: UUID!
bonus: Boolean!
teile: Boolean!
wechsel: Boolean
}

type Timeslot {
_id: UUID!
time: Time!
duration: Int!
team: Team
result: Result
}

"""A time string HH:MM:SS"""
@@ -114,6 +129,7 @@ type Mutation {
EventDeleteTimeslots(timeslots: [UUID!]!, id: UUID!): Event!
EventAddTeam(name: String!, timeslot: UUID!, id: UUID!): Event!
EventDeleteTeam(timeslot: UUID!, id: UUID!): Event!
EventSetResult(result: IResult!, timeslot: UUID!, id: UUID!): Timeslot!
EventDelete(id: UUID!): UUID!
}

@@ -122,6 +138,20 @@ input ITimeslot {
duration: Int!
}

input IResult {
bonus: Int!
runtime: Float!
calctime: Float!
results: [IApparatusResult!]!
}

input IApparatusResult {
_id: UUID!
bonus: Boolean!
teile: Boolean!
wechsel: Boolean
}

type Subscription {
PersonUpdated: Person
PersonDeleted: UUID!

+ 62
- 0
server/src/event/models/Result.ts 查看文件

@@ -0,0 +1,62 @@
import {Field, Float, InputType, Int, ObjectType} from '@nestjs/graphql';
import { UUID } from '../../global/scalars/UUID';

@ObjectType()
export class Result {
@Field(() => Int,{ nullable: false })
bonus: number

@Field(() => Float,{ nullable: false })
runtime: number

@Field(() => Float,{ nullable: false })
calctime: number

@Field(() => [ApparatusResult], { nullable: false })
results: ApparatusResult[]
}

@InputType()
export class IResult {
@Field(() => Int,{ nullable: false })
bonus: number

@Field(() => Float,{ nullable: false })
runtime: number

@Field(() => Float,{ nullable: false })
calctime: number

@Field(() => [IApparatusResult], { nullable: false })
results: IApparatusResult[]
}

@ObjectType()
export class ApparatusResult {
@Field(() => UUID,{ nullable: false })
_id: number

@Field(() => Boolean,{ nullable: false })
bonus: boolean

@Field(() => Boolean,{ nullable: false })
teile: boolean

@Field(() => Boolean,{ nullable: true })
wechsel?: boolean
}

@InputType()
export class IApparatusResult {
@Field(() => UUID,{ nullable: false })
_id: number

@Field(() => Boolean,{ nullable: false })
bonus: boolean

@Field(() => Boolean,{ nullable: false })
teile: boolean

@Field(() => Boolean,{ nullable: true })
wechsel?: boolean
}

+ 4
- 0
server/src/event/models/Timeslot.ts 查看文件

@@ -2,6 +2,7 @@ import {Field, InputType, Int, ObjectType} from '@nestjs/graphql';
import { UUID } from '../../global/scalars/UUID';
import { Time } from '../../global/scalars/Time'
import {Team} from './Team'
import {Result} from './Result'

@ObjectType()
export class Timeslot {
@@ -16,6 +17,9 @@ export class Timeslot {

@Field(() => Team, { nullable: true })
team?: Team

@Field(() => Result, { nullable: true })
result?: Result
}

@InputType()

+ 35
- 2
server/src/event/resolver/event.mutation.ts 查看文件

@@ -7,8 +7,10 @@ import {Date} from '../../global/scalars/Date'
import {UUID} from '../../global/scalars/UUID'
import {OrganizerService} from '../../organizer/organizer.service'
import {pubsub} from '../../main'
import {ITimeslot} from '../models/Timeslot'
import {ITimeslot, Timeslot} from '../models/Timeslot'
import { v4 as uuid } from 'uuid'
import {IResult} from '../models/Result'
import {Time} from '../../global/scalars/Time'

@Resolver(() => Event)
export class EventResolverM {
@@ -258,7 +260,7 @@ export class EventResolverM {
const neu: Event = await this.service.update(
client,
tmp._id,
{ $unset: { 'timeslots.$[ts].team': '' }},
{ $unset: { 'timeslots.$[ts].team': '', 'timeslots.$[ts].result': '' }},
{ arrayFilters: [{ 'ts._id': timeslot }]}
);

@@ -266,6 +268,37 @@ export class EventResolverM {
return neu;
}

@Mutation(() => Timeslot, { nullable: false })
async EventSetResult(
@Context('client') client: Client,
@Args('id', { type: () => UUID, nullable: false }) id: UUID,
@Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID,
@Args('result', { type: () => IResult, nullable: false }) result: IResult
): Promise<Timeslot> {
const tmp = await this.service.findOneById(id);
if (!tmp) throw new HttpException('Event-ID not found!', 404);

if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
throw new HttpException('Access denied', 403);
}

if (!tmp?.timeslots?.find(t => t._id === timeslot)) {
throw new HttpException('Timeslot-ID not found', 404);
} else if (!tmp.timeslots.find(t => t._id === timeslot).team) {
throw new HttpException('Timeslot empty', 404);
}

const neu: Event = await this.service.update(
client,
tmp._id,
{ $set: { 'timeslots.$[ts].result': result }},
{ arrayFilters: [{ 'ts._id': timeslot }]}
);

pubsub.publish('EventUpdated', { EventUpdated: neu });
return neu.timeslots.find(t => t._id === timeslot);
}

@Mutation(() => UUID, { nullable: false })
async EventDelete(
@Context('client') client, Client,

Loading…
取消
儲存