Files
raven-client/ayanova/src/views/home-schedule.vue
2021-09-22 18:02:44 +00:00

1187 lines
38 KiB
Vue

<template>
<div v-if="formState.ready" v-resize="onResize" class="my-n8">
<!-- `{{ "focus:" + focus }}` {{ diagInfo() }}
{{ evInfo }}
{{ events }}-->
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
<v-sheet height="64">
<v-toolbar flat class="ml-n3">
<v-btn outlined class="xxmr-4" color="grey darken-2" @click="setToday">
{{ $ay.t("DateRangeToday") }}
</v-btn>
<v-btn fab text small color="grey darken-2" @click="prev">
<v-icon small>$prev</v-icon>
</v-btn>
<v-btn fab text small color="grey darken-2" @click="next">
<v-icon small>$next</v-icon>
</v-btn>
<v-toolbar-title v-if="$refs.calendar">
<v-btn text @click="viewType = 'month'">
{{ $refs.calendar.title }}</v-btn
>
</v-toolbar-title>
<v-spacer v-if="!$vuetify.breakpoint.xs"></v-spacer>
<v-btn
class="mr-3"
fab
text
small
color="grey darken-2"
@click="settingsDialog = true"
>
<v-icon small>$ayiCog</v-icon>
</v-btn>
<v-menu bottom right>
<template v-slot:activator="{ on, attrs }">
<v-btn outlined color="grey darken-2" v-bind="attrs" v-on="on">
<span>{{ typeToLabel() }}</span>
<v-icon right>$sort</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item @click="viewType = 'day'">
<v-list-item-title>{{ $ay.t("ScheduleDay") }}</v-list-item-title>
</v-list-item>
<v-list-item @click="viewType = 'week'">
<v-list-item-title>{{ $ay.t("ScheduleWeek") }}</v-list-item-title>
</v-list-item>
<v-list-item @click="viewType = 'month'">
<v-list-item-title>{{
$ay.t("ScheduleMonth")
}}</v-list-item-title>
</v-list-item>
<v-list-item @click="viewType = '4day'">
<v-list-item-title>{{ $ay.t("Schedule4Day") }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar>
</v-sheet>
<v-sheet :height="calendarHeight">
<v-calendar
ref="calendar"
v-model="focus"
color="primary"
:events="events"
:event-color="getEventColor"
:type="viewType"
:locale="languageName"
:event-more-text="$ay.t('More')"
:first-time="formUserOptions.firstTime"
@click:event="showMoreInfo"
@click:more="viewDay"
@click:date="viewDay"
@change="fetchEvents"
@mousedown:event="startDrag"
@mousedown:time="startTime"
@mousemove:time="mouseMoveDayView"
@mousemove:day="mouseMoveMonthView"
@mouseup:day="endDragExtend"
@mouseup:time="endDragExtend"
@mouseleave.native="cancelDrag"
>
<!--
@touchstart:time="startTime"
@touchmove:time="mouseMoveDa
@touchend:event="touchEndEvent"
@touchstart:event="touchStartEvent"yView"
@touchmove:day="mouseMoveMonthView"
@touchend:day="endDragExtend"
@touchend:time="endDragExtend"
-->
<template v-slot:event="{ event, timed, eventSummary }">
<div class="v-event-draggable">
<v-icon small :color="event.textColor" class="mr-1">{{
iconForEvent(event.type)
}}</v-icon>
<span
:class="event.textColor + '--text'"
v-html="eventSummary()"
/><v-icon
v-if="!event.editable"
x-small
:color="event.textColor"
class="ml-1"
>
$ayiLock</v-icon
>
</div>
<div
v-if="timed && event.editable"
class="v-event-drag-bottom"
@mousedown.stop="extendBottom(event)"
></div>
</template>
</v-calendar>
<!-- NEW ITEM DIALOG -->
<template>
<v-row justify="center">
<v-dialog max-width="360px" persistent v-model="newItemDialog">
<v-card>
<v-card-title>{{ $ay.t("New") }}</v-card-title>
<v-card-text>
<v-col cols="12">
<v-btn
x-large
block
@click="newItem($ay.ayt().WorkOrderItemScheduledUser)"
><v-icon large left>$ayiTools</v-icon
>{{ $ay.t("WorkOrder") }}</v-btn
> </v-col
><v-col cols="12">
<v-btn x-large block @click="newItem($ay.ayt().Reminder)"
><v-icon large left>$ayiStickyNote</v-icon
>{{ $ay.t("Reminder") }}</v-btn
>
</v-col>
</v-card-text>
<v-card-actions>
<v-btn text @click="cancelAddNew" color="primary">{{
$ay.t("Cancel")
}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
<!-- MORE INFO DIALOG -->
<v-menu
v-model="moreInfoDialog"
:close-on-content-click="false"
:activator="selectedElement"
offset-x
>
<v-card color="grey lighten-4" min-width="350px" flat>
<v-toolbar>
<v-btn icon @click="openScheduledItem()">
<v-icon color="primary">{{ iconForSelectedEvent() }}</v-icon>
</v-btn>
<v-toolbar-title>{{ selectedEvent.name }}</v-toolbar-title>
</v-toolbar>
<v-card-text>
<!--reminder -->
<template v-if="selectedEvent.type == $ay.ayt().Reminder">
<div>
<span class="text-h6">{{ $ay.t("ReminderName") }}:</span>
<span class="text-body-1 ml-2">{{ evInfo.name }}</span>
</div>
<div>
<span class="text-h6">{{ $ay.t("ReviewDate") }}:</span>
<span class="text-body-1 ml-2"
>{{ $ay.dt(evInfo.startDate) }}&nbsp;&mdash;&nbsp;{{
$ay.dt(evInfo.stopDate)
}}</span
>
</div>
<div>
<span class="text-h6">{{ $ay.t("ReminderNotes") }}:</span>
<span class="text-body-1 ml-2">
<v-icon class="mr-3" :color="evInfo.color"
>$ayiSquareFull</v-icon
>{{ evInfo.notes }}</span
>
</div>
</template>
<!--review -->
<template v-if="selectedEvent.type == $ay.ayt().Review">
<div class="mb-1" v-if="evInfo.aType">
<v-icon
large
color="primary"
@click="openObject(evInfo.aType, evInfo.objectId)"
>{{ $ay.util().iconForType(evInfo.aType) }}</v-icon
><span
class="text-h6"
@click="openObject(evInfo.aType, evInfo.objectId)"
>
{{ evInfo.reviewObjectViz }}</span
>
</div>
<div>
<span class="text-h6">{{ $ay.t("ReviewName") }}:</span>
<span class="text-body-1 ml-2">{{ evInfo.name }}</span>
</div>
<div>
<span class="text-h6">{{ $ay.t("ReviewDate") }}:</span>
<span class="text-body-1 ml-2">{{
$ay.dt(evInfo.reviewDate)
}}</span>
</div>
<div>
<span class="text-h6">{{ $ay.t("ReviewNotes") }}:</span>
<span class="text-body-1 ml-2"> {{ evInfo.notes }}</span>
</div>
<template v-if="evInfo.completedDate">
<div>
<span class="text-h6"
>{{ $ay.t("ReviewCompletedDate") }}:</span
>
<span class="text-body-1 ml-2">{{
$ay.dt(evInfo.completedDate)
}}</span>
</div>
<div>
<span class="text-h6"
>{{ $ay.t("ReviewCompletionNotes") }}:</span
>
<span class="text-body-1 ml-2">
{{ evInfo.completionNotes }}</span
>
</div>
</template>
</template>
<!--woitemscheduleduser -->
<template
v-if="selectedEvent.type == $ay.ayt().WorkOrderItemScheduledUser"
>
<div>
<span class="text-h6">{{ $ay.t("WorkOrder") }}:</span>
<span class="text-body-1 ml-2"
>{{ evInfo.serial }}&nbsp; {{ evInfo.customerViz }}</span
>
</div>
<div>
<span class="text-h6">{{ $ay.t("Tags") }}:</span>
<span class="text-body-1 ml-2">{{
$ay.util().formatTags(evInfo.wotags)
}}</span>
</div>
<div>
<span class="text-h6">{{ $ay.t("DashboardScheduled") }}:</span>
<span class="text-body-1 ml-2"
>{{ $ay.dt(evInfo.startDate) }}&nbsp;&mdash;&nbsp;{{
$ay.dt(evInfo.stopDate)
}}</span
>
</div>
<div>
<span class="text-h6"
>{{
$ay.t("WorkOrderItemScheduledUserEstimatedQuantity")
}}:</span
>
<span class="text-body-1 ml-2">{{ evInfo.qty }}</span>
</div>
<div>
<span class="text-h6"
>{{ $ay.t("WorkOrderItemScheduledUserServiceRateID") }}:</span
>
<span class="text-body-1 ml-2">{{ evInfo.rate }}</span>
</div>
<div v-if="evInfo.haswostatus">
<span class="text-h6">{{ $ay.t("WorkOrderStatus") }}:</span>
<span class="text-body-1 ml-2">{{ evInfo.wostatus }}</span>
<v-icon :color="evInfo.wostatuscolor" class="ml-4"
>$ayiFlag</v-icon
>
<v-icon
color="primary"
v-if="evInfo.wostatuslocked"
class="ml-4"
>$ayiLock</v-icon
>
<v-icon
color="primary"
v-if="evInfo.wostatuscompleted"
class="ml-4"
>$ayiCheckCircle</v-icon
>
</div>
<div>
<span class="text-h6"
>{{ $ay.t("WorkOrderItemSummary") }}:</span
>
<span class="text-body-1 ml-2">
<v-icon class="mr-3" :color="evInfo.woitemstatuscolor"
>$ayiCircle</v-icon
>{{ evInfo.woitemstatus }}</span
>
</div>
<div>
<span class="text-h6"
>{{ $ay.t("WorkOrderItemPriorityID") }}:</span
>
<span class="text-body-1 ml-2">
<v-icon class="mr-3" :color="evInfo.woitemprioritycolor"
>$ayiFireAlt</v-icon
>{{ evInfo.woitempriority }}</span
>
</div>
<div>
<span class="text-h6">{{ $ay.t("WorkOrderItemTags") }}:</span>
<span class="text-body-1 ml-2">{{
$ay.util().formatTags(evInfo.woitemtags)
}}</span>
</div>
</template>
</v-card-text>
<v-card-actions>
<v-btn color="primary" text @click="openScheduledItem()">{{
$ay.t("Open")
}}</v-btn>
<v-spacer v-if="!$vuetify.breakpoint.xs"></v-spacer>
<v-btn color="primary" text @click="moreInfoDialog = false">
{{ $ay.t("Close") }}
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
</v-sheet>
<template>
<v-row justify="center">
<v-dialog max-width="600px" v-model="settingsDialog">
<v-card>
<v-card-title> </v-card-title>
<v-card-text>
settings here
</v-card-text>
<v-card-actions>
<v-btn text @click="settingsDialog = false" color="primary">{{
$ay.t("Cancel")
}}</v-btn>
<v-spacer v-if="!$vuetify.breakpoint.xs"></v-spacer>
<v-btn
color="primary"
text
@click="acceptSettings()"
class="ml-4"
>{{ $ay.t("OK") }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
</div>
</template>
<script>
/*
TODO NEXT:
Determine all settings required to persist (try to think of shared schedule too becuase want to use the same basic structure so one path to api)
Store and retrieve as a single json object?
then could re-use for potentially other things
Like a route that stores per userid and formkey a single json object, null if none
on change of settings persists when settings form closes
does this supersede some user options?
Settings form
Settings should be for the FORM, not the user so that a user can have different settings in personal schedule vs svc-schedule etc
persist, imminentize
Save / load from server (or locally only? as formSettings.saved)??
saved with device or saved with account is the heart of it
wo display section:
DISPLAY
Color: wostatus/woitemstatus/woitempriority/none
Weekdays: weekdays to display, copy from pm exclude days thingy
More info: checkbox beside each item below that can be shown in wo
todo: Needs a refresh button somewhere to refresh for new appts.
todo: ### find all trans keys and make sure they are fetched in initform ###
TESTING:
Test here locally and check for layout issues while doing it on all devices
drag/extend in all views working?
mobile device testing
phones
ipad
shitty tablet
NOTE: no need for drag drop extend on non mouse device, but do want it to be able to create at least near the touch point
Move common functionality out of home-schedule to be re-used in svc-schedule and also potentially other scheds like customer view of workorders or visual calendar view of work for a single customer for techs etc
the more I can move out of it the easier to do more scheds in other places
DOCS:
how to use the schedule
Investigate: can AyaNova be a calendar source for Google calendar and others (like provide ical)
so they can view, move around but create would be outside that but provided for when refresh.
This way a user could just use their built in calendar app rather than fighting with our implementation
Make a case with whatever is found if it looks like a v.next thing
keywords google calendar feed url
https://stayrony.github.io/Create-iCal-ics-Files-in-net-Core/
http://blog.bennymichielsen.be/2013/11/11/exposing-ical-data-in-webapi/
https://blog.elmah.io/generate-calendar-in-ical-format-with-net-using-ical-net/
https://stackoverflow.com/questions/49032408/i-am-trying-to-provide-google-calendar-with-url-of-calendar-served-by-iis-asp-n
Looks like you just provide text back in ical format so would need a token system of some kind I guess since google calendar is not going to login
Maybe a sync option via the ayanova app is what is needed, i.e. they have a menu option to download as ical or sync as ical in ayanova and the device recognizes the url protocol or other type and syncs
this might be a workaround to security issues and users can just update as needed for offline working
ical maybe has urls to open objects in it somehow as well so they can clickthrough google calendar to the source object??
reporting - make it happen
could just default to regular reporting list, doesn't absolutely need to be a calendar at least at first
as long as it has all appointments consolidated in single list
Move on to the shared service schedule with categories being people and all that entails
*/
// const FORM_CUSTOM_TEMPLATE_KEY = "home-schedule";
const FORM_KEY = "home-schedule";
const CLICK_DETECT_TIMEOUT = 100;
export default {
async created() {
let vm = this;
try {
await initForm(vm);
window.$gz.eventBus.$on("menu-click", clickHandler);
//----------------------------
generateMenu(vm);
} catch (error) {
window.$gz.errorHandler.handleFormError(error, vm);
} finally {
vm.formState.ready = true;
}
},
beforeDestroy() {
saveFormSettings(this);
window.$gz.eventBus.$off("menu-click", clickHandler);
},
data() {
return {
//formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
focus: "",
viewType: "month",
selectedEvent: {},
selectedElement: null,
moreInfoDialog: false,
events: [],
evInfo: {},
dragEvent: null,
dragStart: null,
extendEvent: null,
createStart: null,
extendOriginal: null,
dragged: false,
dragTimeout: null,
formState: {
ready: false,
dirty: false,
valid: true,
readOnly: false,
loading: true,
errorBoxMessage: null,
appError: null,
serverError: {}
},
rights: window.$gz.role.defaultRightsObject(),
calendarHeight: 600,
settingsDialog: false,
newItemDialog: false,
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
languageName: window.$gz.locale.getResolvedLanguage(),
hour12: window.$gz.locale.getHour12(),
formUserOptions: {}
};
},
methods: {
openObject: function(type, id) {
window.$gz.eventBus.$emit("openobject", {
type: type,
id: id
});
},
newItem(atype) {
let newEvent = this.events[this.events.length - 1];
const addStart = window.$gz.locale.localScheduleFormatToUTC8601String(
newEvent.start,
this.timeZoneName
);
const addEnd = window.$gz.locale.localScheduleFormatToUTC8601String(
newEvent.end,
this.timeZoneName
);
switch (atype) {
case this.$ay.ayt().WorkOrderItemScheduledUser:
this.$router.push({
name: "workorder-edit",
params: {
recordid: 0,
add: {
type: atype,
start: addStart,
end: addEnd,
userId: this.$store.state.userId,
name: this.$store.state.userName
}
}
});
break;
case this.$ay.ayt().Reminder:
this.$router.push({
name: "reminder-edit",
params: {
recordid: 0,
add: {
start: addStart,
end: addEnd,
userId: this.$store.state.userId,
name: this.$store.state.userName
}
}
});
break;
}
//remove faux item, server will provide it back once it's created anyway
this.events.splice(this.events.length - 1);
this.newItemDialog = false;
},
cancelAddNew() {
this.events.splice(this.events.length - 1);
this.newItemDialog = false;
},
extendBottom(event) {
if (event.editable) {
//My work around to disambiguate extending and clicking
clearTimeout(this.dragTimeout);
this.dragged = false;
this.dragTimeout = setTimeout(() => {
this.dragged = true;
}, CLICK_DETECT_TIMEOUT); // Min delay to be regarded as extend instead of click, also affects click time to display more info, too short needs faster click
//console.log("extendBottom setting extendEvent");
this.extendEvent = event;
this.createStart = event.start;
this.extendOriginal = event.end;
} else {
//probably don't need this, was diagnosing new on click on locked items
////console.log("extendBottom not editable");
// // this.dragged = false;
// // this.extendEvent = null;
}
},
async endDragExtend() {
//On drag then dragged is set to true and dragEvent and dragTime are set
//on extend then dragged is set to true extendEvent (actual event), extendOriginal and createStart are set, dragEvent is null
//on create then dragged is set to false and createStart is only value set, dragEvent is null and extendEvent is null
//Handle the event, could be one of three things: changing an event start time, changing an event length or creating a new event
////console.log("end drag extend");
if (this.extendEvent && this.extendEvent.type == 0) {
//console.log("end drag extend NEW");
//NEW, prompt for deets and create or if cancelled then just remove this faux event from events list (it will be the last one in the array)
this.newItemDialog = true;
} else {
////console.log("end drag extend MODIFY??");
if (!this.dragged) {
//we're here due to mouse up but it's not a drag or an extend so it's a More Info mouse up click so bail out now
//console.log("end drag extend NOPE NOT MODIFY, MUST BE MOREINFO", {
// dragEvent: this.dragEvent
// });
this.dragEvent = null; //this needs to be set or it will keep dragging off an editable event even as the moreinfo dialog show
return;
}
//console.log("end drag extend YES MODIFY");
//MODIFY existing event, drag or extend
if (this.dragEvent || this.extendEvent) {
let param = { type: null, id: null, start: null, end: null };
if (this.dragEvent) {
// && this.dragged
param.type = this.dragEvent.type;
param.id = this.dragEvent.id;
param.start = window.$gz.locale.localScheduleFormatToUTC8601String(
this.dragEvent.start,
this.timeZoneName
);
param.end = window.$gz.locale.localScheduleFormatToUTC8601String(
this.dragEvent.end,
this.timeZoneName
);
} else {
param.type = this.extendEvent.type;
param.id = this.extendEvent.id;
param.start = window.$gz.locale.localScheduleFormatToUTC8601String(
this.extendEvent.start,
this.timeZoneName
);
param.end = window.$gz.locale.localScheduleFormatToUTC8601String(
this.extendEvent.end,
this.timeZoneName
);
}
try {
window.$gz.form.deleteAllErrorBoxErrors(this);
let res = await window.$gz.api.post("schedule/adjust", param);
if (res.error) {
this.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(this);
} else {
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error, this);
}
}
}
this.dragTime = null;
this.dragEvent = null;
//console.log("endDragExtend setting extendEvent to null");
this.extendEvent = null;
this.createStart = null;
this.extendOriginal = null;
},
cancelDrag() {
if (this.extendEvent) {
if (this.extendOriginal) {
//console.log("cancelDrag setting extendEvent");
this.extendEvent.end = this.extendOriginal;
} else {
const i = this.events.indexOf(this.extendEvent);
if (i !== -1) {
this.events.splice(i, 1);
}
}
}
//console.log("cancelDrag setting extendEvent to null");
this.extendEvent = null;
this.createStart = null;
this.dragTime = null;
this.dragEvent = null;
},
startDrag({ event }) {
//# mouse down on an event triggers this call
if (event) {
if (event.editable) {
//console.log("startDrag editable event: ", event);
//My work around to disambiguate dragging and clicking
clearTimeout(this.dragTimeout);
this.dragged = false;
this.dragTimeout = setTimeout(() => {
this.dragged = true;
}, CLICK_DETECT_TIMEOUT); // Min delay to be regarded as extend instead of click, also affects click time to display more info, too short needs faster click
this.dragEvent = event;
this.dragTime = null;
this.extendOriginal = null;
} else {
//console.log("startDrag not editable");
this.dragged = false;
this.dragEvent = event;
// this.extendEvent = null;
this.dragTime = null;
}
}
},
mouseMoveDayView(tms) {
//no event being dragged or exgtended?
if (!this.dragEvent && !this.extendEvent) {
return;
}
//console.log("mouseMoveDayView has event, processing...");
const mouse = this.toTime(tms);
if (this.dragEvent && this.dragTime !== null) {
//console.log("mousemovedayview dragging");
////console.log("mouseMoveDAyView::dragging path ", {
// dragged: this.dragged,
// dragEvent: this.dragEvent,
// dragTime: this.dragTime
// });
//# DRAGGING PATH
const start = this.dragEvent.start;
const end = this.dragEvent.end;
const duration = end - start;
const newStartTime = mouse - this.dragTime;
const newStart = this.roundTime(newStartTime);
const newEnd = newStart + duration;
this.dragEvent.start = newStart;
this.dragEvent.end = newEnd;
} else if (this.extendEvent && this.createStart !== null) {
//console.log("mouseMoveDAyView::extending path ", this.extendEvent);
//# EXTENDING PATH
const mouseRounded = this.roundTime(mouse, false);
const min = Math.min(mouseRounded, this.createStart);
const max = Math.max(mouseRounded, this.createStart);
this.extendEvent.start = min;
this.extendEvent.end = max;
}
},
mouseMoveMonthView(dd) {
if (!this.dragEvent) {
return;
}
//# DRAGGING PATH MONTH VIEW
//need to get the actual start time as it isn't in the mouse date
const dragEventStartDate = new Date(this.dragEvent.start);
const mouseDate = new Date(
dd.year,
dd.month - 1,
dd.day,
dragEventStartDate.getHours(),
dragEventStartDate.getMinutes()
).getTime();
const moveDelta = mouseDate - this.dragEvent.start;
this.dragEvent.start = mouseDate;
this.dragEvent.end = this.dragEvent.end + moveDelta;
},
startTime(tms) {
//This is called on the start of dragging an existing schedule item or drag extending a NEW schedule item
const mouse = this.toTime(tms);
if (this.dragEvent && this.dragTime === null) {
//# DAY VIEW *DRAG* EXISTING START EVENT (not extend)
//(also called on simple click to view schedule more info)
if (this.dragEvent.editable) {
//console.log("startTime calculating this.dragTime");
const start = this.dragEvent.start;
this.dragTime = mouse - start;
} else {
//console.log("startTime AVOIDED calculating this.dragTime");
}
} else {
//# DAY VIEW CREATE START EVENT
this.createStart = this.roundTime(mouse);
//console.log("startTime setting extendEvent");
this.extendEvent = {
name: "-",
color: this.$store.state.darkMode ? "white" : "black",
textColor: this.$store.state.darkMode ? "black" : "white",
start: this.createStart,
end: this.createStart,
timed: true,
type: 0,
id: 0,
editable: true
};
this.events.push(this.extendEvent);
}
},
roundTime(time, down = true) {
const roundTo = 15; // minutes
const roundDownTime = roundTo * 60 * 1000;
return down
? time - (time % roundDownTime)
: time + (roundDownTime - (time % roundDownTime));
},
toTime(tms) {
return new Date(
tms.year,
tms.month - 1,
tms.day,
tms.hour,
tms.minute
).getTime();
},
diagInfo() {
if (this.$refs.calendar) {
return (
"cal" +
JSON.stringify({
start: this.$refs.calendar.start,
end: this.$refs.calendar.end,
focus: this.focus,
firstInterval: this.$refs.calendar.firstInterval
})
);
} else {
return "no calendar";
}
},
typeToLabel() {
switch (this.viewType) {
case "month":
return this.$ay.t("ScheduleMonth");
case "week":
return this.$ay.t("ScheduleWeek");
case "day":
return this.$ay.t("ScheduleDay");
case "4day":
return this.$ay.t("Schedule4Day");
}
},
onResize() {
this.calendarHeight = window.innerHeight * 0.84;
},
viewDay({ date }) {
this.focus = date;
this.viewType = "day";
},
getEventColor(event) {
return event.color;
},
setToday() {
this.focus = "";
},
prev() {
this.$refs.calendar.prev();
},
next() {
this.$refs.calendar.next();
},
iconForSelectedEvent() {
return window.$gz.util.iconForType(this.selectedEvent.type);
},
iconForEvent(type) {
return window.$gz.util.iconForType(type);
},
openScheduledItem() {
window.$gz.eventBus.$emit("openobject", {
type: this.selectedEvent.type,
id: this.selectedEvent.id
});
},
async showMoreInfo({ nativeEvent, event }) {
//workaround to disambiguate drag click from view more info click
if (this.dragged) {
return;
}
let route = null;
this.evInfo = {};
switch (event.type) {
case window.$gz.type.WorkOrderItemScheduledUser:
route = `workorder/items/scheduled-users/sched-info/${event.id}`;
break;
case window.$gz.type.Reminder:
route = `reminder/sched-info/${event.id}`;
break;
case window.$gz.type.Review:
route = `review/sched-info/${event.id}`;
break;
}
if (route) {
let res = await window.$gz.api.get(route);
if (!res.error) {
this.evInfo = res.data;
}
}
const open = () => {
this.selectedEvent = event;
this.selectedElement = nativeEvent.target;
requestAnimationFrame(() =>
requestAnimationFrame(() => (this.moreInfoDialog = true))
);
};
if (this.moreInfoDialog) {
this.moreInfoDialog = false;
requestAnimationFrame(() => requestAnimationFrame(() => open()));
} else {
open();
}
nativeEvent.stopPropagation();
},
async fetchEvents({ start, end }) {
console.log("Fetching events");
try {
window.$gz.form.deleteAllErrorBoxErrors(this);
let res = await window.$gz.api.post("schedule/personal", {
view: window.$gz.util.calendarViewToAyaNovaEnum(this.viewType),
dark: this.$store.state.darkMode,
start: window.$gz.locale.localTimeDateStringToUTC8601String(
`${start.date}T00:00:00`,
this.timeZoneName
),
end: window.$gz.locale.localTimeDateStringToUTC8601String(
`${end.date}T23:59:59`,
this.timeZoneName
),
wisuColorSource: this.formUserOptions.wisuColorSource,
wisu: this.formUserOptions.wisu, //workorder item scheduled user records
reviews: this.formUserOptions.reviews,
reminders: this.formUserOptions.reminders
});
if (res.error) {
this.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(this);
} else {
this.events = res.data.map(x => {
return {
...x,
start: window.$gz.locale.utcDateToScheduleCompatibleFormatLocalized(
x.start,
this.timeZoneName
),
end: window.$gz.locale.utcDateToScheduleCompatibleFormatLocalized(
x.end,
this.timeZoneName
)
};
});
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error, this);
}
}
//eom
}
};
/////////////////////////////
//
//
async function clickHandler(menuItem) {
if (!menuItem) {
return;
}
let m = window.$gz.menu.parseMenuItem(menuItem);
if (m.owner == FORM_KEY && !m.disabled) {
switch (m.key) {
// case "report":
// let res = await m.vm.$refs.reportSelector.open(
// {
// AType: window.$gz.type.Project,
// selectedRowIds: [m.vm.obj.id]
// },
// m.id
// );
// if (res == null) {
// return;
// }
// window.$gz.form.setLastReport(FORM_KEY, res);
// generateMenu(m.vm);
// break;
case "WorkOrderItemScheduledUserList":
m.vm.$router.push({
name: "svc-workorder-item-scheduled-users",
params: {
aType: window.$gz.type.User,
objectId: m.vm.$store.state.userId,
name: m.vm.$store.state.userName
}
});
break;
case "WorkOrderItemLaborList":
m.vm.$router.push({
name: "svc-workorder-item-labors",
params: {
aType: window.$gz.type.User,
objectId: m.vm.$store.state.userId,
name: m.vm.$store.state.userName
}
});
break;
default:
window.$gz.eventBus.$emit(
"notify-warning",
FORM_KEY + "::context click: [" + m.key + "]"
);
}
}
}
//////////////////////
//
//
function generateMenu(vm) {
let menuOptions = {
isMain: true,
readOnly: vm.formState.readOnly,
icon: "$ayiCalendarDay",
title: "Schedule",
helpUrl: "home-schedule",
menuItems: []
};
// //REPORTS
// //Report not Print, print is a further option
// menuOptions.menuItems.push({
// title: "Report",
// icon: "$ayiFileAlt",
// key: FORM_KEY + ":report",
// vm: vm
// });
// //get last report selected
// let lastReport = window.$gz.form.getLastReport(FORM_KEY);
// if (lastReport != null) {
// menuOptions.menuItems.push({
// title: lastReport.name,
// notrans: true,
// icon: "$ayiFileAlt",
// key: FORM_KEY + ":report:" + lastReport.id,
// vm: vm
// });
// }
// if (vm.rights.change) {
// menuOptions.menuItems.push({
// title: "New",
// icon: "$ayiPlus",
// key: FORM_KEY + ":new",
// vm: vm
// });
// }
if (vm.$store.getters.isScheduleableUser) {
menuOptions.menuItems.push({ divider: true, inset: false });
menuOptions.menuItems.push({
title: "WorkOrderItemScheduledUserList",
icon: "$ayiUserClock",
key: FORM_KEY + ":WorkOrderItemScheduledUserList",
vm: vm
});
menuOptions.menuItems.push({
title: "WorkOrderItemLaborList",
icon: "$ayiHammer",
key: FORM_KEY + ":WorkOrderItemLaborList",
vm: vm
});
//--- /show all ---
}
menuOptions.menuItems.push({ divider: true, inset: false });
window.$gz.eventBus.$emit("menu-change", menuOptions);
}
/////////////////////////////////
//
//
async function initForm(vm) {
await fetchTranslatedText(vm);
getFormSettings(vm);
await getFormUserOptions(vm);
}
function getFormSettings(vm) {
let formSettings = window.$gz.form.getFormSettings(FORM_KEY);
if (!formSettings || !formSettings.temp || !formSettings.temp.viewType) {
//defaults
formSettings = { temp: { viewType: "month", focus: null } };
}
vm.viewType = formSettings.temp.viewType;
vm.focus = formSettings.temp.focus;
return formSettings;
}
function saveFormSettings(vm) {
let formSettings = window.$gz.form.getFormSettings(FORM_KEY);
formSettings.temp = { viewType: vm.viewType, focus: vm.focus };
window.$gz.form.setFormSettings(FORM_KEY, formSettings);
}
////////////////////
//
async function getFormUserOptions(vm) {
let res = await window.$gz.api.get(`form-user-options/${FORM_KEY}`);
if (res.error) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
if (res.data == null) {
console.log("getFormUserOptions null setting default");
//make a default
vm.formUserOptions = {
firstTime: "00:00",
wisuColorSource: 4,
wisu: true,
reviews: true,
reminders: true
};
} else {
vm.formUserOptions = res.data;
}
}
}
////////////////////
//
async function saveFormUserOptions(vm) {
let res = await window.$gz.api.upsert(
"form-user-options",
vm.formUserOptions
);
if (res.error) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
}
}
//////////////////////////////////////////////////////////
//
// Ensures UI translated text is available
//
async function fetchTranslatedText(vm) {
await window.$gz.translation.cacheTranslations([
"DateRangeToday",
"ScheduleMonth",
"ScheduleDay",
"ScheduleWeek",
"Schedule4Day",
"WorkOrder",
"Reminder",
"Review",
"DashboardScheduled",
"WorkOrderItemPriorityID",
"WorkOrderItemSummary",
"WorkOrderStatus",
"WorkOrderItemScheduledUserEstimatedQuantity",
"WorkOrderItemScheduledUserServiceRateID",
"WorkOrderItemTags"
]);
}
</script>
<style scoped lang="scss">
.v-event-draggable {
padding-left: 6px;
}
.v-event-timed {
user-select: none;
-webkit-user-select: none;
}
.v-event-drag-bottom {
position: absolute;
left: 0;
right: 0;
bottom: 4px;
height: 4px;
cursor: ns-resize;
&::after {
display: none;
position: absolute;
left: 50%;
height: 4px;
border-top: 1px solid gray;
border-bottom: 1px solid gray;
width: 32px;
margin-left: -8px;
// opacity: 0.8;
content: "";
}
&:hover::after {
display: block;
}
}
</style>