Finished Basic Calendar Week Functionality

This commit is contained in:
Dominik 2021-02-06 00:07:58 +01:00
parent e03b2f20ff
commit cc47e21a31
7 changed files with 396 additions and 16 deletions

3
quasar.extensions.json Normal file
View File

@ -0,0 +1,3 @@
{
"@quasar/qcalendar": {}
}

View File

@ -71,7 +71,7 @@ import { Store } from 'vuex';
import { StateInterface } from 'src/store'; import { StateInterface } from 'src/store';
import { ScheduleInterface } from '../../store/schedule'; import { ScheduleInterface } from '../../store/schedule';
import { date } from 'quasar'; import { date } from 'quasar';
// import { emit } from 'process';
export default defineComponent({ export default defineComponent({
name: 'CreateEvent', name: 'CreateEvent',
components: { IsoDateInput, Job }, components: { IsoDateInput, Job },
@ -140,16 +140,21 @@ export default defineComponent({
function save() { function save() {
console.log('Event:', event); console.log('Event:', event);
store.dispatch('schedule/addEvent', event.value).catch(error => { void store
console.warn(error); .dispatch('schedule/addEvent', event.value)
}); .catch(error => {
console.warn(error);
})
.then(() => {
reset();
});
} }
function reset() { function reset() {
event.value.id = NaN; event.value.id = NaN;
event.value.start = new Date(); event.value.start = new Date();
event.value.description = ''; event.value.description = '';
event.value.type = {id: -1, name: ''}; event.value.type = { id: -1, name: '' };
event.value.jobs = [Object.assign({}, newJob.value)]; event.value.jobs = [Object.assign({}, newJob.value)];
} }
function notEmpty(val: string) { function notEmpty(val: string) {
@ -183,4 +188,4 @@ export default defineComponent({
}); });
</script> </script>
<style scoped></style> <style></style>

View File

@ -21,6 +21,7 @@
</q-card-section> </q-card-section>
<q-card-section class=" row fit justify-start content-center items-center"> <q-card-section class=" row fit justify-start content-center items-center">
<q-select <q-select
:key="refreshKey"
filled filled
use-input use-input
label="Dienstart" label="Dienstart"
@ -61,7 +62,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from '@vue/composition-api'; import { defineComponent, computed, ref } from '@vue/composition-api';
import IsoDateInput from 'src/components/utils/IsoDateInput.vue'; import IsoDateInput from 'src/components/utils/IsoDateInput.vue';
import { Store } from 'vuex'; import { Store } from 'vuex';
import { StateInterface } from 'src/store'; import { StateInterface } from 'src/store';
@ -82,11 +83,18 @@ export default defineComponent({
default: false default: false
} }
}, },
watch: {
job: function() {
// this.type.name = this.type.name;
this.$props.job.type = this.$props.job.type;
}
},
setup(props: Props, { root, emit }) { setup(props: Props, { root, emit }) {
const store = <Store<StateInterface>>root.$store; const store = <Store<StateInterface>>root.$store;
const state = <ScheduleInterface>store.state.schedule; const state = <ScheduleInterface>store.state.schedule;
const jobtypes = computed(() => state.jobTypes); const jobtypes = computed(() => state.jobTypes);
// job = computed(() => job);
function setStart(value: Date) { function setStart(value: Date) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-call
emit('set-start', { job: props.job, value }); emit('set-start', { job: props.job, value });
@ -106,6 +114,7 @@ export default defineComponent({
// eslint-disable-next-line @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-call
console.log('setJobType', value); console.log('setJobType', value);
emit('set-job-type', { job: props.job, value }); emit('set-job-type', { job: props.job, value });
refresh();
} }
function setRequired(value: number) { function setRequired(value: number) {
@ -131,6 +140,12 @@ export default defineComponent({
return props.job.start < new Date(val) || 'Ende muss hinter dem Start liegen'; return props.job.start < new Date(val) || 'Ende muss hinter dem Start liegen';
} }
const refreshKey = ref(0);
function refresh() {
refreshKey.value += 1;
}
return { return {
jobtypes, jobtypes,
setStart, setStart,
@ -141,7 +156,8 @@ export default defineComponent({
removeJob, removeJob,
notEmpty, notEmpty,
noValidDate, noValidDate,
isAfterDate isAfterDate,
refreshKey
}; };
} }
}); });

View File

@ -0,0 +1,165 @@
<template>
<q-page padding>
<q-card>
<template>
<div style="max-width: 1800px; width: 100%;">
<q-toolbar class="bg-primary text-white q-my-md shadow-2 items-center row justify-center">
<div class="row justify-center items-center">
<q-btn flat dense label="Prev" @click="calendarPrev" />
<q-separator vertical />
<q-btn flat dense
>{{ selectedDate | toMonth }} {{ selectedDate | toYear }}
<q-popup-proxy
@before-show="updateProxy"
transition-show="scale"
transition-hide="scale"
>
<q-date v-model="proxyDate">
<div class="row items-center justify-end q-gutter-sm">
<q-btn label="Cancel" color="primary" flat v-close-popup />
<q-btn
label="OK"
color="primary"
flat
@click="saveNewSelectedDate(proxyDate)"
v-close-popup
/>
</div>
</q-date>
</q-popup-proxy>
</q-btn>
<q-separator vertical />
<q-btn flat dense label="Next" @click="calendarNext" />
</div>
</q-toolbar>
<q-calendar
v-model="selectedDate"
view="week-agenda"
:weekdays="[1, 2, 3, 4, 5, 6, 0]"
locale="de-de"
style="height: 100%; min-height: 400px;"
ref="calendar"
>
<template #day-body="{ timestamp }" style="min-height: 200px;">
<template !v-if="getAgenda(timestamp)" style="min-height: 200px;"> </template>
<template v-for="agenda in getAgenda(timestamp)">
<eventslot :event="agenda" :key="agenda.id" />
</template>
</template>
</q-calendar>
</div>
</template>
</q-card>
</q-page>
</template>
<script lang="ts">
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { computed, defineComponent, onBeforeMount, ref } from '@vue/composition-api';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
import { ScheduleInterface } from '../../store/schedule';
import Eventslot from './slots/EventSlot.vue';
import { date } from 'quasar';
export default defineComponent({
name: 'AgendaView',
components: { Eventslot },
filters: {
toMonth: function(value: string) {
if (value) {
return date.formatDate(new Date(value), 'MMMM', {
months: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
]
});
}
},
toYear: function(value: string) {
if (value) {
return date.formatDate(new Date(value), 'YYYY');
}
}
},
setup(_, { root }) {
const store = <Store<StateInterface>>root.$store;
const state = <ScheduleInterface>store.state.schedule;
const selectedDate = ref(date.formatDate(new Date(), 'YYYY-MM-DD'));
const proxyDate = ref('');
const events2 = computed(() => {
console.log(state.events);
let agenda: Agendas = {};
state.events
.filter(event => {
const thisWeek = date.formatDate(new Date(selectedDate.value), 'w');
console.log(thisWeek, date.formatDate(event.start, 'w'));
return date.formatDate(event.start, 'w') == thisWeek;
})
.forEach(event => {
let day = event.start.getDay();
console.log('event', event, day, !agenda[day]);
if (!agenda[day]) {
agenda[day] = [];
}
agenda[day].push(event);
});
console.log('finish agenda:', agenda);
return agenda;
});
interface Agendas {
[index: number]: FG.Event[];
}
onBeforeMount(() => {
void store.dispatch('schedule/getEvents');
// void store.dispatch('schedule/getJobTypes');
});
function getAgenda(this: any, day: { weekday: string }) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return this.events2[parseInt(day.weekday, 10)];
}
function calendarNext(this: any) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return this.$refs.calendar.next();
}
function calendarPrev(this: any) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return this.$refs.calendar.prev();
}
function updateProxy(this: any) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
proxyDate.value = selectedDate.value;
}
function saveNewSelectedDate(this: any) {
proxyDate.value = date.formatDate(proxyDate.value, 'YYYY-MM-DD');
selectedDate.value = proxyDate.value;
}
return {
selectedDate,
events2,
getAgenda,
calendarNext,
calendarPrev,
updateProxy,
saveNewSelectedDate,
proxyDate
};
}
});
</script>
<style></style>

