flaschengeist-schedule/src/components/overview/AgendaView.vue

301 lines
9.3 KiB
Vue

<template>
<q-dialog
:model-value="editor !== undefined"
persistent
transition-show="scale"
transition-hide="scale"
>
<q-card>
<div class="column">
<div class="col" align="right" style="position: sticky; top: 0; z-index: 999">
<q-btn round color="negative" icon="mdi-close" dense rounded @click="editDone(false)" />
</div>
<div class="col" style="margin: 0; padding: 0; margin-top: -2.4em">
<edit-event v-model="editor" @done="editDone" />
</div>
</div>
</q-card>
</q-dialog>
<q-card>
<div style="max-width: 1800px; width: 100%">
<div class="bg-primary text-white q-my-sm shadow-2 row justify-center">
<div class="col-xs-12 col-sm-9 row justify-center items-center">
<q-btn flat dense icon="mdi-chevron-left" title="previous" @click="calendarPrev" />
<q-separator vertical />
<q-btn flat dense
>{{ asMonth(selectedDate) }} {{ asYear(selectedDate) }}
<q-popup-proxy ref="proxy" transition-show="scale" transition-hide="scale">
<q-date
ref="datepicker"
:model-value="date(selectedDate)"
mask="YYYY-MM-DD"
no-unset
@update:model-value="updateDate"
><q-btn v-close-popup label="jetzt" dense flat @click="datepicker?.setToday()"
/></q-date>
</q-popup-proxy>
</q-btn>
<q-separator vertical />
<q-btn flat dense icon="mdi-chevron-right" title="next" @click="calendarNext" />
</div>
<div class="col-xs-12 col-sm-3 text-center">
<q-btn-toggle
v-if="$q.screen.gt.xs"
v-model="calendarView"
flat
stretch
toggle-color="black"
:options="[
{ label: 'Tag', value: 'day' },
{ label: 'Woche', value: 'week' },
]"
/>
</div>
</div>
<q-calendar-agenda
:model-value="date(realDate)"
:view="calendarRealView"
:max-days="calendarDays"
:weekdays="[1, 2, 3, 4, 5, 6, 0]"
locale="de-de"
style="height: 100%; min-height: 400px"
>
<template #head-day-label="{ scope: { timestamp } }">
{{ timestamp.day }}
<q-menu>
<q-list style="min-width: 100px">
<q-item exact clickable @click="create(timestamp.date)">
<q-item-section>Neue Veranstaltung</q-item-section>
</q-item>
</q-list>
</q-menu>
</template>
<template #day="{ scope: { timestamp } }">
<div itemref="" class="q-pa-sm" style="min-height: 200px">
<event-slot
v-for="(agenda, index) in events[timestamp.weekday]"
:key="index"
v-model="events[timestamp.weekday][index]"
class="q-mb-sm"
@remove-event="remove"
@edit-event="edit"
/>
</div>
</template>
</q-calendar-agenda>
</div>
</q-card>
</template>
<script lang="ts">
import { ComponentPublicInstance, computed, defineComponent, onBeforeMount, ref, watch } from 'vue';
import { QCalendarAgenda } from '@quasar/quasar-ui-qcalendar';
import { date, QDate, QPopupProxy, useQuasar } from 'quasar';
import { useRoute, useRouter } from 'vue-router';
import { EditableEvent, emptyEvent } from '../../store/models';
import { startOfWeek } from '@flaschengeist/api';
import { useEventStore } from '../../store';
import EventSlot from './slots/EventSlot.vue';
import EditEvent from '../management/EditEvent.vue';
export default defineComponent({
name: 'AgendaView',
components: { EventSlot, EditEvent, QCalendarAgenda: <ComponentPublicInstance>QCalendarAgenda },
setup() {
const store = useEventStore();
const quasar = useQuasar();
const route = useRoute();
const router = useRouter();
const datepicker = ref<QDate>();
const proxy = ref<QPopupProxy>();
// User selectable (day vs week)
const calendarView = ref('week');
// User selected date
const selectedDate = ref(date.buildDate({ hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }));
// Real view depending on the screen size
const calendarRealView = computed(() =>
calendarView.value === 'day' || quasar.screen.xs || quasar.screen.sm ? 'day' : 'week'
);
const calendarDays = computed(() => {
if (calendarView.value == 'day' || quasar.screen.xs) return 1;
if (calendarRealView.value == 'week') return 7;
return realDate.value.getDay() === 1 ? 3 : 4;
});
const realDate = computed(() => {
if (calendarView.value === 'day' || calendarRealView.value === 'week' || quasar.screen.xs)
return selectedDate.value;
const start = startOfWeek(selectedDate.value);
if (selectedDate.value.getDay() > 0 && selectedDate.value.getDay() <= 3) return start;
else return date.addToDate(start, { days: 3 });
});
const events = ref<Agendas>({});
const editor = ref<EditableEvent>();
interface Agendas {
[index: number]: FG.Event[];
}
onBeforeMount(async () => {
if (!Object.keys(route.query).includes('q_date')) {
const q_date = date.formatDate(selectedDate.value, 'YYYY-MM-DD');
await router.replace({ query: { ...route.query, q_date } });
} else {
selectedDate.value = date.extractDate(route.query.q_date as string, 'YYYY-MM-DD');
}
if (!Object.keys(route.query).includes('q_view')) {
const q_view = calendarView.value;
await router.replace({ query: { ...route.query, q_view } });
} else {
calendarView.value = route.query.q_view as string;
}
await loadAgendas();
});
function create(ds: string) {
editor.value = emptyEvent(date.extractDate(ds, 'YYYY-MM-DD'));
}
async function edit(id: number) {
editor.value = await store.getEvent(id);
}
function editDone(changed: boolean) {
if (changed) void loadAgendas();
editor.value = undefined;
}
async function remove(id: number) {
if (await store.removeEvent(id)) {
// Successfull removed
for (const idx in events.value) {
const i = events.value[idx].findIndex((event) => event.id === id);
if (i !== -1) {
events.value[idx].splice(i, 1);
break;
}
}
} else {
// Not found, this means our eventa are outdated
await loadAgendas();
}
}
const loading = ref(false);
async function loadAgendas() {
if (loading.value) return;
loading.value = true;
const from =
calendarView.value === 'day' ? selectedDate.value : startOfWeek(selectedDate.value);
const to = date.addToDate(from, { days: calendarView.value === 'day' ? 1 : 7 });
events.value = {};
const { result } = await store.getEvents({ from, to });
result.forEach((event) => {
const day = event.start.getDay();
if (!events.value[day]) {
events.value[day] = [];
}
const idx = events.value[day].findIndex((e) => e.id === event.id);
if (idx === -1) events.value[day].push(event);
else events.value[day][idx] = event;
});
loading.value = false;
}
function addDays(reverse: boolean) {
const oww = Math.floor((startOfWeek(selectedDate.value).getTime() / 1000) * 60 * 60 * 24);
selectedDate.value = date.addToDate(realDate.value, {
days: reverse ? -1 : calendarDays.value,
});
if (oww != Math.floor((startOfWeek(selectedDate.value).getTime() / 1000) * 60 * 60 * 24))
void loadAgendas();
}
function asMonth(value?: Date) {
return [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember',
'-',
][value?.getMonth() || 12];
}
function asYear(value?: Date) {
return value?.getFullYear() || '-';
}
watch(selectedDate, async (newValue) => {
const q_date = date.formatDate(newValue, 'YYYY-MM-DD');
await router.replace({ query: { ...route.query, q_date } });
await loadAgendas();
});
watch(calendarView, async (newValue) => {
const q_view = newValue;
await router.replace({ query: { ...route.query, q_view } });
await loadAgendas();
});
return {
asYear,
asMonth,
calendarDays,
calendarNext: () => addDays(false),
calendarPrev: () => addDays(true),
calendarRealView,
calendarView,
create,
date: (d: Date) => date.formatDate(d, 'YYYY-MM-DD'),
edit,
editor,
editDone,
events,
datepicker,
proxy,
realDate,
remove,
selectedDate,
updateDate: (ds: string) => {
selectedDate.value = date.adjustDate(date.extractDate(ds, 'YYYY-MM-DD'), {
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
});
proxy.value?.hide();
},
};
},
});
</script>
<style>
@import '@quasar/quasar-ui-qcalendar/dist/index.css';
/* Fill full height of card */
.q-calendar-agenda__pane {
height: 100%;
}
/* Same as Qcard */
.q-calendar {
--calendar-background-dark: --q-dark;
}
</style>