Merge remote-tracking branch 'origin/next' into next

This commit is contained in:
Tim Gröger 2021-03-20 11:34:40 +01:00
commit c3e3a272dc
11 changed files with 128 additions and 173 deletions

View File

@ -33,8 +33,10 @@ interface Backend {
export { Backend }; export { Backend };
function setPermissions(object: FG_Plugin.PluginRouteConfig) { function setPermissions(object: FG_Plugin.PluginRouteConfig) {
if (object.route.meta === undefined) object.route.meta = {}; if (object.permissions !== undefined) {
object.route.meta['permissions'] = object.permissions; if (object.route.meta === undefined) object.route.meta = {};
object.route.meta['permissions'] = object.permissions;
}
} }
function convertRoutes(parent: RouteRecordRaw, children?: FG_Plugin.PluginRouteConfig[]) { function convertRoutes(parent: RouteRecordRaw, children?: FG_Plugin.PluginRouteConfig[]) {

View File

@ -28,11 +28,11 @@ declare namespace FG {
id: number; id: number;
time: Date; time: Date;
amount: number; amount: number;
reversal_id: number; reversal_id?: number;
sender_id?: string;
receiver_id?: string;
author_id?: string; author_id?: string;
sender_id?: string;
original_id?: number; original_id?: number;
receiver_id?: string;
} }
interface Drink { interface Drink {
id: number; id: number;
@ -91,7 +91,7 @@ declare namespace FG {
start: Date; start: Date;
end?: Date; end?: Date;
description?: string; description?: string;
type: EventType; type: EventType | number;
jobs: Array<Job>; jobs: Array<Job>;
} }
interface EventType { interface EventType {
@ -102,8 +102,8 @@ declare namespace FG {
id: number; id: number;
start: Date; start: Date;
end?: Date; end?: Date;
comment: string; type: JobType | number;
type: JobType; comment?: string;
services: Array<Service>; services: Array<Service>;
required_services: number; required_services: number;
} }

View File

@ -43,13 +43,8 @@
<q-card-section v-for="(job, index) in event.jobs" :key="index"> <q-card-section v-for="(job, index) in event.jobs" :key="index">
<q-card class="q-my-auto"> <q-card class="q-my-auto">
<job <job
:job="job" v-model="event.jobs[index]"
:job-can-delete="jobDeleteDisabled" :job-can-delete="jobDeleteDisabled"
@set-start="setStart"
@set-required="setRequired"
@set-end="setEnd"
@set-comment="setComment"
@set-job-type="setJobType"
@remove-job="removeJob(index)" @remove-job="removeJob(index)"
/> />
</q-card> </q-card>
@ -78,59 +73,31 @@ export default defineComponent({
const eventtypes = computed(() => store.eventTypes); const eventtypes = computed(() => store.eventTypes);
const jobDeleteDisabled = computed(() => event.value.jobs.length < 2); const jobDeleteDisabled = computed(() => event.value.jobs.length < 2);
const newJob = ref<FG.Job>(({ const newJob = ref<FG.Job>({
id: NaN, id: NaN,
start: undefined, start: new Date(),
end: undefined, end: date.addToDate(new Date(), { hours: 1 }),
comment: '',
services: [], services: [],
required_services: 2, required_services: 2,
} as unknown) as FG.Job); type: store.jobTypes[0],
});
const event = ref<FG.Event>({ const event = ref<FG.Event>({
id: NaN, id: NaN,
start: new Date(), start: new Date(),
description: '',
jobs: [Object.assign({}, newJob.value)], jobs: [Object.assign({}, newJob.value)],
} as FG.Event); type: store.eventTypes[0],
});
onBeforeMount(() => { onBeforeMount(() => {
void store.getEventTypes(); void store.getEventTypes();
void store.getJobTypes(); void store.getJobTypes();
}); });
function setStart(data: { job: FG.Job; value: Date }) {
data.job.start = data.value;
}
function setEnd(data: { job: FG.Job; value: Date }) {
data.job.end = data.value;
}
function setComment(data: { job: FG.Job; value: string }) {
data.job.comment = data.value;
}
function setJobType(job: FG.Job, value: FG.JobType) {
job.type = value;
}
function setRequired(data: { job: FG.Job; value: number }) {
data.job.required_services = data.value;
}
function addJob() { function addJob() {
const addJob = Object.assign({}, newJob.value); event.value.jobs.push(Object.assign({}, newJob.value));
event.value.jobs.unshift(addJob);
} }
//function removeJob(id: number) {
// let jobtoremove = event.value.jobs.findIndex(job => job.id == id);
// if (jobtoremove != undefined) {
// event.value.jobs.splice(jobtoremove, 1);
// }
//}
function removeJob(index: number) { function removeJob(index: number) {
event.value.jobs.splice(index, 1); event.value.jobs.splice(index, 1);
} }
@ -174,11 +141,6 @@ export default defineComponent({
reset, reset,
event, event,
isAfterDate, isAfterDate,
setStart,
setEnd,
setComment,
setJobType,
setRequired,
}; };
}, },
}); });