View File

@ -0,0 +1,82 @@
<template>
<q-card
class="background: radial-gradient(circle, #35a2ff 0%, #014a88 100%) justify-start content-center items-center "
bordered
>
{{ event.type.name }}
<div v-if="event.description" class=" col-12 q-px-sm" style="font-size: 10px">
Info
{{ event.description }}
</div>
<div v-for="(job, index) in event.jobs" v-bind:key="index">
<q-separator style="justify-start content-center" />
<div>{{ job.start | formatToHour }} - {{ job.end | formatToHour }}</div>
<div>
{{ job.type.name }}
</div>
<div class="col-12 q-px-sm" style="font-size: 10px">
{{ job.comment }}
</div>
<div>
<q-select
filled
v-model="availableUsers"
multiple
:options="availableUsers"
use-chips
stack-label
label="Dienste"
class="col-12 q-px-sm"
style="font-size: 10px "
counter
:max-values="job.required_services"
>
</q-select>
</div>
</div>
</q-card>
</template>
<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
import { ScheduleInterface } from '../../../store/schedule';
import { date } from 'quasar';
interface Props {
event: FG.Event;
}
export default defineComponent({
name: 'Eventslot',
components: {},
props: {
event: {
required: true
}
},
filters: {
formatToHour: function(value: Date) {
if (value) {
return date.formatDate(value, 'HH:mm');
}
}
},
setup(props: Props, { root }) {
const store = <Store<StateInterface>>root.$store;
const state = <ScheduleInterface>store.state.schedule;
const availableUsers = null;
const refreshKey = ref(0);
return {
refreshKey,
availableUsers
};
}
});
</script>
<style></style>

View File

@ -1,8 +1,100 @@
<template> <template>
<q-page padding> <div>
<q-card> <q-tabs v-model="tab" v-if="$q.screen.gt.sm">
<q-card-section class="row"> </q-card-section> <q-tab
<q-card-section> </q-card-section> v-for="(tabindex, index) in tabs"
</q-card> :key="'tab' + index"
</q-page> :name="tabindex.name"
:label="tabindex.label"
/>
</q-tabs>
<div class="fit row justify-end" v-else>
<q-btn flat round icon="mdi-menu" @click="showDrawer = !showDrawer" />
</div>
<q-drawer side="right" v-model="showDrawer" @click="showDrawer = !showDrawer" behavior="mobile">
<q-list v-model="tab">
<q-item
v-for="(tabindex, index) in tabs"
:key="'tab' + index"
:active="tab == tabindex.name"
clickable
@click="tab = tabindex.name"
>
<q-item-label>{{ tabindex.label }}</q-item-label>
</q-item>
</q-list>
</q-drawer>
<q-page padding class="fit row justify-center content-start items-start q-gutter-sm">
<q-tab-panels
v-model="tab"
style="background-color: transparent;"
class="q-ma-none q-pa-none fit row justify-center content-start items-start"
animated
>
<q-tab-panel name="agendaView">
<AgendaView />
</q-tab-panel>
<q-tab-panel name="eventtypes">
<Eventtypes />
</q-tab-panel>
<q-tab-panel name="jobtypes">
<JobTypes v-if="canEditRoles" />
</q-tab-panel>
</q-tab-panels>
</q-page>
</div>
</template> </template>
<script lang="ts">
import { computed, defineComponent, ref } from '@vue/composition-api';
import Eventtypes from '../components/management/Eventtypes.vue';
import JobTypes from '../components/management/JobTypes.vue';
import CreateEvent from '../components/management/CreateEvent.vue';
import AgendaView from '../components/overview/AgendaView.vue';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
import { hasPermission } from 'src/utils/permission';
import { PERMISSIONS } from '../permissions';
import { Screen } from 'quasar';
export default defineComponent({
name: 'EventOverview',
components: { AgendaView, Eventtypes, JobTypes },
setup(_, { root }) {
const store = <Store<StateInterface>>root.$store;
const canEditRoles = computed(() => hasPermission(PERMISSIONS.ROLES_EDIT, store));
interface Tab {
name: string;
label: string;
}
const tabs: Tab[] = [
{ name: 'agendaView', label: 'Wochenkalendar' }
// { name: 'eventtypes', label: 'Veranstaltungsarten' },
// { name: 'jobtypes', label: 'Dienstarten' }
];
const drawer = ref<boolean>(false);
const showDrawer = computed({
get: () => {
return !Screen.gt.sm && drawer.value;
},
set: (val: boolean) => {
drawer.value = val;
}
});
const tab = ref<string>('agendaView');
return {
canEditRoles,
showDrawer,
tab,
tabs
};
}
});
</script>

View File

@ -78,6 +78,9 @@ const mutations: MutationTree<ScheduleInterface> = {
}, },
addEvent(state, event: FG.Event) { addEvent(state, event: FG.Event) {
state.events.unshift(event); state.events.unshift(event);
},
setEvents(state, events: FG.Event[]) {
state.events = events;
} }
}; };
@ -174,13 +177,27 @@ const actions: ActionTree<ScheduleInterface, StateInterface> = {
addEvent({ commit }, event: Event) { addEvent({ commit }, event: Event) {
axios axios
.post('/schedule/events', event) .post('/schedule/events', event)
.then((response: AxiosResponse<EventType>) => { .then((response: AxiosResponse<Event>) => {
commit('addEvent', response.data); commit('addEvent', response.data);
}) })
.catch(err => { .catch(err => {
console.warn(err); console.warn(err);
}); });
console.log('Events: ', state.events); console.log('Events: ', state.events);
},
getEvents({ commit }) {
axios
.get('/schedule/events')
.then((response: AxiosResponse<Event[]>) => {
console.log('action:', response.data);
response.data.forEach(event => {
event.start = new Date(event.start);
});
commit('setEvents', response.data);
})
.catch(err => {
console.warn(err);
});
} }
}; };
const getters: GetterTree<ScheduleInterface, StateInterface> = { const getters: GetterTree<ScheduleInterface, StateInterface> = {