[ported] Validate jobs before adding new and minimize inactive jobs
Ported from flaschengeist-frontend @7d1993e3faecdca3af47bc19f444857c49c3f3c4
This commit is contained in:
parent
c31b804102
commit
ff15ceb7d0
|
@ -27,7 +27,7 @@
|
||||||
"quasar": "^2.3.3",
|
"quasar": "^2.3.3",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"prettier": "^2.4.1",
|
"prettier": "^2.4.1",
|
||||||
"typescript": "^4.4.4",
|
"typescript": "^4.5.2",
|
||||||
"pinia": "^2.0.4",
|
"pinia": "^2.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||||
"@typescript-eslint/parser": "^5.4.0",
|
"@typescript-eslint/parser": "^5.4.0",
|
||||||
|
|
|
@ -82,7 +82,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="(job, index) in event.jobs" :key="index">
|
<template v-for="(job, index) in event.jobs" :key="index">
|
||||||
<edit-job-slot v-model="event.jobs[index]" @remove-job="removeJob(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>
|
</template>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="around">
|
<q-card-actions align="around">
|
||||||
|
@ -143,6 +150,8 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const active = ref(0);
|
||||||
|
const activeJob = ref<{ validate: () => Promise<boolean> }>();
|
||||||
const templates = computed(() => store.templates);
|
const templates = computed(() => store.templates);
|
||||||
const template = ref<FG.Event>();
|
const template = ref<FG.Event>();
|
||||||
const event = ref<EditableEvent>(props.modelValue || emptyEvent(startDate.value));
|
const event = ref<EditableEvent>(props.modelValue || emptyEvent(startDate.value));
|
||||||
|
@ -157,34 +166,40 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
function addJob() {
|
function addJob() {
|
||||||
|
if (!activeJob.value) event.value.jobs.push(emptyJob());
|
||||||
|
else
|
||||||
|
void activeJob.value.validate().then((success) => {
|
||||||
|
if (success) {
|
||||||
event.value.jobs.push(emptyJob());
|
event.value.jobs.push(emptyJob());
|
||||||
|
active.value = event.value.jobs.length - 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeJob(index: number) {
|
function removeJob(index: number) {
|
||||||
event.value.jobs.splice(index, 1);
|
event.value.jobs.splice(index, 1);
|
||||||
|
if (active.value >= index) active.value--;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromTemplate(tpl: FG.Event) {
|
function fromTemplate(tpl: FG.Event) {
|
||||||
const today = new Date()
|
const today = new Date();
|
||||||
template.value = tpl;
|
template.value = tpl;
|
||||||
|
|
||||||
event.value = Object.assign({}, tpl, {id: undefined});
|
event.value = Object.assign({}, tpl, { id: undefined });
|
||||||
// Adjust the start to match today
|
// Adjust the start to match today
|
||||||
event.value.start = date.adjustDate(event.value.start,{
|
event.value.start = date.adjustDate(event.value.start, {
|
||||||
date: today.getDate(),
|
date: today.getDate(),
|
||||||
month: today.getMonth() + 1, // js inconsitency between getDate (1-31) and getMonth (0-11)
|
month: today.getMonth() + 1, // js inconsitency between getDate (1-31) and getMonth (0-11)
|
||||||
year: today.getFullYear()
|
year: today.getFullYear(),
|
||||||
})
|
});
|
||||||
// Use timestamp difference for faster adjustment
|
// Use timestamp difference for faster adjustment
|
||||||
const diff = event.value.start.getTime() - tpl.start.getTime()
|
const diff = event.value.start.getTime() - tpl.start.getTime();
|
||||||
// Adjust end of event and all jobs
|
// Adjust end of event and all jobs
|
||||||
if (event.value.end)
|
if (event.value.end) event.value.end.setTime(event.value.end.getTime() + diff);
|
||||||
event.value.end.setTime(event.value.end.getTime() + diff)
|
event.value.jobs.forEach((job) => {
|
||||||
event.value.jobs.forEach(job => {
|
job.start.setTime(job.start.getTime() + diff);
|
||||||
job.start.setTime(job.start.getTime() + diff)
|
if (job.end) job.end.setTime(job.end.getTime() + diff);
|
||||||
if (job.end)
|
});
|
||||||
job.end.setTime(job.end.getTime() + diff)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(template = false) {
|
async function save(template = false) {
|
||||||
|
@ -242,14 +257,24 @@ export default defineComponent({
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
event.value = Object.assign({}, props.modelValue || emptyEvent());
|
event.value = Object.assign({}, props.modelValue || emptyEvent());
|
||||||
|
active.value = 0;
|
||||||
template.value = undefined;
|
template.value = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const afterStart = (d: Date) =>
|
const afterStart = (d: Date) =>
|
||||||
!d || event.value.start <= d || 'Das Veranstaltungsende muss vor dem Beginn liegen';
|
!d || event.value.start <= d || 'Das Veranstaltungsende muss vor dem Beginn liegen';
|
||||||
|
|
||||||
|
function activate(idx: number) {
|
||||||
|
void activeJob.value?.validate().then((s) => {
|
||||||
|
if (s) active.value = idx;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
activate,
|
||||||
|
active,
|
||||||
addJob,
|
addJob,
|
||||||
|
activeJob,
|
||||||
afterStart,
|
afterStart,
|
||||||
event,
|
event,
|
||||||
eventtypes,
|
eventtypes,
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<q-card class="fit row justify-start content-center items-center">
|
<q-card class="fit">
|
||||||
<q-card-section class="fit row justify-start content-center items-center">
|
<q-card-section
|
||||||
|
v-if="!active"
|
||||||
|
class="fit row justify-start content-center items-center text-center"
|
||||||
|
@click="$emit('activate')"
|
||||||
|
>
|
||||||
|
<div class="text-h6 col-12">{{ formatStartEnd(modelValue.start, modelValue.end) }}</div>
|
||||||
|
<div class="text-subtitle1 col-12">{{ typeName }} ({{ modelValue.required_services }})</div>
|
||||||
|
<div class="text-body2 text-italic text-left col-12">{{ modelValue.comment }}</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-else>
|
||||||
|
<q-form ref="form" class="fit row justify-start content-center items-center">
|
||||||
<IsoDateInput
|
<IsoDateInput
|
||||||
v-model="job.start"
|
v-model="job.start"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
@ -40,27 +50,33 @@
|
||||||
<q-input
|
<q-input
|
||||||
v-model="job.comment"
|
v-model="job.comment"
|
||||||
class="col-12 q-pa-sm"
|
class="col-12 q-pa-sm"
|
||||||
label="Beschreibung"
|
label="Kommentar"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
|
</q-form>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions>
|
<q-card-actions>
|
||||||
<q-btn label="Schicht löschen" color="negative" :disabled="canDelete" @click="removeJob" />
|
<q-btn label="Schicht löschen" color="negative" :disabled="canDelete" @click="$emit('remove-job')" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, onBeforeMount, PropType } from 'vue';
|
import { defineComponent, computed, onBeforeMount, ref, PropType } from 'vue';
|
||||||
import { IsoDateInput } from '@flaschengeist/api/components';
|
import { IsoDateInput } from '@flaschengeist/api/components';
|
||||||
import { notEmpty } from '@flaschengeist/api';
|
import { formatStartEnd, notEmpty } from '@flaschengeist/api';
|
||||||
import { useEventStore } from '../../store';
|
import { useEventStore } from '../../store';
|
||||||
|
import { QForm } from 'quasar';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'JobSlot',
|
name: 'JobSlot',
|
||||||
components: { IsoDateInput },
|
components: { IsoDateInput },
|
||||||
props: {
|
props: {
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
required: true,
|
required: true,
|
||||||
type: Object as PropType<FG.Job>,
|
type: Object as PropType<FG.Job>,
|
||||||
|
@ -71,16 +87,25 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: {
|
emits: {
|
||||||
|
activate: () => true,
|
||||||
'remove-job': () => true,
|
'remove-job': () => true,
|
||||||
'update:modelValue': (job: FG.Job) => !!job,
|
'update:modelValue': (job: FG.Job) => !!job,
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit, expose }) {
|
||||||
const store = useEventStore();
|
const store = useEventStore();
|
||||||
|
|
||||||
onBeforeMount(() => store.getJobTypes());
|
onBeforeMount(() => store.getJobTypes());
|
||||||
|
|
||||||
|
const form = ref<QForm>();
|
||||||
|
|
||||||
const jobtypes = computed(() => store.jobTypes);
|
const jobtypes = computed(() => store.jobTypes);
|
||||||
|
|
||||||
|
const typeName = computed(() =>
|
||||||
|
typeof props.modelValue.type === 'object'
|
||||||
|
? props.modelValue.type.name
|
||||||
|
: jobtypes.value.find((j) => j.id === props.modelValue.type)?.name || 'Kein Typ gesetzt!'
|
||||||
|
);
|
||||||
|
|
||||||
const job = new Proxy(props.modelValue, {
|
const job = new Proxy(props.modelValue, {
|
||||||
get(target, prop) {
|
get(target, prop) {
|
||||||
if (typeof prop === 'string') {
|
if (typeof prop === 'string') {
|
||||||
|
@ -96,23 +121,26 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function removeJob() {
|
|
||||||
emit('remove-job');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAfterDate(val: Date) {
|
function isAfterDate(val: Date) {
|
||||||
return props.modelValue.start < val || 'Ende muss hinter dem Start liegen';
|
return props.modelValue.start < val || 'Ende muss hinter dem Start liegen';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expose({
|
||||||
|
validate: () => form.value?.validate() || Promise.resolve(true)
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
form,
|
||||||
|
formatStartEnd,
|
||||||
|
isAfterDate,
|
||||||
job,
|
job,
|
||||||
jobtypes,
|
jobtypes,
|
||||||
removeJob,
|
|
||||||
notEmpty,
|
notEmpty,
|
||||||
isAfterDate,
|
typeName,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const innerRoutes: FG_Plugin.MenuRoute[] = [
|
||||||
path: 'schedule-management',
|
path: 'schedule-management',
|
||||||
name: 'schedule-management',
|
name: 'schedule-management',
|
||||||
component: () => import('../pages/EventManagement.vue'),
|
component: () => import('../pages/EventManagement.vue'),
|
||||||
props: (route) => ({date: route.query.date}),
|
props: (route) => ({ date: route.query.date }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -52,7 +52,7 @@ export const privateRoutes: FG_Plugin.NamedRouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
name: 'new-event',
|
name: 'new-event',
|
||||||
path: 'new-event',
|
path: 'new-event',
|
||||||
redirect: {name: 'schedule-management'}
|
redirect: { name: 'schedule-management' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'events-edit',
|
name: 'events-edit',
|
||||||
|
|
|
@ -46,16 +46,14 @@ export const useEventStore = defineStore({
|
||||||
removeJobType(id: number) {
|
removeJobType(id: number) {
|
||||||
return api
|
return api
|
||||||
.delete(`/events/job-types/${id}`)
|
.delete(`/events/job-types/${id}`)
|
||||||
.then(() => this.jobTypes = this.jobTypes.filter(v => v.id !== id));
|
.then(() => (this.jobTypes = this.jobTypes.filter((v) => v.id !== id)));
|
||||||
},
|
},
|
||||||
|
|
||||||
renameJobType(id: number, newName: string) {
|
renameJobType(id: number, newName: string) {
|
||||||
return api
|
return api.put(`/events/job-types/${id}`, { name: newName }).then(() => {
|
||||||
.put(`/events/job-types/${id}`, { name: newName })
|
const idx = this.jobTypes.findIndex((v) => v.id === id);
|
||||||
.then(() => {
|
|
||||||
const idx = this.jobTypes.findIndex(v=>v.id===id);
|
|
||||||
if (idx >= 0) this.jobTypes[idx].name = newName;
|
if (idx >= 0) this.jobTypes[idx].name = newName;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async getEventTypes(force = false) {
|
async getEventTypes(force = false) {
|
||||||
|
@ -76,7 +74,7 @@ export const useEventStore = defineStore({
|
||||||
addEventType(name: string) {
|
addEventType(name: string) {
|
||||||
return api
|
return api
|
||||||
.post<FG.EventType>('/events/event-types', { name: name })
|
.post<FG.EventType>('/events/event-types', { name: name })
|
||||||
.then(({data}) => this.eventTypes.push(data))
|
.then(({ data }) => this.eventTypes.push(data));
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeEvent(id: number) {
|
async removeEvent(id: number) {
|
||||||
|
@ -85,7 +83,7 @@ export const useEventStore = defineStore({
|
||||||
const idx = this.templates.findIndex((v) => v.id === id);
|
const idx = this.templates.findIndex((v) => v.id === id);
|
||||||
if (idx !== -1) this.templates.splice(idx, 1);
|
if (idx !== -1) this.templates.splice(idx, 1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isAxiosError(e, 404)) return false
|
if (isAxiosError(e, 404)) return false;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -94,16 +92,14 @@ export const useEventStore = defineStore({
|
||||||
removeEventType(id: number) {
|
removeEventType(id: number) {
|
||||||
return api
|
return api
|
||||||
.delete(`/events/event-types/${id}`)
|
.delete(`/events/event-types/${id}`)
|
||||||
.then(() => this.eventTypes = this.eventTypes.filter(v => v.id !== id));
|
.then(() => (this.eventTypes = this.eventTypes.filter((v) => v.id !== id)));
|
||||||
},
|
},
|
||||||
|
|
||||||
renameEventType(id: number, newName: string) {
|
renameEventType(id: number, newName: string) {
|
||||||
return api
|
return api.put(`/events/event-types/${id}`, { name: newName }).then(() => {
|
||||||
.put(`/events/event-types/${id}`, { name: newName })
|
const idx = this.eventTypes.findIndex((v) => v.id === id);
|
||||||
.then(() => {
|
|
||||||
const idx = this.eventTypes.findIndex(v=>v.id===id);
|
|
||||||
if (idx >= 0) this.eventTypes[idx].name = newName;
|
if (idx >= 0) this.eventTypes[idx].name = newName;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async getTemplates(force = false) {
|
async getTemplates(force = false) {
|
||||||
|
@ -115,7 +111,11 @@ export const useEventStore = defineStore({
|
||||||
return this.templates;
|
return this.templates;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getEvents(filter: { from?: Date; to?: Date, limit?: number, offset?: number, descending?: boolean } | undefined = undefined) {
|
async getEvents(
|
||||||
|
filter:
|
||||||
|
| { from?: Date; to?: Date; limit?: number; offset?: number; descending?: boolean }
|
||||||
|
| undefined = undefined
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get<FG.Event[]>('/events', { params: filter });
|
const { data } = await api.get<FG.Event[]>('/events', { params: filter });
|
||||||
data.forEach((element) => fixEvent(element));
|
data.forEach((element) => fixEvent(element));
|
||||||
|
@ -140,6 +140,7 @@ export const useEventStore = defineStore({
|
||||||
},
|
},
|
||||||
|
|
||||||
async addEvent(event: EditableEvent) {
|
async addEvent(event: EditableEvent) {
|
||||||
|
console.log('addEvent', event);
|
||||||
if (event?.id === undefined) {
|
if (event?.id === undefined) {
|
||||||
const { data } = await api.post<FG.Event>('/events', event);
|
const { data } = await api.post<FG.Event>('/events', event);
|
||||||
if (data.is_template) this.templates.push(data);
|
if (data.is_template) this.templates.push(data);
|
||||||
|
@ -147,7 +148,10 @@ export const useEventStore = defineStore({
|
||||||
} else {
|
} else {
|
||||||
if (typeof event.type === 'object') event.type = event.type.id;
|
if (typeof event.type === 'object') event.type = event.type.id;
|
||||||
|
|
||||||
const { data } = await api.put<FG.Event>(`/events/${event.id}`, Object.assign(event, {jobs: undefined}));
|
const { data } = await api.put<FG.Event>(
|
||||||
|
`/events/${event.id}`,
|
||||||
|
Object.assign(event, { jobs: undefined })
|
||||||
|
);
|
||||||
if (data.is_template) this.templates.push(data);
|
if (data.is_template) this.templates.push(data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@ -157,8 +161,8 @@ export const useEventStore = defineStore({
|
||||||
return api.post<FG.Event>('/events/transfer', {
|
return api.post<FG.Event>('/events/transfer', {
|
||||||
job: job.id,
|
job: job.id,
|
||||||
receiver: invitees,
|
receiver: invitees,
|
||||||
is_invite: isInvite
|
is_invite: isInvite,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue