flaschengeist-schedule/src/components/management/EditEvent.vue

302 lines
9.3 KiB
Vue
Raw Normal View History

<template>
<q-card>
<q-form @submit="save()" @reset="reset">
<q-card-section class="fit row justify-start content-center items-center">
<div class="text-h6 col-xs-12 col-sm-6 q-pa-sm">
Veranstaltung <template v-if="modelValue">bearbeiten</template
><template v-else>erstellen</template>
</div>
<q-select
:model-value="template"
filled
label="Vorlage"
class="col-xs-12 col-sm-6 q-pa-sm"
:options="templates"
option-label="name"
map-options
clearable
:disable="templates.length == 0"
@update:model-value="fromTemplate"
@clear="reset()"
/>
<q-input
v-model="event.name"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Name"
type="text"
filled
/>
<q-select
v-model="event.type"
filled
use-input
label="Veranstaltungstyp"
input-debounce="0"
class="col-xs-12 col-sm-6 q-pa-sm"
:options="eventtypes"
option-label="name"
option-value="id"
emit-value
map-options
clearable
:rules="[notEmpty]"
/>
<IsoDateInput
v-model="event.start"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Veranstaltungsbeginn"
:rules="[notEmpty]"
/>
<IsoDateInput
v-model="event.end"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Veranstaltungsende"
2021-11-17 00:55:55 +00:00
:rules="[afterStart]"
/>
<q-input
v-model="event.description"
class="col-12 q-pa-sm"
label="Beschreibung"
type="textarea"
filled
/>
</q-card-section>
<q-card-section v-if="event.is_template !== true && modelValue === undefined">
<q-btn-toggle
v-model="recurrent"
spread
no-caps
:options="[
{ label: 'Einmalig', value: false },
{ label: 'Wiederkehrend', value: true },
]"
/>
<RecurrenceRule v-if="!!recurrent" v-model="recurrenceRule" />
</q-card-section>
<q-separator />
<q-card-section>
<div class="row justify-around q-mb-sm" align="around">
<div class="text-h6 text-center col-6">Schichten</div>
<div class="col-6 text-center">
<q-btn color="primary" label="Schicht hinzufügen" @click="addJob()" />
</div>
</div>
<template v-for="(job, index) in event.jobs" :key="index">
<edit-job-slot
:ref="active === index ? 'activeJob' : undefined"
v-model="event.jobs[index]"
:active="index === active"
class="q-mb-md"
@activate="activate(index)"
@remove-job="removeJob(index)"
/>
</template>
</q-card-section>
<q-card-actions align="around">
<q-card-actions align="left">
<q-btn v-if="!template" color="secondary" label="Neue Vorlage" @click="save(true)" />
<q-btn v-else color="negative" label="Vorlage löschen" @click="removeTemplate" />
</q-card-actions>
<q-card-actions align="right">
<q-btn label="Zurücksetzen" type="reset" />
<q-btn color="primary" type="submit" label="Speichern" />
</q-card-actions>
</q-card-actions>
</q-form>
</q-card>
</template>
<script lang="ts">
import { notEmpty } from '@flaschengeist/api';
2021-05-25 15:15:10 +00:00
import { IsoDateInput } from '@flaschengeist/api/components';
import { useEventStore } from '../../store';
import { emptyEvent, emptyJob, EditableEvent } from '../../store/models';
import { date, ModifyDateOptions } from 'quasar';
2021-11-24 20:45:00 +00:00
import { computed, defineComponent, PropType, ref, onBeforeMount, watch } from 'vue';
import EditJobSlot from './EditJobSlot.vue';
2021-11-24 20:45:00 +00:00
import RecurrenceRuleVue from './RecurrenceRule.vue';
import { RecurrenceRule } from 'app/events';
export default defineComponent({
name: 'EditEvent',
2021-11-24 20:45:00 +00:00
components: { IsoDateInput, EditJobSlot, RecurrenceRule: RecurrenceRuleVue },
props: {
modelValue: {
required: false,
default: () => undefined,
type: Object as PropType<FG.Event | undefined>,
},
date: {
required: false,
2021-11-17 00:55:55 +00:00
default: undefined,
type: String as PropType<string | undefined>,
},
},
emits: {
done: (val: boolean) => typeof val === 'boolean',
},
setup(props, { emit }) {
2021-11-21 11:39:02 +00:00
const store = useEventStore();
2021-11-17 00:55:55 +00:00
const startDate = computed(() => {
const d = date.buildDate({ milliseconds: 0, seconds: 0, minutes: 0, hours: 0 });
if (!props.date || !date.isValid(props.date)) return d;
2021-11-17 00:55:55 +00:00
const split = props.date.split('-');
return date.adjustDate(d, {
year: parseInt(split[0]),
month: parseInt(split[1]),
date: parseInt(split[2]),
});
2021-11-17 00:55:55 +00:00
});
const active = ref(0);
const activeJob = ref<{ validate: () => Promise<boolean> }>();
const templates = computed(() => store.templates);
const template = ref<FG.Event>();
2021-11-24 20:45:00 +00:00
const event = ref<EditableEvent>(props.modelValue || emptyEvent());
const eventtypes = computed(() => store.eventTypes);
const recurrent = ref(false);
2021-11-24 20:45:00 +00:00
const recurrenceRule = ref<RecurrenceRule>({ frequency: 'daily', interval: 1 });
onBeforeMount(() => {
void store.getEventTypes();
void store.getJobTypes();
void store.getTemplates();
});
2021-11-24 20:45:00 +00:00
watch(props, (n, o) => {
if (event.value?.id !== n.modelValue?.id) reset();
});
function addJob() {
if (!activeJob.value) event.value.jobs.push(emptyJob());
else
void activeJob.value.validate().then((success) => {
if (success) {
event.value.jobs.push(emptyJob());
active.value = event.value.jobs.length - 1;
}
});
}
function removeJob(index: number) {
event.value.jobs.splice(index, 1);
if (active.value >= index) active.value--;
}
function fromTemplate(tpl: FG.Event) {
const today = new Date();
template.value = tpl;
event.value = Object.assign({}, tpl, { id: undefined });
// Adjust the start to match today
event.value.start = date.adjustDate(event.value.start, {
date: today.getDate(),
month: today.getMonth() + 1, // js inconsitency between getDate (1-31) and getMonth (0-11)
year: today.getFullYear(),
});
// Use timestamp difference for faster adjustment
const diff = event.value.start.getTime() - tpl.start.getTime();
// Adjust end of event and all jobs
if (event.value.end) event.value.end.setTime(event.value.end.getTime() + diff);
event.value.jobs.forEach((job) => {
job.start.setTime(job.start.getTime() + diff);
if (job.end) job.end.setTime(job.end.getTime() + diff);
});
}
async function save(template = false) {
event.value.is_template = template;
try {
if (event.value?.id !== undefined) {
2021-11-21 11:36:17 +00:00
//fix
}
await store.addEvent(event.value);
2021-11-21 11:36:17 +00:00
if (props.modelValue === undefined && recurrent.value && !event.value.is_template) {
let count = 0;
const options: ModifyDateOptions = {};
switch (recurrenceRule.value.frequency) {
case 'daily':
options['days'] = 1 * recurrenceRule.value.interval;
break;
case 'weekly':
options['days'] = 7 * recurrenceRule.value.interval;
break;
case 'monthly':
options['months'] = 1 * recurrenceRule.value.interval;
break;
}
while (true) {
event.value.start = date.addToDate(event.value.start, options);
if (event.value.end) event.value.end = date.addToDate(event.value.end, options);
event.value.jobs.forEach((job) => {
job.start = date.addToDate(job.start, options);
if (job.end) job.end = date.addToDate(job.end, options);
});
count++;
if (
count <= 120 &&
(!recurrenceRule.value.count || count <= recurrenceRule.value.count) &&
(!recurrenceRule.value.until || event.value.start < recurrenceRule.value.until)
)
await store.addEvent(event.value);
else break;
}
}
reset();
emit('done', true);
} catch (error) {
console.error(error);
}
}
async function removeTemplate() {
if (template.value !== undefined) {
await store.removeEvent(template.value.id);
template.value = undefined;
}
}
function reset() {
2021-11-17 00:55:55 +00:00
event.value = Object.assign({}, props.modelValue || emptyEvent());
active.value = 0;
template.value = undefined;
}
const afterStart = (d: Date) =>
!d || event.value.start <= d || 'Das Veranstaltungsende muss vor dem Beginn liegen';
2021-11-17 00:55:55 +00:00
function activate(idx: number) {
void activeJob.value?.validate().then((s) => {
if (s) active.value = idx;
});
}
return {
activate,
active,
addJob,
activeJob,
2021-11-17 00:55:55 +00:00
afterStart,
event,
eventtypes,
2021-11-17 00:55:55 +00:00
fromTemplate,
notEmpty,
2021-11-17 00:55:55 +00:00
recurrenceRule,
recurrent,
2021-11-17 00:55:55 +00:00
removeJob,
removeTemplate,
2021-11-17 00:55:55 +00:00
reset,
save,
template,
2021-11-17 00:55:55 +00:00
templates,
};
},
});
</script>
<style></style>