"dependencies": { | "dependencies": { | ||||
"@fortawesome/fontawesome-pro": "^5.15.3", | "@fortawesome/fontawesome-pro": "^5.15.3", | ||||
"@mdi/font": "^5.9.55", | "@mdi/font": "^5.9.55", | ||||
"@vue/composition-api": "1.0.0-rc.8", | |||||
"@vue/composition-api": "1.0.0-rc.12", | |||||
"apollo-cache-inmemory": "^1.6.6", | "apollo-cache-inmemory": "^1.6.6", | ||||
"apollo-client": "^2.6.10", | "apollo-client": "^2.6.10", | ||||
"core-js": "^3.6.5", | |||||
"core-js": "^3.15.1", | |||||
"jot": "git+https://github.com/joshdata/jot.git", | "jot": "git+https://github.com/joshdata/jot.git", | ||||
"js-cookie": "^2.2.1", | "js-cookie": "^2.2.1", | ||||
"lodash": "^4.17.21", | "lodash": "^4.17.21", | ||||
"subscriptions-transport-ws": "^0.9.18", | |||||
"moment": "^2.29.1", | |||||
"subscriptions-transport-ws": "^0.9.19", | |||||
"uuid": "^3.4.0", | "uuid": "^3.4.0", | ||||
"vue": "^2.6.11", | |||||
"vue": "^2.6.14", | |||||
"vue-apollo": "^3.0.7", | "vue-apollo": "^3.0.7", | ||||
"vue-cookies": "^1.7.4", | "vue-cookies": "^1.7.4", | ||||
"vue-router": "^3.2.0", | |||||
"vuetify": "^2.4.0", | |||||
"vue-router": "^3.5.2", | |||||
"vuedraggable": "^2.24.3", | |||||
"vuetify": "^2.5.5", | |||||
"vuetify-image-input": "^19.2.2", | "vuetify-image-input": "^19.2.2", | ||||
"vuex": "^3.4.0" | |||||
"vuex": "^3.6.2" | |||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@vue/cli-plugin-babel": "~4.5.0", | |||||
"@vue/cli-plugin-eslint": "~4.5.0", | |||||
"@vue/cli-plugin-router": "~4.5.0", | |||||
"@vue/cli-plugin-vuex": "~4.5.0", | |||||
"@vue/cli-service": "~4.5.0", | |||||
"@vue/cli-plugin-babel": "~4.5.13", | |||||
"@vue/cli-plugin-eslint": "~4.5.13", | |||||
"@vue/cli-plugin-router": "~4.5.13", | |||||
"@vue/cli-plugin-vuex": "~4.5.13", | |||||
"@vue/cli-service": "~4.5.13", | |||||
"@vue/eslint-config-standard": "^5.1.2", | "@vue/eslint-config-standard": "^5.1.2", | ||||
"babel-eslint": "^10.1.0", | "babel-eslint": "^10.1.0", | ||||
"eslint": "^6.7.2", | "eslint": "^6.7.2", | ||||
"eslint-plugin-import": "^2.20.2", | |||||
"eslint-plugin-import": "^2.23.4", | |||||
"eslint-plugin-node": "^11.1.0", | "eslint-plugin-node": "^11.1.0", | ||||
"eslint-plugin-promise": "^4.2.1", | "eslint-plugin-promise": "^4.2.1", | ||||
"eslint-plugin-standard": "^4.0.0", | |||||
"eslint-plugin-standard": "^4.1.0", | |||||
"eslint-plugin-vue": "^6.2.2", | "eslint-plugin-vue": "^6.2.2", | ||||
"sass": "^1.32.0", | |||||
"sass": "^1.35.1", | |||||
"sass-loader": "^10.0.0", | "sass-loader": "^10.0.0", | ||||
"vue-cli-plugin-vuetify": "~2.4.0", | |||||
"vue-template-compiler": "^2.6.11", | |||||
"vuetify-loader": "^1.7.0" | |||||
"vue-cli-plugin-vuetify": "~2.4.1", | |||||
"vue-template-compiler": "^2.6.14", | |||||
"vuetify-loader": "^1.7.2" | |||||
} | } | ||||
} | } |
import Vue from 'vue' | |||||
import moment from 'moment' | |||||
moment.locale('de') | |||||
Vue.filter('ts2date', value => { | |||||
if (value) { | |||||
return (new Date(parseInt(value))).toISOString() | |||||
} | |||||
}) | |||||
Vue.filter('date', value => { | |||||
if (value) { | |||||
return moment(String(value)).format('DD.MM.YYYY') | |||||
} | |||||
}) | |||||
Vue.filter('dateformat', (value, format) => { | |||||
if (!format) format = 'DD.MM.YYYY HH:mm' | |||||
if (value) { | |||||
return moment(String(value)).format(format) | |||||
} | |||||
}) | |||||
Vue.filter('ms2time', (value) => { | |||||
if (!value) value = 0 | |||||
value = value / 1000 | |||||
const min = Math.floor(value / 60) | |||||
const s = value - 60 * min | |||||
let ret = '' | |||||
if (min) { | |||||
ret += min + 'm ' | |||||
if (s < 10) ret += '0' | |||||
} | |||||
ret += s.toFixed(2) + 's' | |||||
return ret | |||||
}) | |||||
Vue.filter('isodate', value => { | |||||
if (value) { | |||||
return moment(String(value)).format() | |||||
} | |||||
return null | |||||
}) | |||||
Vue.filter('float2', function (value) { | |||||
const float = parseFloat(value) | |||||
if (isNaN(float) || float === 0) return '' | |||||
return float.toFixed(2) | |||||
}) | |||||
Vue.filter('float2force', function (value) { | |||||
const float = parseFloat(value) | |||||
if (isNaN(float)) return '' | |||||
return float.toFixed(2) | |||||
}) | |||||
Vue.filter('float2_0', function (value) { | |||||
const float = parseFloat(value) | |||||
if (isNaN(float) || float === 0) return '0.00' | |||||
return float.toFixed(2) | |||||
}) | |||||
Vue.filter('float1', function (value) { | |||||
const float = parseFloat(value) | |||||
if (isNaN(float) || float === 0) return '' | |||||
return float.toFixed(1) | |||||
}) | |||||
Vue.filter('int', function (value) { | |||||
const int = parseInt(value) | |||||
if (isNaN(int) || int === 0) return '' | |||||
return int.toString() | |||||
}) | |||||
Vue.filter('int_0', function (value) { | |||||
const int = parseInt(value) | |||||
if (isNaN(int) || int === 0) return '0' | |||||
return int.toString() | |||||
}) | |||||
Vue.filter('nl2br', function (value) { | |||||
return value?.replace(/\n/g, '<br/>') | |||||
}) |
import './plugins/compositionAPI' | import './plugins/compositionAPI' | ||||
import './plugins/base' | import './plugins/base' | ||||
import './filter' | |||||
Vue.component('Draggable', () => import('vuedraggable')) | |||||
Vue.component('E403', () => import('./components/e403.vue')) | Vue.component('E403', () => import('./components/e403.vue')) | ||||
Vue.component('DateSelector', () => import('./components/DateSelector.vue')) | Vue.component('DateSelector', () => import('./components/DateSelector.vue')) | ||||
Vue.component('DateTimeSelector', () => import('./components/DateTimeSelector.vue')) | Vue.component('DateTimeSelector', () => import('./components/DateTimeSelector.vue')) |
return context.root.$store.getters.isMaster | return context.root.$store.getters.isMaster | ||||
}) | }) | ||||
return { isMaster } | |||||
const isAdmin = (id) => { | |||||
return context.root.$store.getters.isAdmin(id) | |||||
} | |||||
const isOrga = (id) => { | |||||
return context.root.$store.getters.isOrga(id) | |||||
} | |||||
return { isMaster, isAdmin, isOrga } | |||||
} | } |
path: 'management/organizer', | path: 'management/organizer', | ||||
name: 'Veranstalter bearbeiten', | name: 'Veranstalter bearbeiten', | ||||
component: () => import('../views/components/management/organizer') | component: () => import('../views/components/management/organizer') | ||||
}, | |||||
{ | |||||
path: 'orga/:id', | |||||
name: 'Übersicht Events', | |||||
component: () => import('../views/components/orga/event'), | |||||
props: true | |||||
}, | |||||
{ | |||||
path: 'event/:id', | |||||
name: 'Event', | |||||
component: () => import('../views/components/event/event'), | |||||
props: true | |||||
} | } | ||||
] | ] | ||||
} | } | ||||
] | ] | ||||
const router = new VueRouter({ | const router = new VueRouter({ | ||||
mode: 'history', | |||||
routes | routes | ||||
}) | }) | ||||
getters: { | getters: { | ||||
profile: (state) => state.profile || {}, | profile: (state) => state.profile || {}, | ||||
isMaster: (state) => !!state.profile?.master, | isMaster: (state) => !!state.profile?.master, | ||||
isLogin: (state) => !!state.profile?.token | |||||
isLogin: (state) => !!state.profile?.token, | |||||
isAdmin: (state) => (id) => !!state.profile?.adminOf?.find(a => a._id === id), | |||||
isOrga: (state) => (id) => !!state.profile?.adminOf?.find(a => a._id === id) || !!state.profile?.organizerOf?.find(a => a._id === id) | |||||
} | } | ||||
}) | }) |
] | ] | ||||
}" | }" | ||||
/> | /> | ||||
<base-item | |||||
v-for="a in adminOf" | |||||
:key="'admin-'+a._id" | |||||
:item="{ | |||||
// group: '/admin', | |||||
icon: 'fa-user-crown', | |||||
title: 'Verwaltung', | |||||
subtitle: a.name, | |||||
to: '/admin/' + a._id | |||||
/* children: [ | |||||
{ | |||||
title: 'Nutzer verwalten', | |||||
icon: 'fa-users', | |||||
to: 'person' | |||||
} | |||||
] */ | |||||
}" | |||||
/> | |||||
<base-item | |||||
v-for="o in organizerOf" | |||||
:key="'orga-'+o._id" | |||||
:item="{ | |||||
// group: '/orga', | |||||
icon: 'fa-calendar-edit', | |||||
title: 'Organisation', | |||||
subtitle: o.name, | |||||
to: '/orga/' + o._id | |||||
/* children: [ | |||||
{ | |||||
title: 'Events verwalten', | |||||
icon: 'fa-calendar-day', | |||||
to: 'event/' + o._id | |||||
} | |||||
] */ | |||||
}" | |||||
/> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
name: 'Menu', | name: 'Menu', | ||||
computed: { | computed: { | ||||
...mapGetters(['profile', 'isMaster']) | |||||
...mapGetters(['profile', 'isMaster']), | |||||
adminOf () { | |||||
return this.profile?.adminOf | |||||
}, | |||||
organizerOf () { | |||||
return [...(this.profile?.adminOf || []), ...(this.profile?.organizerOf || [])] | |||||
} | |||||
} | } | ||||
} | } | ||||
</script> | </script> |
<template> | |||||
<v-container | |||||
fluid | |||||
tag="section" | |||||
> | |||||
<v-tabs | |||||
v-model="tab" | |||||
background-color="primary lighten-1" | |||||
fixed-tabs | |||||
> | |||||
<v-tab | |||||
href="#apparatus" | |||||
> | |||||
Geräte | |||||
</v-tab> | |||||
<v-tab | |||||
href="#timeslot" | |||||
> | |||||
Zeitslots | |||||
</v-tab> | |||||
<v-tab | |||||
href="#result" | |||||
> | |||||
Ergebnisse | |||||
</v-tab> | |||||
</v-tabs> | |||||
<v-tabs-items | |||||
v-model="tab" | |||||
> | |||||
<v-tab-item | |||||
value="apparatus" | |||||
> | |||||
<v-card | |||||
flat | |||||
class="ma-0" | |||||
> | |||||
<v-card-title class="text-h3"> | |||||
Geräte | |||||
<v-spacer /> | |||||
<v-menu> | |||||
<template #activator="{on, attrs}"> | |||||
<v-btn | |||||
icon | |||||
v-bind="attrs" | |||||
v-on="on" | |||||
> | |||||
<v-icon> | |||||
fa-plus | |||||
</v-icon> | |||||
</v-btn> | |||||
</template> | |||||
<v-list> | |||||
<v-list-item | |||||
v-for="a in geraete" | |||||
:key="a._id" | |||||
@click="addApparatus(a._id)" | |||||
> | |||||
<v-list-item-title>{{ a.name }}</v-list-item-title> | |||||
</v-list-item> | |||||
</v-list> | |||||
</v-menu> | |||||
</v-card-title> | |||||
<v-card-text> | |||||
<draggable | |||||
:list="[]" | |||||
handle=".handle" | |||||
tag="div" | |||||
class="row row-dense" | |||||
v-bind="dragOptions" | |||||
@end="orderApparatus" | |||||
> | |||||
<v-col | |||||
v-for="(a) in (Event || {}).apparatus" | |||||
:key="a.name" | |||||
cols="6" | |||||
lg="3" | |||||
class="item" | |||||
> | |||||
<v-card> | |||||
<v-card-title class="text-h4"> | |||||
<v-icon class="handle mr-4">far fa-bars</v-icon> | |||||
{{ a.apparatus.name }} | |||||
</v-card-title> | |||||
<v-card-text> | |||||
<p><b>Elemente: </b> {{ a.elements }}</p> | |||||
<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-spacer /> | |||||
<v-btn icon> | |||||
<v-icon>far fa-pencil</v-icon> | |||||
</v-btn> | |||||
<v-btn | |||||
icon | |||||
@click="deleteApparatus(a._apparatus)" | |||||
> | |||||
<v-icon>far fa-trash</v-icon> | |||||
</v-btn> | |||||
</v-card-actions> | |||||
</v-card> | |||||
</v-col> | |||||
</draggable> | |||||
</v-card-text> | |||||
</v-card> | |||||
</v-tab-item> | |||||
<v-tab-item | |||||
value="timeslot" | |||||
> | |||||
<v-card | |||||
flat | |||||
> | |||||
<v-btn | |||||
v-if="ts.selected.length > 0" | |||||
absolute | |||||
top | |||||
right | |||||
fab | |||||
small | |||||
style="margin-right:60px;" | |||||
@click="deleteTimeslots" | |||||
> | |||||
<v-icon> | |||||
fa-minus | |||||
</v-icon> | |||||
</v-btn> | |||||
<v-btn | |||||
absolute | |||||
top | |||||
right | |||||
fab | |||||
small | |||||
@click="ts.dialog.open = true" | |||||
> | |||||
<v-icon> | |||||
fa-plus | |||||
</v-icon> | |||||
</v-btn> | |||||
<v-data-table | |||||
:headers="ts.headers" | |||||
:items="timeslots" | |||||
sort-by="time" | |||||
:items-per-page="-1" | |||||
mobile-breakpoint="0" | |||||
> | |||||
<template #header.select> | |||||
<v-checkbox | |||||
@change="(v) => !v ? ts.selected = [] : ts.selected = timeslots.map(t => t._id)" | |||||
/> | |||||
</template> | |||||
<template #item.select="{item}"> | |||||
<v-checkbox | |||||
v-if="!item.team" | |||||
v-model="ts.selected" | |||||
:value="item._id" | |||||
multiple | |||||
/> | |||||
</template> | |||||
<template #item.time="{item}"> | |||||
{{ `1900-01-01 ${item.time}` | dateformat('HH:mm') }} | |||||
</template> | |||||
<template #item.end="{item}"> | |||||
{{ `1900-01-01 ${addMinutes(item.time, item.duration)}` | dateformat('HH:mm') }} | |||||
</template> | |||||
<template #item.team="{item}"> | |||||
<v-btn | |||||
v-if="item.team" | |||||
text | |||||
x-small | |||||
style="float: left;" | |||||
@click="deleteTeam(item._id)" | |||||
> | |||||
<v-icon small> | |||||
far fa-trash-alt | |||||
</v-icon> | |||||
</v-btn> | |||||
<base-edit-dialog | |||||
:value="item.team ? item.team.name : ''" | |||||
label="Team hinzufügen" | |||||
@input="(v) => addTeam(item._id, v)" | |||||
/> | |||||
</template> | |||||
</v-data-table> | |||||
</v-card> | |||||
<base-material-dialog | |||||
v-model="ts.dialog.open" | |||||
title="Zeitslots hinzufügen" | |||||
color="rgb(255, 4, 29)" | |||||
:actions="['save','cancel']" | |||||
@save="addTimeslots" | |||||
@close="ts.dialog.open = false" | |||||
@esc="ts.dialog.open = false" | |||||
> | |||||
<v-row> | |||||
<v-col | |||||
cols="12" | |||||
sm="6" | |||||
md="3" | |||||
> | |||||
<v-menu | |||||
ref="beginn" | |||||
v-model="ts.dialog.beginnmenu" | |||||
:close-on-content-click="false" | |||||
:nudge-right="40" | |||||
:return-value.sync="ts.dialog.beginn" | |||||
transition="scale-transition" | |||||
offset-y | |||||
max-width="290px" | |||||
min-width="290px" | |||||
> | |||||
<template v-slot:activator="{ on, attrs }"> | |||||
<v-text-field | |||||
v-model="ts.dialog.beginn" | |||||
label="Erster Zeitslot" | |||||
prepend-icon="far fa-clock" | |||||
readonly | |||||
v-bind="attrs" | |||||
v-on="on" | |||||
></v-text-field> | |||||
</template> | |||||
<v-time-picker | |||||
v-if="ts.dialog.beginnmenu" | |||||
v-model="ts.dialog.beginn" | |||||
full-width | |||||
format="24hr" | |||||
@click:minute="$refs.beginn.save(`${ts.dialog.beginn}:00`)" | |||||
></v-time-picker> | |||||
</v-menu> | |||||
</v-col> | |||||
<v-col | |||||
cols="12" | |||||
sm="6" | |||||
md="3" | |||||
> | |||||
<v-menu | |||||
ref="ende" | |||||
v-model="ts.dialog.endemenue" | |||||
:close-on-content-click="false" | |||||
:nudge-right="40" | |||||
:return-value.sync="ts.dialog.ende" | |||||
transition="scale-transition" | |||||
offset-y | |||||
max-width="290px" | |||||
min-width="290px" | |||||
> | |||||
<template v-slot:activator="{ on, attrs }"> | |||||
<v-text-field | |||||
v-model="ts.dialog.ende" | |||||
label="Bis" | |||||
prepend-icon="far fa-clock" | |||||
readonly | |||||
v-bind="attrs" | |||||
v-on="on" | |||||
></v-text-field> | |||||
</template> | |||||
<v-time-picker | |||||
v-if="ts.dialog.endemenue" | |||||
v-model="ts.dialog.ende" | |||||
full-width | |||||
format="24hr" | |||||
@click:minute="$refs.ende.save(`${ts.dialog.ende}:00`)" | |||||
></v-time-picker> | |||||
</v-menu> | |||||
</v-col> | |||||
<v-col | |||||
cols="12" | |||||
sm="6" | |||||
md="3" | |||||
> | |||||
<v-text-field | |||||
v-model="ts.dialog.duration" | |||||
label="Dauer eines Zeitslots (in min)" | |||||
type="number" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="12" | |||||
sm="6" | |||||
md="3" | |||||
> | |||||
<v-text-field | |||||
v-model="ts.dialog.pause" | |||||
label="Pause nach einem Zeitslot (in min)" | |||||
type="number" | |||||
/> | |||||
</v-col> | |||||
</v-row> | |||||
<h3>Es werden folgende {{ newtimeslots.length }} Zeitslots angelegt:</h3> | |||||
<v-data-table | |||||
:headers="ts.dialog.headers" | |||||
:items="newtimeslots" | |||||
:items-per-page="-1" | |||||
> | |||||
<template #item.time="{item}"> | |||||
{{ `1900-01-01 ${item.time}` | dateformat('HH:mm') }} | |||||
</template> | |||||
<template #item.end="{item}"> | |||||
{{ `1900-01-01 ${addMinutes(item.time, item.duration)}` | dateformat('HH:mm') }} | |||||
</template> | |||||
</v-data-table> | |||||
</base-material-dialog> | |||||
</v-tab-item> | |||||
<v-tab-item | |||||
value="result" | |||||
> | |||||
Ergebnisse | |||||
</v-tab-item> | |||||
</v-tabs-items> | |||||
</v-container> | |||||
</template> | |||||
<script> | |||||
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 } }' | |||||
export default { | |||||
name: 'Event', | |||||
props: { | |||||
id: { | |||||
type: String, | |||||
required: true | |||||
} | |||||
}, | |||||
data: () => ({ | |||||
Event: null, | |||||
ApparatusFind: [], | |||||
tab: 'timeslot', | |||||
ts: { | |||||
headers: [ | |||||
{ | |||||
text: '', | |||||
value: 'select', | |||||
sortable: false, | |||||
width: 75 | |||||
}, | |||||
{ | |||||
text: 'Beginn', | |||||
value: 'time', | |||||
sortable: false | |||||
}, | |||||
{ | |||||
text: 'Ende', | |||||
value: 'end', | |||||
sortable: false | |||||
}, | |||||
{ | |||||
text: 'Team', | |||||
value: 'team', | |||||
sortable: false | |||||
}, | |||||
{ | |||||
text: 'Ergebnis', | |||||
value: 'result', | |||||
sortable: false | |||||
} | |||||
], | |||||
selected: [], | |||||
dialog: { | |||||
open: false, | |||||
beginn: '09:00:00', | |||||
ende: '12:00:00', | |||||
duration: 4, | |||||
pause: 1, | |||||
beginnmenue: false, | |||||
endemenue: false, | |||||
headers: [ | |||||
{ | |||||
text: 'Beginn', | |||||
value: 'time', | |||||
sortable: false | |||||
}, | |||||
{ | |||||
text: 'Ende', | |||||
value: 'end', | |||||
sortable: false | |||||
} | |||||
] | |||||
} | |||||
} | |||||
}), | |||||
computed: { | |||||
dragOptions () { | |||||
return { | |||||
animation: 200, | |||||
disabled: false, | |||||
ghostClass: 'ghost' | |||||
} | |||||
}, | |||||
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)) || [] | |||||
}, | |||||
timeslots () { | |||||
return this.Event?.timeslots || [] | |||||
}, | |||||
newtimeslots () { | |||||
const neu = [] | |||||
if (this.ts.dialog.duration <= 0) return neu | |||||
for (let t = this.ts.dialog.beginn; t < this.ts.dialog.ende; t = moment(`1900-01-01 ${t}`).add(parseInt(this.ts.dialog.duration) + parseInt(this.ts.dialog.pause), 'minutes').format('HH:mm:ss')) { | |||||
neu.push(t) | |||||
} | |||||
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 })) | |||||
} | |||||
}, | |||||
methods: { | |||||
addApparatus (id) { | |||||
this.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!, $apparatus: UUID!) { | |||||
EventAddApparatus(id: $id, apparatus: $apparatus) { ${query} } | |||||
}`, | |||||
variables: { | |||||
id: this.id, | |||||
apparatus: id | |||||
} | |||||
}) | |||||
}, | |||||
deleteApparatus (id) { | |||||
this.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!, $apparatus: UUID!) { | |||||
EventDeleteApparatus(id: $id, apparatus: $apparatus) { ${query} } | |||||
}`, | |||||
variables: { | |||||
id: this.id, | |||||
apparatus: id | |||||
} | |||||
}) | |||||
}, | |||||
orderApparatus (a) { | |||||
const src = a.oldIndex | |||||
const target = a.newIndex | |||||
if (src === target) return | |||||
this.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!, $src: Int!, $target: Int!) { | |||||
EventOrderApparatus(id: $id, src: $src, target: $target) { ${query} } | |||||
}`, | |||||
variables: { | |||||
id: this.id, | |||||
src, | |||||
target | |||||
} | |||||
}).then((r) => { | |||||
this.Event.apparatus = r.data.EventOrderApparatus.apparatus | |||||
}) | |||||
}, | |||||
addTimeslots () { | |||||
if (this.timeslots.length > 0) { | |||||
this.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!, $timeslots: [ITimeslot!]!) { | |||||
EventAddTimeslots(id: $id, timeslots: $timeslots) { ${query} } | |||||
}`, | |||||
variables: { | |||||
id: this.id, | |||||
timeslots: this.newtimeslots | |||||
} | |||||
}).then((r) => { | |||||
this.Event.timeslots = r.data.EventAddTimeslots.timeslots | |||||
}) | |||||
} | |||||
this.ts.dialog.open = false | |||||
}, | |||||
deleteTimeslots () { | |||||
this.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!, $timeslots: [UUID!]!) { | |||||
EventDeleteTimeslots(id: $id, timeslots: $timeslots) { ${query} } | |||||
}`, | |||||
variables: { | |||||
id: this.id, | |||||
timeslots: this.ts.selected | |||||
} | |||||
}).then((r) => { | |||||
this.Event.timeslots = r.data.EventDeleteTimeslots.timeslots | |||||
this.ts.selected = [] | |||||
}) | |||||
}, | |||||
addMinutes (time, min) { | |||||
return moment(`1900-01-01 ${time}`).add(min, 'minutes').format('HH:mm:ss') | |||||
}, | |||||
addTeam (timeslot, name) { | |||||
this.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!, $timeslot: UUID!, $name: String!) { | |||||
EventAddTeam(id: $id, timeslot: $timeslot, name: $name) { ${query} } | |||||
}`, | |||||
variables: { | |||||
id: this.id, | |||||
timeslot, | |||||
name | |||||
} | |||||
}).then((r) => { | |||||
this.Event.timeslots = r.data.EventAddTeam.timeslots | |||||
this.ts.selected = [] | |||||
}) | |||||
}, | |||||
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?')) { | |||||
this.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!, $timeslot: UUID!) { | |||||
EventDeleteTeam(id: $id, timeslot: $timeslot) { ${query} } | |||||
}`, | |||||
variables: { | |||||
id: this.id, | |||||
timeslot | |||||
} | |||||
}).then((r) => { | |||||
this.Event.timeslots = r.data.EventDeleteTeam.timeslots | |||||
this.ts.selected = [] | |||||
}) | |||||
} | |||||
} | |||||
}, | |||||
apollo: { | |||||
Event: { | |||||
query: gql`query($event: UUID!) { Event(id: $event) { ${query} } }`, | |||||
variables () { | |||||
return { | |||||
event: this.id | |||||
} | |||||
}, | |||||
subscribeToMore: { | |||||
document: gql`subscription($id: UUID!) { EventUpdated(id: $id) { ${query} } }`, | |||||
variables () { | |||||
return { | |||||
id: this.id | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
ApparatusFind: { | |||||
query: gql`query { ApparatusFind { _id name }}` | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
.apparatus { | |||||
width: calc(25% - 20px); | |||||
margin: 10px; | |||||
float: left; | |||||
} | |||||
.draggable:after { | |||||
display: block; | |||||
content: ''; | |||||
clear: both; | |||||
} | |||||
</style> |
<template> | |||||
<base-material-dialog | |||||
:value="value" | |||||
icon="mdi-home" | |||||
title="Person bearbeiten" | |||||
:sub-title="id ? id : 'NEU'" | |||||
color="rgb(255, 4, 29)" | |||||
:actions="[doc && isMaster ? 'del' : '', isMaster ? 'save' : '', 'cancel']" | |||||
@del="del" | |||||
@save="save" | |||||
@close="close" | |||||
@esc="close" | |||||
> | |||||
<v-row v-if="!isMaster"> | |||||
Kein Zugriff! {{ isMaster }} | |||||
</v-row> | |||||
<v-row v-else> | |||||
<v-col | |||||
cols="12" | |||||
sm="6" | |||||
> | |||||
<v-text-field | |||||
v-model="data.name" | |||||
label="Name" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="4" | |||||
sm="2" | |||||
> | |||||
<v-text-field | |||||
v-model="data.plz" | |||||
label="PLZ" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="8" | |||||
sm="4" | |||||
> | |||||
<v-text-field | |||||
v-model="data.ort" | |||||
label="Ort" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="6" | |||||
> | |||||
<h3>Administratoren:</h3> | |||||
{{ admins }} | |||||
</v-col> | |||||
<v-col | |||||
cols="6" | |||||
> | |||||
<h3>Veranstalter:</h3> | |||||
{{ organizers }} | |||||
</v-col> | |||||
</v-row> | |||||
</base-material-dialog> | |||||
</template> | |||||
<script> | |||||
import { useAuth } from '@/plugins/auth' | |||||
import { useEditDialog } from '@/plugins/editdialog' | |||||
export default { | |||||
name: 'EditOrganizer', | |||||
props: { | |||||
value: { | |||||
type: Boolean, | |||||
required: true | |||||
}, | |||||
id: { | |||||
type: String, | |||||
default: null | |||||
} | |||||
}, | |||||
setup (props, context) { | |||||
return { | |||||
...useAuth(context), | |||||
...useEditDialog(props, context, 'Organizer(id: $id) { _id name plz ort admins { _id givenName familyName } organizers { _id givenName familyName } }', ['name', 'plz', 'ort']) | |||||
} | |||||
}, | |||||
computed: { | |||||
admins () { | |||||
return this.data.admins?.map(e => e.familyName + ', ' + e.givenName)?.join('<br>') | |||||
}, | |||||
organizers () { | |||||
return this.data.organizers?.map(e => e.familyName + ', ' + e.givenName)?.join('<br>') | |||||
} | |||||
}, | |||||
methods: { | |||||
update () { | |||||
return { | |||||
mutation: `mutation($id: UUID!, $name: String, $plz: Int, $ort: String) { | |||||
OrganizerUpdate(id: $id, name: $name, plz: $plz, ort: $ort) { | |||||
_id name plz ort | |||||
} | |||||
}`, | |||||
variables: { | |||||
plz: parseInt(this.data.plz) || null | |||||
} | |||||
} | |||||
}, | |||||
create () { | |||||
return { | |||||
mutation: `mutation($name: String!, $plz: Int, $ort: String) { | |||||
OrganizerCreate(name: $name, plz: $plz, ort: $ort) { | |||||
_id name plz ort | |||||
} | |||||
}`, | |||||
variables: { | |||||
plz: parseInt(this.data.plz) || null | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="sass"> | |||||
$dialog-elevation: 0 | |||||
</style> |
@click:row="open" | @click:row="open" | ||||
/> | /> | ||||
</v-card> | </v-card> | ||||
<!--edit-people | |||||
<edit-organizer | |||||
:id="dialog.id" | :id="dialog.id" | ||||
v-model="dialog.open" | v-model="dialog.open" | ||||
/--> | |||||
/> | |||||
</v-container> | </v-container> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import { useAuth } from '@/plugins/auth' | import { useAuth } from '@/plugins/auth' | ||||
import gql from 'graphql-tag' | import gql from 'graphql-tag' | ||||
// import { updateQuery, deleteQuery } from '@/graphql' | |||||
import { updateQuery, deleteQuery } from '@/plugins/graphql' | |||||
export default { | export default { | ||||
name: 'People', | name: 'People', | ||||
components: { | components: { | ||||
// EditPeople: () => import('./dialogs/EditPeople'), | |||||
EditOrganizer: () => import('./dialogs/EditOrganizer') | |||||
}, | }, | ||||
setup (props, context) { | setup (props, context) { | ||||
apollo: { | apollo: { | ||||
OrganizerFind: { | OrganizerFind: { | ||||
query: gql`query { OrganizerFind { _id name plz ort } }` | |||||
/* subscribeToMore: { | |||||
document: gql`subscription { PersonUpdated { _id vorname nachname geburtstag dtbid gymnet { id } email { email typ } tel { nummer typ } adresse { strasse hausnummer plz ort land typ } turnportal { typ gueltigvon gueltigbis lastcheck sperre festgeturnt verein vermerk } } }`, | |||||
updateQuery: updateQuery('PersonList', 'PersonUpdated') | |||||
} */ | |||||
} | |||||
/* $subscribe: { | |||||
query: gql`query { OrganizerFind { _id name plz ort admins { _id givenName familyName } organizers { _id givenName familyName } } }`, | |||||
subscribeToMore: { | |||||
document: gql`subscription { OrganizerUpdated { _id name plz ort admins { _id givenName familyName } organizers { _id givenName familyName } } }`, | |||||
updateQuery: updateQuery('OrganizerFind', 'OrganizerUpdated') | |||||
} | |||||
}, | |||||
$subscribe: { | |||||
PersonDeleted: { | PersonDeleted: { | ||||
query: gql`subscription { PersonDeleted }`, | |||||
query: gql`subscription { OrganizerDeleted }`, | |||||
result (id) { | result (id) { | ||||
deleteQuery('PersonList', 'PersonUpdated', this.PersonList, id) | |||||
}, | |||||
}, | |||||
}, */ | |||||
deleteQuery('OrganizerFind', 'OrganizerDeleted', this.OrganizerFind, id) | |||||
} | |||||
} | |||||
} | |||||
}, | }, | ||||
methods: { | methods: { |
query: gql`query { PersonFind { _id familyName givenName adminOf { name plz ort } organizerOf { name plz ort } } }`, | query: gql`query { PersonFind { _id familyName givenName adminOf { name plz ort } organizerOf { name plz ort } } }`, | ||||
subscribeToMore: { | subscribeToMore: { | ||||
document: gql`subscription { PersonUpdated { _id familyName givenName adminOf { name plz ort } organizerOf { name plz ort } } }`, | document: gql`subscription { PersonUpdated { _id familyName givenName adminOf { name plz ort } organizerOf { name plz ort } } }`, | ||||
updateQuery: updateQuery('PersonList', 'PersonUpdated') | |||||
updateQuery: updateQuery('PersonFind', 'PersonUpdated') | |||||
} | } | ||||
}, | }, | ||||
$subscribe: { | $subscribe: { |
<template> | |||||
<base-material-dialog | |||||
:value="value" | |||||
icon="mdi-home" | |||||
title="Event bearbeiten" | |||||
:sub-title="id ? id : 'NEU'" | |||||
color="rgb(255, 4, 29)" | |||||
:actions="[doc && orga ? 'del' : '', !id || orga ? 'save' : '', 'cancel']" | |||||
@del="del" | |||||
@save="save" | |||||
@close="close" | |||||
@esc="close" | |||||
> | |||||
<v-row v-if="id && (!data || !data._organizer)"> | |||||
Warte auf Daten... | |||||
</v-row> | |||||
<v-row v-else-if="id && !isOrga(data._organizer)"> | |||||
Kein Zugriff! | |||||
</v-row> | |||||
<v-row v-else> | |||||
<v-col | |||||
cols="12" | |||||
> | |||||
<date-selector | |||||
v-model="data.date" | |||||
label="Datum" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="12" | |||||
> | |||||
<v-text-field | |||||
v-model="data.name" | |||||
label="Name" | |||||
/> | |||||
</v-col> | |||||
</v-row> | |||||
</base-material-dialog> | |||||
</template> | |||||
<script> | |||||
import { useAuth } from '@/plugins/auth' | |||||
import { useEditDialog } from '@/plugins/editdialog' | |||||
export default { | |||||
name: 'EditEvent', | |||||
props: { | |||||
value: { | |||||
type: Boolean, | |||||
required: true | |||||
}, | |||||
id: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
organizer: { | |||||
type: String, | |||||
required: true | |||||
} | |||||
}, | |||||
setup (props, context) { | |||||
return { | |||||
...useAuth(context), | |||||
...useEditDialog(props, context, 'Event(id: $id) { _id name date _organizer }', ['name', 'date']) | |||||
} | |||||
}, | |||||
computed: { | |||||
orga () { | |||||
return this.isOrga(this.data?._organizer) | |||||
} | |||||
}, | |||||
methods: { | |||||
update () { | |||||
return { | |||||
mutation: `mutation($id: UUID!, $name: String, $date: Date) { | |||||
EventUpdate(id: $id, name: $name, date: $date) { | |||||
_id name date | |||||
} | |||||
}` | |||||
} | |||||
}, | |||||
create () { | |||||
return { | |||||
mutation: `mutation($name: String!, $date: Date!, $organizer: UUID!) { | |||||
EventCreate(name: $name, date: $date, organizer: $organizer) { | |||||
_id name date _organizer | |||||
} | |||||
}`, | |||||
variables: { | |||||
organizer: this.organizer | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="sass"> | |||||
$dialog-elevation: 0 | |||||
</style> |
<template> | |||||
<e403 v-if="!isOrga(id)" /> | |||||
<v-container | |||||
v-else | |||||
fluid | |||||
tag="section" | |||||
> | |||||
<v-card | |||||
flat | |||||
> | |||||
<v-btn | |||||
absolute | |||||
top | |||||
right | |||||
fab | |||||
small | |||||
@click="open(null)" | |||||
> | |||||
<v-icon> | |||||
fa-plus | |||||
</v-icon> | |||||
</v-btn> | |||||
<v-col> | |||||
<v-text-field | |||||
v-model="filter" | |||||
label="Filter" | |||||
/> | |||||
</v-col> | |||||
<v-data-table | |||||
:headers="headers" | |||||
:items="EventFind" | |||||
sort-by="date" | |||||
sort-desc | |||||
:items-per-page="15" | |||||
mobile-breakpoint="0" | |||||
:search="filter" | |||||
@click:row="open" | |||||
> | |||||
<template #item.date="{item}"> | |||||
{{ item.date | date }} | |||||
</template> | |||||
<template #item.open="{item}"> | |||||
<v-btn | |||||
fab | |||||
small | |||||
@click.stop="start(item)" | |||||
> | |||||
<v-icon>far fa-play</v-icon> | |||||
</v-btn> | |||||
</template> | |||||
</v-data-table> | |||||
</v-card> | |||||
<edit-event | |||||
:id="dialog.id" | |||||
v-model="dialog.open" | |||||
:organizer="id" | |||||
/> | |||||
</v-container> | |||||
</template> | |||||
<script> | |||||
import { useAuth } from '@/plugins/auth' | |||||
import gql from 'graphql-tag' | |||||
import { deleteQuery, updateQuery } from '@/plugins/graphql' | |||||
export default { | |||||
name: 'event', | |||||
components: { | |||||
EditEvent: () => import('./dialogs/EditEvent') | |||||
}, | |||||
setup (props, context) { | |||||
return { | |||||
...useAuth(context) | |||||
} | |||||
}, | |||||
props: { | |||||
id: { | |||||
type: String, | |||||
required: true | |||||
} | |||||
}, | |||||
data: () => ({ | |||||
EventFind: [], | |||||
headers: [ | |||||
{ | |||||
text: 'Datum', | |||||
value: 'date', | |||||
sortable: false | |||||
}, | |||||
{ | |||||
text: 'Name', | |||||
value: 'name', | |||||
sortable: false | |||||
}, | |||||
{ | |||||
text: 'Öffnen', | |||||
value: 'open', | |||||
sortable: false, | |||||
width: 100, | |||||
align: 'center' | |||||
} | |||||
], | |||||
dialog: { | |||||
open: false, | |||||
id: null | |||||
}, | |||||
filter: '' | |||||
}), | |||||
apollo: { | |||||
EventFind: { | |||||
query: gql`query($organizer: UUID!) { EventFind(organizer: $organizer) { _id name date _organizer } }`, | |||||
variables () { | |||||
return { | |||||
organizer: this.id | |||||
} | |||||
}, | |||||
subscribeToMore: { | |||||
document: gql`subscription($organizer: UUID!) { EventUpdated(organizer: $organizer) { _id name date _organizer } }`, | |||||
variables () { | |||||
return { | |||||
organizer: this.id | |||||
} | |||||
}, | |||||
updateQuery: updateQuery('EventFind', 'EventUpdated') | |||||
} | |||||
}, | |||||
$subscribe: { | |||||
EventDeleted: { | |||||
query: gql`subscription($organizer: UUID!) { EventDeleted(organizer: $organizer) }`, | |||||
variables () { | |||||
return { | |||||
organizer: this.id | |||||
} | |||||
}, | |||||
result (id) { | |||||
deleteQuery('EventFind', 'EventDeleted', this.EventFind, id) | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
methods: { | |||||
open (item) { | |||||
this.dialog.open = true | |||||
this.dialog.id = item?._id | |||||
}, | |||||
start (item) { | |||||
if (!item?._id) return | |||||
this.$router.push(`/event/${item._id}`) | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
</style> |
logo: String | logo: String | ||||
} | } | ||||
type Team { | |||||
_id: UUID! | |||||
name: String! | |||||
} | |||||
type Timeslot { | type Timeslot { | ||||
_id: UUID! | _id: UUID! | ||||
time: Time! | time: Time! | ||||
duration: Int! | |||||
team: Team | |||||
} | } | ||||
"""A time string HH:MM:SS""" | """A time string HH:MM:SS""" | ||||
scalar Time | scalar Time | ||||
type EventApparatus { | |||||
_apparatus: UUID! | |||||
elements: Int! | |||||
bonus: Int! | |||||
malus: Int! | |||||
apparatus: Apparatus! | |||||
} | |||||
type Event { | type Event { | ||||
_id: UUID! | _id: UUID! | ||||
name: String | name: String | ||||
date: Date! | date: Date! | ||||
apparatus: [EventApparatus!]! | |||||
timeslots: [Timeslot!]! | timeslots: [Timeslot!]! | ||||
_organizer: UUID! | _organizer: UUID! | ||||
organizer: Organizer! | organizer: Organizer! | ||||
ApparatusCreate(logo: String, name: String!): Apparatus! | ApparatusCreate(logo: String, name: String!): Apparatus! | ||||
ApparatusDelete(id: UUID!): UUID! | ApparatusDelete(id: UUID!): UUID! | ||||
OrganizerRegister(ort: String, plz: Int, name: String!): Organizer! | OrganizerRegister(ort: String, plz: Int, name: String!): Organizer! | ||||
OrganizerUpdate(ort: String, plz: Int, name: String, id: UUID!): Organizer! | |||||
OrganizerCreate(ort: String, plz: Int, name: String!): Organizer! | |||||
OrganizerDelete(id: UUID!): UUID! | |||||
EventCreate(name: String, date: Date!, organizer: UUID!): Event! | EventCreate(name: String, date: Date!, organizer: UUID!): Event! | ||||
EventUpdate(date: Date, name: String, id: UUID!): Event! | |||||
EventAddApparatus(apparatus: UUID!, id: UUID!): Event! | |||||
EventDeleteApparatus(apparatus: UUID!, id: UUID!): Event! | |||||
EventOrderApparatus(target: Int!, src: Int!, id: UUID!): Event! | |||||
EventAddTimeslots(timeslots: [ITimeslot!]!, id: UUID!): Event! | |||||
EventDeleteTimeslots(timeslots: [UUID!]!, id: UUID!): Event! | |||||
EventAddTeam(name: String!, timeslot: UUID!, id: UUID!): Event! | |||||
EventDeleteTeam(timeslot: UUID!, id: UUID!): Event! | |||||
EventDelete(id: UUID!): UUID! | |||||
} | |||||
input ITimeslot { | |||||
time: Time! | |||||
duration: Int! | |||||
} | } | ||||
type Subscription { | type Subscription { | ||||
PersonDeleted: UUID! | PersonDeleted: UUID! | ||||
ApparatusUpdated: Apparatus | ApparatusUpdated: Apparatus | ||||
ApparatusDeleted: UUID! | ApparatusDeleted: UUID! | ||||
OrganizerUpdated: Organizer! | |||||
OrganizerDeleted: UUID! | |||||
EventUpdated(organizer: UUID, id: UUID): Event! | |||||
EventDeleted(organizer: UUID!): UUID! | |||||
} | } |
import {EventService} from './event.service' | import {EventService} from './event.service' | ||||
import {EventResolverQ} from './resolver/event.query' | import {EventResolverQ} from './resolver/event.query' | ||||
import {EventResolverM} from './resolver/event.mutation' | import {EventResolverM} from './resolver/event.mutation' | ||||
import {EventResolverS} from './resolver/event.subscription' | |||||
import {EventApparatusResolver} from './resolver/eventapparatus' | |||||
@Module({ | @Module({ | ||||
providers: [ | providers: [ | ||||
EventResolverQ, EventResolverM, | |||||
EventResolverQ, EventResolverM, EventResolverS, | |||||
EventService, | EventService, | ||||
EventResolver, | EventResolver, | ||||
EventApparatusResolver, | |||||
], | ], | ||||
}) | }) | ||||
export class EventModule {} | export class EventModule {} |
import { UUID } from '../../global/scalars/UUID'; | import { UUID } from '../../global/scalars/UUID'; | ||||
import { Date } from '../../global/scalars/Date' | import { Date } from '../../global/scalars/Date' | ||||
import { Timeslot } from './Timeslot' | import { Timeslot } from './Timeslot' | ||||
import { Organizer } from '../../organizer/models/Organizer' | |||||
import {EventApparatus} from './EventApparatus' | |||||
@ObjectType() | @ObjectType() | ||||
export class Event { | export class Event { | ||||
@Field(() => Date, { nullable: false }) | @Field(() => Date, { nullable: false }) | ||||
date: Date | date: Date | ||||
@Field(() => [EventApparatus], { nullable: false }) | |||||
apparatus: EventApparatus[] | |||||
@Field(() => [Timeslot], { nullable: false }) | @Field(() => [Timeslot], { nullable: false }) | ||||
timeslots: Timeslot[] | timeslots: Timeslot[] | ||||
import {Field, Int, ObjectType} from '@nestjs/graphql'; | |||||
import { UUID } from '../../global/scalars/UUID'; | |||||
@ObjectType() | |||||
export class EventApparatus { | |||||
@Field(() => UUID,{ nullable: false }) | |||||
_apparatus: UUID | |||||
@Field(() => Int, { nullable: false }) | |||||
elements: number | |||||
@Field(() => Int, { nullable: false }) | |||||
bonus: number | |||||
@Field(() => Int, { nullable: false }) | |||||
malus: number | |||||
} |
import {Field, ObjectType} from '@nestjs/graphql'; | |||||
import { UUID } from '../../global/scalars/UUID'; | |||||
@ObjectType() | |||||
export class Team { | |||||
@Field(() => UUID,{ nullable: false }) | |||||
_id: UUID | |||||
@Field(() => String,{ nullable: false }) | |||||
name: string | |||||
} |
import { Field, ObjectType } from '@nestjs/graphql'; | |||||
import {Field, InputType, Int, ObjectType} from '@nestjs/graphql'; | |||||
import { UUID } from '../../global/scalars/UUID'; | import { UUID } from '../../global/scalars/UUID'; | ||||
import { Time } from '../../global/scalars/Time' | import { Time } from '../../global/scalars/Time' | ||||
import {Team} from './Team' | |||||
@ObjectType() | @ObjectType() | ||||
export class Timeslot { | export class Timeslot { | ||||
@Field(() => Time, { nullable: false }) | @Field(() => Time, { nullable: false }) | ||||
time: Time | time: Time | ||||
@Field(() => Int, { nullable: false }) | |||||
duration: number | |||||
@Field(() => Team, { nullable: true }) | |||||
team?: Team | |||||
} | |||||
@InputType() | |||||
export class ITimeslot { | |||||
@Field(() => Time, { nullable: false }) | |||||
time: Time | |||||
@Field(() => Int, { nullable: false }) | |||||
duration: number | |||||
} | } |
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql'; | |||||
import {Args, Context, Int, Mutation, Resolver} from '@nestjs/graphql'; | |||||
import { Event } from '../models/Event'; | import { Event } from '../models/Event'; | ||||
import { Client } from '../../client'; | import { Client } from '../../client'; | ||||
import { EventService } from '../event.service'; | import { EventService } from '../event.service'; | ||||
import {Date} from '../../global/scalars/Date' | import {Date} from '../../global/scalars/Date' | ||||
import {UUID} from '../../global/scalars/UUID' | import {UUID} from '../../global/scalars/UUID' | ||||
import {OrganizerService} from '../../organizer/organizer.service' | import {OrganizerService} from '../../organizer/organizer.service' | ||||
import {pubsub} from '../../main' | |||||
import {ITimeslot} from '../models/Timeslot' | |||||
import { v4 as uuid } from 'uuid' | |||||
@Resolver(() => Event) | @Resolver(() => Event) | ||||
export class EventResolverM { | export class EventResolverM { | ||||
throw new HttpException('Organizer-ID not found!', 404); | throw new HttpException('Organizer-ID not found!', 404); | ||||
} | } | ||||
return this.service.create(client, date, organizer, name); | |||||
const neu = await this.service.create(client, date, organizer, name); | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventUpdate( | |||||
@Context('client') client, Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('name', { type: () => String, nullable: true }) name: string, | |||||
@Args('date', { type: () => Date, nullable: true }) date: Date | |||||
): Promise<Event> { | |||||
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); | |||||
} | |||||
const set: { | |||||
name?: string, | |||||
date?: Date, | |||||
} = {}; | |||||
if (name !== undefined && name !== tmp.name) set.name = name; | |||||
if (date !== undefined && date !== tmp.date) set.date = date; | |||||
if (Object.values(set).length === 0) return tmp; | |||||
const neu = await this.service.update(client, tmp._id, {$set: set}); | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventAddApparatus( | |||||
@Context('client') client, Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('apparatus', { type: () => UUID, nullable: false }) apparatus: UUID, | |||||
): Promise<Event> { | |||||
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); | |||||
} | |||||
let neu: Event; | |||||
if (!tmp.apparatus) { | |||||
neu = await this.service.update(client, tmp._id, {$set: {apparatus: [{ _apparatus: apparatus, elements: 3, bonus: 3.00, malus: 5.00 }]}}); | |||||
} else { | |||||
neu = await this.service.update(client, tmp._id, {$push: { apparatus: { _apparatus: apparatus, elements: 3, bonus: 3.00, malus: 5.00 }}}) | |||||
} | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventDeleteApparatus( | |||||
@Context('client') client, Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('apparatus', { type: () => UUID, nullable: false }) apparatus: UUID, | |||||
): Promise<Event> { | |||||
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); | |||||
} | |||||
let neu: Event; | |||||
if (!tmp.apparatus) { | |||||
neu = tmp; | |||||
} else { | |||||
neu = await this.service.update(client, tmp._id, {$pull: { apparatus: { _apparatus: apparatus }}}) | |||||
} | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventOrderApparatus( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('src', { type: () => Int, nullable: false }) src: number, | |||||
@Args('target', { type: () => Int, nullable: false }) target: number, | |||||
): Promise<Event> { | |||||
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); | |||||
} | |||||
let neu: Event; | |||||
if (!tmp.apparatus) { | |||||
neu = tmp; | |||||
} else { | |||||
const app = tmp.apparatus.splice(src, 1) | |||||
if (app?.length === 1) tmp.apparatus.splice(target, 0, app[0]) | |||||
neu = await this.service.update(client, tmp._id, {$set: { apparatus: tmp.apparatus }}) | |||||
} | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventAddTimeslots( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('timeslots', { type: () => [ITimeslot], nullable: false}) timeslots: ITimeslot[], | |||||
): Promise<Event> { | |||||
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); | |||||
} | |||||
let neu: Event; | |||||
if (!tmp.timeslots) { | |||||
neu = await this.service.update(client, tmp._id, {$set: {timeslots: timeslots.map(t => ({_id: uuid(), ...t }))}}); | |||||
} else { | |||||
timeslots = timeslots.filter(n => !tmp.timeslots.find(o => o.time === n.time && o.duration === n.duration)); | |||||
neu = await this.service.update(client, tmp._id, {$push: { timeslots: { $each: timeslots.map(t => ({_id: uuid(), ...t })) }}}); | |||||
} | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventDeleteTimeslots( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('timeslots', { type: () => [UUID], nullable: false}) timeslots: UUID[], | |||||
): Promise<Event> { | |||||
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); | |||||
} | |||||
let neu: Event; | |||||
if (!tmp.timeslots) { | |||||
neu = tmp; | |||||
} else { | |||||
neu = await this.service.update(client, tmp._id, {$pull: { timeslots: { _id: { $in: timeslots } }}}); | |||||
} | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventAddTeam( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID, | |||||
@Args('name', { type: () => String, nullable: false }) name: string | |||||
): Promise<Event> { | |||||
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); | |||||
} | |||||
let neu: Event; | |||||
if (tmp.timeslots.find(t => t._id === timeslot).team) { | |||||
neu = await this.service.update( | |||||
client, | |||||
tmp._id, | |||||
{ $set: { 'timeslots.$[ts].team,name': name }}, | |||||
{ arrayFilters: [{ 'ts._id': timeslot }]} | |||||
); | |||||
} else { | |||||
neu = await this.service.update( | |||||
client, | |||||
tmp._id, | |||||
{ $set: { 'timeslots.$[ts].team': { _id: uuid(), name }}}, | |||||
{ arrayFilters: [{ 'ts._id': timeslot }]} | |||||
); | |||||
} | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Event, { nullable: false }) | |||||
async EventDeleteTeam( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID, | |||||
): Promise<Event> { | |||||
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, | |||||
{ $unset: { 'timeslots.$[ts].team': '' }}, | |||||
{ arrayFilters: [{ 'ts._id': timeslot }]} | |||||
); | |||||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => UUID, { nullable: false }) | |||||
async EventDelete( | |||||
@Context('client') client, Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
): Promise<UUID> { | |||||
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); | |||||
} | |||||
this.service.delete(client, id); | |||||
pubsub.publish('EventDeleted', { EventDeleted: id, organizer: tmp._organizer }); | |||||
return id; | |||||
} | } | ||||
} | } |
import {Args, Resolver, Subscription} from '@nestjs/graphql' | |||||
import {Event} from '../models/Event' | |||||
import {pubsub} from '../../main' | |||||
import {UUID} from '../../global/scalars/UUID' | |||||
import {EventService} from '../event.service' | |||||
@Resolver(() => Event) | |||||
export class EventResolverS { | |||||
constructor( | |||||
private readonly service: EventService | |||||
) {} | |||||
@Subscription(() => Event, { | |||||
nullable: false, | |||||
filter: async (payload, variables, context) => { | |||||
let ret = true; | |||||
if (ret && !!variables.organizer) | |||||
ret = payload?.EventUpdated?._organizer === variables?.organizer | |||||
&& (context?.client?.isMaster() || context?.client?.isAdmin(variables.organizer)); | |||||
if (ret && !!variables.id) | |||||
ret = payload?.EventUpdated?._id === variables?.id | |||||
return ret; | |||||
} | |||||
}) | |||||
async EventUpdated( | |||||
@Args('id', { type: () => UUID, nullable: true }) id: UUID, | |||||
@Args('organizer', { type: () => UUID, nullable: true }) organizer: UUID, | |||||
) { | |||||
return pubsub.asyncIterator('EventUpdated'); | |||||
} | |||||
@Subscription(() => UUID, { | |||||
nullable: false, | |||||
filter: async (payload, variables, context) => { | |||||
return payload?.organizer === variables?.organizer | |||||
&& (context?.client?.isMaster() || context?.client?.isAdmin(variables.organizer)) | |||||
} | |||||
}) | |||||
async EventDeleted( | |||||
@Args('organizer', { type: () => UUID, nullable: false }) organizer: UUID | |||||
) { | |||||
return pubsub.asyncIterator('EventDeleted'); | |||||
} | |||||
} |
import {Timeslot} from '../models/Timeslot' | import {Timeslot} from '../models/Timeslot' | ||||
import {Organizer} from '../../organizer/models/Organizer' | import {Organizer} from '../../organizer/models/Organizer' | ||||
import {OrganizerService} from '../../organizer/organizer.service' | import {OrganizerService} from '../../organizer/organizer.service' | ||||
import {EventApparatus} from '../models/EventApparatus' | |||||
@Resolver(() => Event) | @Resolver(() => Event) | ||||
export class EventResolver { | export class EventResolver { | ||||
return parent.date; | return parent.date; | ||||
} | } | ||||
@ResolveField(() => [Timeslot], { nullable: true }) | |||||
@ResolveField(() => [EventApparatus], { nullable: false }) | |||||
async apparatus( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: Event | |||||
): Promise<EventApparatus[]> { | |||||
return parent.apparatus || []; | |||||
} | |||||
@ResolveField(() => [Timeslot], { nullable: false }) | |||||
async timeslots( | async timeslots( | ||||
@Context('client') client: Client, | @Context('client') client: Client, | ||||
@Parent() parent: Event | @Parent() parent: Event |
import {Context, Float, Int, Parent, ResolveField, Resolver} from '@nestjs/graphql'; | |||||
import { Client } from '../../client'; | |||||
import { UUID } from '../../global/scalars/UUID'; | |||||
import {EventApparatus} from '../models/EventApparatus' | |||||
import {Apparatus} from '../../apparatus/models/Apparatus' | |||||
import {ApparatusService} from '../../apparatus/apparatus.service' | |||||
@Resolver(() => EventApparatus) | |||||
export class EventApparatusResolver { | |||||
@ResolveField(() => UUID, { nullable: false }) | |||||
async _apparatus( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: EventApparatus | |||||
): Promise<UUID> { | |||||
return parent._apparatus as UUID; | |||||
} | |||||
@ResolveField(() => Apparatus, { nullable: false }) | |||||
async apparatus( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: EventApparatus | |||||
): Promise<Apparatus> { | |||||
return new ApparatusService().findOneById(parent._apparatus); | |||||
} | |||||
@ResolveField(() => Int, { nullable: false }) | |||||
async elements( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: EventApparatus | |||||
): Promise<number> { | |||||
return parent.elements; | |||||
} | |||||
@ResolveField(() => Float, { nullable: false }) | |||||
async bonus( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: EventApparatus | |||||
): Promise<number> { | |||||
return parent.bonus; | |||||
} | |||||
@ResolveField(() => Float, { nullable: false }) | |||||
async malus( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: EventApparatus | |||||
): Promise<number> { | |||||
return parent.malus; | |||||
} | |||||
} |
import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||
import { OrganizerResolverQ } from './resolver/organizer.query'; | import { OrganizerResolverQ } from './resolver/organizer.query'; | ||||
import { OrganizerResolverM } from './resolver/organizer.mutation'; | import { OrganizerResolverM } from './resolver/organizer.mutation'; | ||||
import { OrganizerResolverS } from './resolver/organizer.subscription' | |||||
import { OrganizerService } from './organizer.service'; | import { OrganizerService } from './organizer.service'; | ||||
import { OrganizerResolver } from './resolver/organizer'; | import { OrganizerResolver } from './resolver/organizer'; | ||||
@Module({ | @Module({ | ||||
providers: [ | providers: [ | ||||
OrganizerResolverQ, OrganizerResolverM, | |||||
OrganizerResolverQ, OrganizerResolverM, OrganizerResolverS, | |||||
OrganizerService, | OrganizerService, | ||||
OrganizerResolver, | OrganizerResolver, | ||||
], | ], |
import { Client } from '../../client'; | import { Client } from '../../client'; | ||||
import { OrganizerService } from '../organizer.service'; | import { OrganizerService } from '../organizer.service'; | ||||
import { HttpException } from '@nestjs/common'; | import { HttpException } from '@nestjs/common'; | ||||
import {UUID} from '../../global/scalars/UUID' | |||||
import {pubsub} from '../../main' | |||||
@Resolver(() => Organizer) | @Resolver(() => Organizer) | ||||
export class OrganizerResolverM { | export class OrganizerResolverM { | ||||
return this.service.create(client, name, plz, ort); | return this.service.create(client, name, plz, ort); | ||||
return tmp[0]; | return tmp[0]; | ||||
} | } | ||||
@Mutation(() => Organizer, { nullable: false }) | |||||
async OrganizerUpdate( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('name', { nullable: true }) name?: string, | |||||
@Args('plz', { type: () => Int, nullable: true }) plz?: number, | |||||
@Args('ort', { nullable: true }) ort?: string | |||||
): Promise<Organizer> { | |||||
if (!client.isMaster() && !client.isAdmin(id)) throw new HttpException('Access denied', 403) | |||||
const tmp = await this.service.findOneById(id); | |||||
const set: { | |||||
name?: string, | |||||
plz?: number, | |||||
ort?: string, | |||||
} = {}; | |||||
if (name !== undefined && name !== tmp.name) set.name = name | |||||
if (plz !== undefined && plz !== tmp.plz) set.plz = plz | |||||
if (ort !== undefined && ort !== tmp.ort) set.ort = ort | |||||
if (Object.values(set).length === 0) return tmp; | |||||
const neu = await this.service.update(client, tmp._id, {$set: set}); | |||||
pubsub.publish('OrganizerUpdated', { OrganizerUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Organizer, { nullable: false }) | |||||
async OrganizerCreate( | |||||
@Context('client') client: Client, | |||||
@Args('name') name: string, | |||||
@Args('plz', { type: () => Int, nullable: true }) plz?: number, | |||||
@Args('ort', { nullable: true }) ort?: string, | |||||
): Promise<Organizer> { | |||||
if (!client.isMaster()) throw new HttpException('Access denied', 403); | |||||
const neu = await this.service.insert(client, { name, plz, ort }); | |||||
pubsub.publish('OrganizerUpdated', { OrganizerUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => UUID, { nullable: false }) | |||||
async OrganizerDelete( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID | |||||
): Promise<UUID> { | |||||
if (!client.isMaster() && !client.isAdmin(id)) throw new HttpException('Access denied', 403); | |||||
this.service.delete(client, id); | |||||
pubsub.publish('OrganizerDeleted', { OrganizerDeleted: id }); | |||||
return id; | |||||
} | |||||
} | } |
import {Resolver, Subscription} from '@nestjs/graphql' | |||||
import {Organizer} from '../models/Organizer' | |||||
import {pubsub} from '../../main' | |||||
import {UUID} from '../../global/scalars/UUID' | |||||
import {OrganizerService} from '../organizer.service' | |||||
@Resolver(() => Organizer) | |||||
export class OrganizerResolverS { | |||||
constructor( | |||||
private readonly service: OrganizerService | |||||
) {} | |||||
@Subscription(() => Organizer, { nullable: false }) | |||||
async OrganizerUpdated() { | |||||
return pubsub.asyncIterator('OrganizerUpdated'); | |||||
} | |||||
@Subscription(() => UUID, { nullable: false }) | |||||
async OrganizerDeleted() { | |||||
return pubsub.asyncIterator('OrganizerDeleted'); | |||||
} | |||||
} |
@Subscription(() => UUID, { nullable: false }) | @Subscription(() => UUID, { nullable: false }) | ||||
async PersonDeleted() { | async PersonDeleted() { | ||||
return pubsub.asyncIterator('PersonDeleted') | |||||
return pubsub.asyncIterator('PersonDeleted'); | |||||
} | } | ||||
} | } |