Compare commits

...

6 Commits

8 changed files with 93 additions and 45 deletions

View File

@ -1,25 +1,53 @@
<template> <template>
<q-card class="row justify-center content-center" style="text-align: center"> <q-card style="text-align: center">
<q-card-section> <q-card-section class="row justify-center items-center content-center">
<div class="text-h6 col-12">Dienste diesen Monat: {{ jobs }}</div> <div class="col-5">
<!--TODO: Filters are deprecated! --> <q-icon :name="jobs == 0 ? 'mdi-calendar-blank' : 'mdi-calendar-alert'" :size="divHeight" />
<!--<div class="text-h6 col-12">Nächster Dienst: {{ nextJob | date }}</div>--> </div>
<div v-if="(jobs || 0) > 0" ref="div" class="col-7">
<div class="text-h6">Anstehende Dienste</div>
<div class="text-body1">{{ jobs }}</div>
<div class="text-h6">Nächster Dienst</div>
<div class="text-body1">{{ formatDate(nextJob) }}</div>
</div>
<div v-else ref="div" class="col-7">
<div class="text-subtitle1">Keine anstehenden Dienste</div>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { date } from 'quasar';
import { computed, defineComponent, onBeforeMount, ref } from 'vue';
import { asHour, formatDateTime } from '@flaschengeist/api';
import { useEventStore } from '../store';
export default defineComponent({ export default defineComponent({
name: 'EventsWidget', name: 'EventsWidget',
setup() { setup() {
function randomNumber(start: number, end: number) { const store = useEventStore();
return start + Math.floor(Math.random() * Math.floor(end));
const jobs = ref<number>();
const nextJob = ref<Date>();
const div = ref<HTMLElement>();
const divHeight = computed(() => `${div.value?.scrollHeight || '100'}px`);
onBeforeMount(() => {
void store.getJobs({ limit: 1, from: new Date() }).then(({ count, result }) => {
jobs.value = count;
nextJob.value = count > 0 ? result[0].start : undefined;
});
});
function formatDate(d?: Date) {
if (d === undefined) return '-';
if (date.isSameDate(d, new Date(), 'day')) return `Heute ${asHour(d)} Uhr`;
return formatDateTime(d, true, true, false, true) + ' Uhr';
} }
const jobs = randomNumber(0, 5);
const nextJob = new Date(2021, randomNumber(1, 12), randomNumber(1, 31)); return { div, divHeight, formatDate, jobs, nextJob };
return { jobs, nextJob };
}, },
}); });
</script> </script>

View File