View File

@ -2,59 +2,52 @@
<q-card-section class="fit row justify-start content-center items-center"> <q-card-section class="fit row justify-start content-center items-center">
<q-card-section class="fit row justify-start content-center items-center"> <q-card-section class="fit row justify-start content-center items-center">
<IsoDateInput <IsoDateInput
v-model="job.start"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
:value="job.start"
label="Beginn" label="Beginn"
type="datetime" type="datetime"
:rules="[noValidDate, notEmpty]" :rules="[notEmpty, noValidDate]"
@input="setStart"
/> />
<IsoDateInput <IsoDateInput
ref="bla" v-model="job.end"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
:value="job.end"
label="Ende" label="Ende"
type="datetime" type="datetime"
:rules="[noValidDate, isAfterDate, notEmpty]" :rules="[notEmpty, noValidDate, isAfterDate]"
@input="setEnd"
/> />
</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" v-model="job.type"
filled filled
use-input use-input
label="Dienstart" label="Dienstart"
input-debounce="0" input-debounce="0"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
:value="job.type"
:options="jobtypes" :options="jobtypes"
option-label="name" option-label="name"
option-value="name" option-value="id"
map-options map-options
clearable clearable
:rules="[notEmpty]" :rules="[notEmpty]"
@input="setJobType"
/> />
<q-input <q-input
v-model="job.required_services"
filled filled
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Dienstanzahl" label="Dienstanzahl"
type="number" type="number"
:value="job.required_services"
:rules="[notEmpty]" :rules="[notEmpty]"
@input="setRequired"
/> />
</q-card-section> </q-card-section>
<q-card-section class="fit row justify-start content-center items-center"> <q-card-section class="fit row justify-start content-center items-center">
<q-input <q-input
v-model="job.comment"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Beschreibung" label="Beschreibung"
type="textarea" type="textarea"
:value="job.comment"
filled filled
:rules="[notEmpty]" :rules="[notEmpty]"
@input="setComment"
/> />
</q-card-section> </q-card-section>
<q-btn label="Schicht löschen" color="negative" :disabled="jobCanDelete" @click="removeJob" /> <q-btn label="Schicht löschen" color="negative" :disabled="jobCanDelete" @click="removeJob" />
@ -63,7 +56,7 @@
<script lang="ts"> <script lang="ts">
import IsoDateInput from 'src/components/utils/IsoDateInput.vue'; import IsoDateInput from 'src/components/utils/IsoDateInput.vue';
import { defineComponent, computed, ref, onBeforeMount, PropType } from 'vue'; import { defineComponent, computed, onBeforeMount, PropType } from 'vue';
import { useScheduleStore } from '../../store'; import { useScheduleStore } from '../../store';
import { date } from 'quasar'; import { date } from 'quasar';
@ -71,19 +64,15 @@ export default defineComponent({
name: 'Job', name: 'Job',
components: { IsoDateInput }, components: { IsoDateInput },
props: { props: {
job: { modelValue: {
required: true, required: true,
type: Object as PropType<FG.Job>, type: Object as PropType<FG.Job>,
}, },
jobCanDelete: Boolean, jobCanDelete: Boolean,
}, },
emits: { emits: {
'set-start': (d: Date) => !!d,
'remove-job': () => true, 'remove-job': () => true,
'set-end': (d: Date) => !!d, 'update:modelValue': (job: FG.Job) => !!job,
'set-comment': (c: string) => !!c,
'set-job-type': (jt: FG.JobType) => !!jt,
'set-required': (r: number) => isFinite(r),
}, },
setup(props, { emit }) { setup(props, { emit }) {
const store = useScheduleStore(); const store = useScheduleStore();
@ -91,27 +80,22 @@ export default defineComponent({
onBeforeMount(() => store.getJobTypes()); onBeforeMount(() => store.getJobTypes());
const jobtypes = computed(() => store.jobTypes); const jobtypes = computed(() => store.jobTypes);
const refreshKey = ref<number>(0);
function setStart(value: Date) { const job = new Proxy(props.modelValue, {
emit('set-start', value); get(target, prop) {
} if (typeof prop === 'string') {
return ((props.modelValue as unknown) as Record<string, unknown>)[prop];
function setEnd(value: Date) { }
emit('set-end', value); },
} set(obj, prop, value) {
console.log('...', obj, prop, value);
function setComment(value: string) { if (typeof prop === 'string') {
emit('set-comment', value); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
} emit('update:modelValue', Object.assign({}, props.modelValue, { [prop]: value }));
}
function setJobType(value: FG.JobType) { return true;
emit('set-job-type', value); },
} });
function setRequired(value: number) {
emit('set-required', value);
}
function removeJob() { function removeJob() {
emit('remove-job'); emit('remove-job');
@ -121,31 +105,20 @@ export default defineComponent({
return !!val || 'Feld darf nicht leer sein!'; return !!val || 'Feld darf nicht leer sein!';
} }
function noValidDate(val: string) { function noValidDate(val: string) {
console.log(val);
return !!date.isValid(val) || 'Datum/Zeit muss gesetzt sein!'; return !!date.isValid(val) || 'Datum/Zeit muss gesetzt sein!';
} }
function isAfterDate(val: Date) { function isAfterDate(val: string) {
console.log('isAfterDate', props.job.start, val); return props.modelValue.start < new Date(val) || 'Ende muss hinter dem Start liegen';
return props.job.start < new Date(val) || 'Ende muss hinter dem Start liegen';
}
function refresh() {
refreshKey.value += 1;
} }
return { return {
job,
jobtypes, jobtypes,
setStart,
setEnd,
setComment,
setJobType,
setRequired,
removeJob, removeJob,
notEmpty, notEmpty,
noValidDate, noValidDate,
isAfterDate, isAfterDate,
refreshKey,
}; };
}, },
}); });

View File

@ -47,14 +47,15 @@
<q-calendar-agenda <q-calendar-agenda
ref="calendar" ref="calendar"
v-model="selectedDate" v-model="selectedDate"
:view="calendarView" :view="calendarRealView"
:max-days="calendarDays"
:weekdays="[1, 2, 3, 4, 5, 6, 0]" :weekdays="[1, 2, 3, 4, 5, 6, 0]"
locale="de-de" locale="de-de"
style="height: 100%; min-height: 400px" style="height: 100%; min-height: 400px"
> >
<template #day="{ scope: { timestamp } }" style="min-height: 200px"> <template #day="{ scope: { timestamp } }" style="min-height: 200px">
<template v-if="!getAgenda(timestamp)" style="min-height: 200px"> </template> <template v-if="!events[timestamp.weekday]" style="min-height: 200px"> </template>
<template v-for="agenda in getAgenda(timestamp)" :key="agenda.id"> <template v-for="agenda in events[timestamp.weekday]" :key="agenda.id">
<eventslot :event="agenda" /> <eventslot :event="agenda" />
</template> </template>
</template> </template>
@ -65,10 +66,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onBeforeMount, ref } from 'vue'; import { computed, defineComponent, onBeforeMount, ref } from 'vue';
import { useScheduleStore } from '../../store'; import { useScheduleStore } from '../../store';
import Eventslot from './slots/EventSlot.vue'; import Eventslot from './slots/EventSlot.vue';
import { date } from 'quasar'; import { date } from 'quasar';
import { startOfWeek } from 'src/utils/datetime';
export default defineComponent({ export default defineComponent({
name: 'AgendaView', name: 'AgendaView',
@ -76,10 +78,16 @@ export default defineComponent({
setup() { setup() {
const store = useScheduleStore(); const store = useScheduleStore();
const windowWidth = ref(window.innerWidth);
const selectedDate = ref(date.formatDate(new Date(), 'YYYY-MM-DD')); const selectedDate = ref(date.formatDate(new Date(), 'YYYY-MM-DD'));
const proxyDate = ref(''); const proxyDate = ref('');
const calendar = ref<QCalendar.QCalendar>(); const calendar = ref<QCalendar.QCalendar>();
const calendarView = 'week'; const calendarView = ref('week');
const calendarRealView = computed(() => (calendarDays.value != 7 ? 'day' : 'week'));
const calendarDays = computed(() =>
calendarView.value == 'day' ? 1 : windowWidth.value < 1000 ? 3 : 7
);
const events = ref<Agendas>({}); const events = ref<Agendas>({});
interface Agendas { interface Agendas {
@ -87,35 +95,40 @@ export default defineComponent({
} }
onBeforeMount(async () => { onBeforeMount(async () => {
let agenda: Agendas = {}; window.addEventListener('resize', () => {
console.log('Hier Passiert was'); windowWidth.value = window.innerWidth;
const list = await store.getEvents({ from: new Date(selectedDate.value) }); });
if (list)
list.forEach((event) => {
let day = event.start.getDay();
if (!agenda[day]) { await loadAgendas();
agenda[day] = [];
}
agenda[day].push(event);
});
console.log('finish agenda:', agenda);
events.value = agenda;
}); });
function getAgenda(day: { weekday: string }) { async function loadAgendas() {
return events.value[parseInt(day.weekday, 10)]; const selected = new Date(selectedDate.value);
const start = calendarRealView.value === 'day' ? selected : startOfWeek(selected);
const end = date.addToDate(start, { days: calendarDays.value });
events.value = {};
const list = await store.getEvents({ from: start, to: end });
list.forEach((event) => {
const day = event.start.getDay();
if (!events.value[day]) {
events.value[day] = [];
}
events.value[day].push(event);
});
} }
function calendarNext() { function calendarNext() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call calendar.value?.next();
return calendar.value?.next(); void loadAgendas();
} }
function calendarPrev() { function calendarPrev() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call calendar.value?.prev();
return calendar.value?.prev(); void loadAgendas();
} }
function updateProxy() { function updateProxy() {
proxyDate.value = selectedDate.value; proxyDate.value = selectedDate.value;
} }
@ -155,13 +168,14 @@ export default defineComponent({
calendar, calendar,
selectedDate, selectedDate,
events, events,
getAgenda,
calendarNext, calendarNext,
calendarPrev, calendarPrev,
updateProxy, updateProxy,
saveNewSelectedDate, saveNewSelectedDate,
proxyDate, proxyDate,
calendarDays,
calendarView, calendarView,
calendarRealView,
}; };
}, },
}); });

View File

@ -38,7 +38,7 @@
<Eventtypes /> <Eventtypes />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="jobtypes"> <q-tab-panel name="jobtypes">
<JobTypes v-if="canEditRoles" /> <JobTypes v-if="canEditJobTypes" />
</q-tab-panel> </q-tab-panel>
</q-tab-panels> </q-tab-panels>
</q-page> </q-page>
@ -58,7 +58,7 @@ export default defineComponent({
name: 'EventManagement', name: 'EventManagement',
components: { CreateEvent, Eventtypes, JobTypes }, components: { CreateEvent, Eventtypes, JobTypes },
setup() { setup() {
const canEditRoles = computed(() => hasPermission(PERMISSIONS.ROLES_EDIT)); const canEditJobTypes = computed(() => hasPermission(PERMISSIONS.JOB_TYPE));
interface Tab { interface Tab {
name: string; name: string;
@ -85,7 +85,7 @@ export default defineComponent({
const tab = ref<string>('create'); const tab = ref<string>('create');
return { return {
canEditRoles, canEditJobTypes,
showDrawer, showDrawer,
tab, tab,
tabs, tabs,

View File

@ -37,9 +37,6 @@
<q-tab-panel name="eventtypes"> <q-tab-panel name="eventtypes">
<Eventtypes /> <Eventtypes />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="jobtypes">
<JobTypes v-if="canEditRoles" />
</q-tab-panel>
</q-tab-panels> </q-tab-panels>
</q-page> </q-page>
</div> </div>
@ -48,26 +45,21 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, ref } from 'vue'; import { computed, defineComponent, ref } from 'vue';
import Eventtypes from '../components/management/Eventtypes.vue'; import Eventtypes from '../components/management/Eventtypes.vue';
import JobTypes from '../components/management/JobTypes.vue';
//import CreateEvent from '../components/management/CreateEvent.vue'; //import CreateEvent from '../components/management/CreateEvent.vue';
import AgendaView from '../components/overview/AgendaView.vue'; import AgendaView from '../components/overview/AgendaView.vue';
import { hasPermission } from 'src/utils/permission';
import { PERMISSIONS } from '../permissions';
import { Screen } from 'quasar'; import { Screen } from 'quasar';
export default defineComponent({ export default defineComponent({
name: 'EventOverview', name: 'EventOverview',
components: { AgendaView, Eventtypes, JobTypes }, components: { AgendaView, Eventtypes },
setup() { setup() {
const canEditRoles = computed(() => hasPermission(PERMISSIONS.ROLES_EDIT));
interface Tab { interface Tab {
name: string; name: string;
label: string; label: string;
} }
const tabs: Tab[] = [ const tabs: Tab[] = [
{ name: 'agendaView', label: 'Kalendar' } { name: 'agendaView', label: 'Kalendar' },
// { name: 'eventtypes', label: 'Veranstaltungsarten' }, // { name: 'eventtypes', label: 'Veranstaltungsarten' },
// { name: 'jobtypes', label: 'Dienstarten' } // { name: 'jobtypes', label: 'Dienstarten' }
]; ];
@ -80,17 +72,16 @@ export default defineComponent({
}, },
set: (val: boolean) => { set: (val: boolean) => {
drawer.value = val; drawer.value = val;
} },
}); });
const tab = ref<string>('agendaView'); const tab = ref<string>('agendaView');
return { return {
canEditRoles,
showDrawer, showDrawer,
tab, tab,
tabs tabs,
}; };
} },
}); });
</script> </script>

View File

@ -1,12 +1,16 @@
export const PERMISSIONS = { export const PERMISSIONS = {
// Kann andere Nutzer bearbeiten // Can create events
EDIT_OTHER: 'users_edit_other', CREATE: 'schedule_create',
// Kann Rollen von Nutzern setzen // Can edit events
SET_ROLES: 'users_set_roles', EDIT: 'schedule_edit',
// Kann Nutzer löschen // Can delete events
DELETE: 'users_delete_other', DELETE: 'schedule_delete',
// Kann neue Nutzer hinzufügen // Can create and edit EventTypes
REGISTER: 'users_register', EVENT_TYPE: 'schedule_event_type',
// Kann Rollen löschen oder bearbeiten, z.b. Rechte hinzufügen etc // Can create and edit JobTypes
ROLES_EDIT: 'roles_edit', JOB_TYPE: 'schedule_job_type',
// Can self assign to jobs
ASSIGN: 'schedule_assign',
// Can assign other users to jobs
ASSIGN_OTHER: 'schedule_assign_other',
}; };

View File

@ -1,4 +1,5 @@
import { FG_Plugin } from 'src/plugins'; import { FG_Plugin } from 'src/plugins';
import { PERMISSIONS } from '../permissions';
const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
{ {
@ -14,9 +15,7 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
{ {
title: 'Dienstübersicht', title: 'Dienstübersicht',
icon: 'mdi-account-group', icon: 'mdi-account-group',
shortcut: true, shortcut: true,
permissions: [],
route: { route: {
path: 'schedule-overview', path: 'schedule-overview',
name: 'schedule-overview', name: 'schedule-overview',
@ -27,6 +26,7 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
title: 'Dienstverwaltung', title: 'Dienstverwaltung',
icon: 'mdi-account-details', icon: 'mdi-account-details',
shortcut: false, shortcut: false,
permissions: [PERMISSIONS.CREATE],
route: { route: {
path: 'schedule-management', path: 'schedule-management',
name: 'schedule-management', name: 'schedule-management',

View File

@ -27,14 +27,15 @@ export const useScheduleStore = defineStore({
getters: {}, getters: {},
actions: { actions: {
async getJobTypes() { async getJobTypes(force = false) {
try { if (force || this.jobTypes.length == 0)
const { data } = await api.get<FG.JobType[]>('/schedule/job-types'); try {
this.jobTypes = data; const { data } = await api.get<FG.JobType[]>('/schedule/job-types');
return this.jobTypes; this.jobTypes = data;
} catch (error) { } catch (error) {
throw error; throw error;
} }
return this.jobTypes;
}, },
async addJobType(name: string) { async addJobType(name: string) {

View File

@ -20,3 +20,11 @@ export function formatDateTime(
export function asHour(date?: Date) { export function asHour(date?: Date) {
if (date) return formatDateTime(date, false, true); if (date) return formatDateTime(date, false, true);
} }
export function startOfWeek(date: Date, startMonday = true) {
const start = new Date(date);
const day = date.getDay() || 7;
if (startMonday && day !== 1) start.setHours(-24 * (day - 1));
else if (!startMonday && day !== 7) start.setHours(-24 * day);
return start;
}