@ -64,7 +64,7 @@
{{ timestamp.day }} {{ timestamp.day }}
<q-menu> <q-menu>
<q-list style="min-width: 100px"> <q-list style="min-width: 100px">
<q-item exact :to="{ name: 'new-event', query: { date: timestamp.date } }"> <q-item exact clickable @click="create(timestamp.date)">
<q-item-section>Neue Veranstaltung</q-item-section> <q-item-section>Neue Veranstaltung</q-item-section>
</q-item> </q-item>
</q-list> </q-list>
@ -95,6 +95,7 @@ import { date, QDate, QPopupProxy, useQuasar } from 'quasar';
import { startOfWeek } from '@flaschengeist/api'; import { startOfWeek } from '@flaschengeist/api';
import EditEvent from '../management/EditEvent.vue'; import EditEvent from '../management/EditEvent.vue';
import { QCalendarAgenda } from '@quasar/quasar-ui-qcalendar'; import { QCalendarAgenda } from '@quasar/quasar-ui-qcalendar';
import { EditableEvent, emptyEvent } from '../../store/models';
export default defineComponent({ export default defineComponent({
name: 'AgendaView', name: 'AgendaView',
@ -115,7 +116,7 @@ export default defineComponent({
calendarView.value == 'day' || quasar.screen.xs ? 1 : quasar.screen.sm ? 3 : 7 calendarView.value == 'day' || quasar.screen.xs ? 1 : quasar.screen.sm ? 3 : 7
); );
const events = ref<Agendas>({}); const events = ref<Agendas>({});
const editor = ref<FG.Event | undefined>(undefined); const editor = ref<EditableEvent>();
interface Agendas { interface Agendas {
[index: number]: FG.Event[]; [index: number]: FG.Event[];
@ -125,6 +126,9 @@ export default defineComponent({
await loadAgendas(); await loadAgendas();
}); });
function create(ds: string) {
editor.value = emptyEvent(date.extractDate(ds, 'YYYY-MM-DD'));
}
async function edit(id: number) { async function edit(id: number) {
editor.value = await store.getEvent(id); editor.value = await store.getEvent(id);
} }
@ -156,12 +160,12 @@ export default defineComponent({
minutes: 0, minutes: 0,
hours: 0, hours: 0,
}); });
const start = calendarRealView.value === 'day' ? selected : startOfWeek(selected); const start = calendarView.value === 'day' ? selected : startOfWeek(selected);
const end = date.addToDate(start, { days: calendarDays.value }); const end = date.addToDate(start, { days: calendarDays.value });
events.value = {}; events.value = {};
const list = await store.getEvents({ from: start, to: end }); const { result } = await store.getEvents({ from: start, to: end });
list.forEach((event) => { result.forEach((event) => {
const day = event.start.getDay(); const day = event.start.getDay();
if (!events.value[day]) { if (!events.value[day]) {
@ -221,6 +225,7 @@ export default defineComponent({
calendarPrev, calendarPrev,
calendarRealView, calendarRealView,
calendarView, calendarView,
create,
edit, edit,
editor, editor,
editDone, editDone,

View File

@ -26,7 +26,7 @@
</q-item> </q-item>
<template v-for="(events, index) in agendas" :key="index"> <template v-for="(events, index) in agendas" :key="index">
<q-separator /> <q-separator />
<q-item-label overline>{{index}}</q-item-label> <q-item-label overline>{{ index }}</q-item-label>
<q-item v-for="(event, idx) in events" :key="idx" <q-item v-for="(event, idx) in events" :key="idx"
><event-slot :model-value="event" />{{ idx }}</q-item ><event-slot :model-value="event" />{{ idx }}</q-item
> >
@ -63,7 +63,7 @@ export default defineComponent({
const editor = ref<FG.Event | undefined>(undefined); const editor = ref<FG.Event | undefined>(undefined);
const events = ref<FG.Event[]>([]); const events = ref<FG.Event[]>([]);
const scrollDiv = ref<Element>() const scrollDiv = ref<Element>();
const agendas = computed<Agendas>(() => { const agendas = computed<Agendas>(() => {
const ag = {} as Agendas; const ag = {} as Agendas;
@ -94,21 +94,24 @@ export default defineComponent({
async function load(index: number, done?: (stop: boolean) => void) { async function load(index: number, done?: (stop: boolean) => void) {
const start = new Date(); const start = new Date();
if (index < 0) { if (index < 0) {
events.value.unshift(...(await store.getEvents({ to: start, limit: 5, descending: true }))); const { result } = await store.getEvents({ to: start, limit: 5, descending: true });
events.value.unshift(...result);
if (done) done(false); if (done) done(false);
} else { } else {
const len = events.value.length; const len = events.value.length;
if ( const { result } = await store.getEvents({
len == from: start,
events.value.push( offset: (index - 1) * 10,
...(await store.getEvents({ from: start, offset: (index - 1) * 10, limit: 10 })) limit: 10,
) });
) { if (len == events.value.push(...result)) {
if (done) return done(true); if (done) return done(true);
} else if (done) done(false); } else if (done) done(false);
} }
if (index <= 1) { if (index <= 1) {
window.setTimeout(() => {(<Element>scrollDiv.value).scrollTop = document.getElementById("bbb")?.scrollHeight || 0}, 150); window.setTimeout(() => {
(<Element>scrollDiv.value).scrollTop = document.getElementById('bbb')?.scrollHeight || 0;
}, 150);
} }
} }
@ -159,7 +162,8 @@ export default defineComponent({
asYear, asYear,
asMonth, asMonth,
edit, edit,
editor,scrollDiv, editor,
scrollDiv,
editDone, editDone,
load, load,
remove, remove,

10
src/events.d.ts vendored
View File

@ -1,4 +1,4 @@
import { FG_Plugin } from "@flaschengeist/types"; import { FG_Plugin } from '@flaschengeist/types';
export interface RecurrenceRule { export interface RecurrenceRule {
frequency: string; frequency: string;
@ -13,13 +13,13 @@ interface InvitationData {
} }
interface InvitationResponseData { interface InvitationResponseData {
event: number, event: number;
job: number, job: number;
invitee: string invitee: string;
} }
export interface EventNotification extends FG_Plugin.Notification { export interface EventNotification extends FG_Plugin.Notification {
data: { data: {
type: number type: number;
} & (InvitationData | InvitationResponseData); } & (InvitationData | InvitationResponseData);
} }

View File

@ -30,7 +30,10 @@ function transpile(msg: FG_Plugin.Notification) {
message.link = { name: 'events-requests' }; message.link = { name: 'events-requests' };
} else if ((message.data.type & EventTypes._mask_) === EventTypes.invitation_response) { } else if ((message.data.type & EventTypes._mask_) === EventTypes.invitation_response) {
message.link = {name: 'events-single-view', params: {id: (<InvitationResponseData>message.data).event}} message.link = {
name: 'events-single-view',
params: { id: (<InvitationResponseData>message.data).event },
};
} }
return message; return message;
} }

View File

@ -53,7 +53,7 @@ export const privateRoutes: FG_Plugin.NamedRouteRecordRaw[] = [
name: 'events-single-view', name: 'events-single-view',
path: 'events/:id', path: 'events/:id',
component: () => import('../pages/EventPage.vue'), component: () => import('../pages/EventPage.vue'),
props: true props: true,
}, },
{ {
name: 'events-edit', name: 'events-edit',

View File

@ -107,13 +107,15 @@ export const useEventStore = defineStore({
}, },
async getEvents( async getEvents(
filter: filter?: FG.PaginationFilter & {
| { from?: Date; to?: Date; limit?: number; offset?: number; descending?: boolean } user?: string;
| undefined = undefined }
) { ) {
try { try {
const { data } = await api.get<FG.Event[]>('/events', { params: filter }); const { data } = await api.get<FG.PaginationResponse<FG.Event>>('/events', {
data.forEach((element) => fixEvent(element)); params: <unknown>filter,
});
data.result.forEach((element) => fixEvent(element));
return data; return data;
} catch (error) { } catch (error) {
throw error; throw error;
@ -166,6 +168,15 @@ export const useEventStore = defineStore({
.then(({ data }) => fixJob(data)); .then(({ data }) => fixJob(data));
}, },
async getJobs(filter?: FG.PaginationFilter) {
return api
.get<FG.PaginationResponse<FG.Job>>('/events/jobs', { params: <unknown>filter })
.then(({ data }) => {
data.result.forEach((j) => fixJob(j));
return data;
});
},
/** /**
* Send invite to job or transfer to other user * Send invite to job or transfer to other user
* @param job Job to invite to * @param job Job to invite to

View File

@ -16,9 +16,6 @@ export type EditableJob = Omit<Omit<FG.Job, 'type'>, 'id'> & {
export function emptyJob(startDate = new Date()): EditableJob { export function emptyJob(startDate = new Date()): EditableJob {
const start = date.adjustDate(startDate, { const start = date.adjustDate(startDate, {
hours: new Date().getHours(), hours: new Date().getHours(),
minutes: 0,
seconds: 0,
milliseconds: 0,
}); });
return { return {
start: start, start: start,
@ -29,9 +26,9 @@ export function emptyJob(startDate = new Date()): EditableJob {
}; };
} }
export function emptyEvent(startDate?: Date): EditableEvent { export function emptyEvent(startDate: Date = new Date()): EditableEvent {
return { return {
start: startDate === undefined ? new Date() : new Date(startDate), start: date.adjustDate(startDate, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }),
jobs: [emptyJob(startDate)], jobs: [emptyJob(startDate)],
is_template: false, is_template: false,
}; };