DOMPurify 2.3.8 -> 2.5.9 and Vuetify 2.6.6 -> 2.7.2, the latest 2.x of each; builds on Node 12.22.9. Only these two packages change in the lockfile (kept in its v2 format). Vuetify 2.7 calendar eventSummary() returns an element, not an HTML string, so the six calendar event templates render it with the new gz-vnodes component instead of v-html. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1280 lines
40 KiB
Vue
1280 lines
40 KiB
Vue
<template>
|
|
<div v-if="formState.ready" v-resize="onResize" class="my-n8">
|
|
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
|
<v-sheet height="64">
|
|
<v-toolbar flat class="ml-n3">
|
|
<v-btn
|
|
text
|
|
:small="isXS"
|
|
:large="issmAndUp"
|
|
data-cy="btnTodayHomeSchedule"
|
|
@click="setToday"
|
|
>
|
|
{{ $ay.t("DateRangeToday") }}
|
|
</v-btn>
|
|
<v-btn fab text :small="isXS" @click="prev">
|
|
<v-icon :small="isXS">$prev</v-icon>
|
|
</v-btn>
|
|
<v-btn fab text :small="isXS" @click="next">
|
|
<v-icon :small="isXS">$next</v-icon>
|
|
</v-btn>
|
|
<v-toolbar-title v-if="$refs.calendar">
|
|
<v-btn
|
|
:small="isXS"
|
|
:large="issmAndUp"
|
|
text
|
|
@click="viewType = 'month'"
|
|
>
|
|
{{ $refs.calendar.title }}</v-btn
|
|
>
|
|
</v-toolbar-title>
|
|
<v-spacer v-if="issmAndUp"></v-spacer>
|
|
<v-btn
|
|
class="mr-3"
|
|
fab
|
|
text
|
|
:small="isXS"
|
|
:large="issmAndUp"
|
|
@click="settingsDialog = true"
|
|
>
|
|
<v-icon :small="isXS">$ayiCog</v-icon>
|
|
</v-btn>
|
|
|
|
<v-menu bottom right>
|
|
<template v-slot:activator="{ on, attrs }">
|
|
<v-btn
|
|
:small="isXS"
|
|
:large="issmAndUp"
|
|
text
|
|
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"
|
|
data-cy="calendar"
|
|
color="primary"
|
|
:events="events"
|
|
:event-color="getEventColor"
|
|
:type="viewType"
|
|
:locale="languageName"
|
|
:event-more-text="$ay.t('More')"
|
|
:first-time="formUserOptions.firstTime"
|
|
:weekdays="weekdays"
|
|
event-overlap-mode="column"
|
|
@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"
|
|
>
|
|
<template v-slot:event="{ event, timed, eventSummary }">
|
|
<div class="v-event-draggable">
|
|
<v-icon small :color="event.textColor" class="mt-n3 mr-1">{{
|
|
iconForEvent(event.type)
|
|
}}</v-icon>
|
|
<span :class="event.textColor + '--text'"
|
|
><gz-vnodes :vnodes="eventSummary()"/></span
|
|
><v-icon
|
|
v-if="!event.editable"
|
|
x-small
|
|
:color="event.textColor"
|
|
class="ml-n3"
|
|
>
|
|
$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 v-model="newItemDialog" max-width="360px" persistent>
|
|
<v-card>
|
|
<v-card-title>{{ $ay.t("New") }}</v-card-title>
|
|
<v-card-text>
|
|
<v-col v-if="canCreateWorkOrder" 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 v-if="canCreateReminder" 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 color="primary" @click="cancelAddNew">{{
|
|
$ay.t("Cancel")
|
|
}}</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
</v-row>
|
|
</template>
|
|
<!-- MORE INFO DIALOG -->
|
|
<template>
|
|
<v-row justify="center">
|
|
<v-dialog v-model="moreInfoDialog" max-width="600px">
|
|
<v-card>
|
|
<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("DashboardScheduled") }}:</span
|
|
>
|
|
<span class="text-body-1 ml-2"
|
|
>{{ $ay.dt(evInfo.startDate) }} — {{
|
|
$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 v-if="evInfo.aType" class="mb-1">
|
|
<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 }} {{ evInfo.customerViz }}</span
|
|
>
|
|
</div>
|
|
<div>
|
|
{{ evInfo.wosummary }}
|
|
</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) }} — {{
|
|
$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
|
|
v-if="evInfo.wostatuslocked"
|
|
color="primary"
|
|
class="ml-4"
|
|
>$ayiLock</v-icon
|
|
>
|
|
<v-icon
|
|
v-if="evInfo.wostatuscompleted"
|
|
color="primary"
|
|
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-dialog>
|
|
</v-row>
|
|
</template>
|
|
</v-sheet>
|
|
<template>
|
|
<!-- ############## SETTINGS DIALOG #################-->
|
|
<v-row justify="center">
|
|
<v-dialog v-model="settingsDialog" max-width="600px">
|
|
<v-card>
|
|
<v-card-title>{{ $ay.t("ScheduleOptions") }} </v-card-title>
|
|
<v-card-text>
|
|
<v-row no-gutters>
|
|
<v-col cols="12" class="mt-2">
|
|
<gz-time-picker
|
|
v-model="tempFirstTime"
|
|
:label="$ay.t('ScheduleFirstHour')"
|
|
></gz-time-picker>
|
|
</v-col>
|
|
|
|
<v-col cols="12">
|
|
<GZDaysOfWeek
|
|
ref="daysofweek"
|
|
v-model="formUserOptions.excludeDaysOfWeek"
|
|
:label="$ay.t('ExcludeDaysOfWeek')"
|
|
></GZDaysOfWeek>
|
|
</v-col>
|
|
|
|
<span class="text-h6 mt-3">{{
|
|
$ay.t("ScheduleShowTypes")
|
|
}}</span>
|
|
<v-col cols="12">
|
|
<v-checkbox
|
|
v-model="formUserOptions.reminders"
|
|
:label="$ay.t('ReminderList')"
|
|
></v-checkbox>
|
|
</v-col>
|
|
<v-col cols="12">
|
|
<v-checkbox
|
|
v-model="formUserOptions.reviews"
|
|
:label="$ay.t('ReviewList')"
|
|
></v-checkbox>
|
|
</v-col>
|
|
|
|
<v-col cols="12">
|
|
<v-checkbox
|
|
v-model="formUserOptions.wisu"
|
|
:label="$ay.t('WorkOrderList')"
|
|
></v-checkbox>
|
|
|
|
<v-radio-group
|
|
v-model="formUserOptions.wisuColorSource"
|
|
:mandatory="true"
|
|
:label="$ay.t('ScheduleWOColorFrom')"
|
|
class="ml-3 mt-n2"
|
|
>
|
|
<v-radio :label="$ay.t('NoColor')" value="0"></v-radio>
|
|
<v-radio
|
|
:label="$ay.t('WorkOrderStatus')"
|
|
value="2"
|
|
></v-radio>
|
|
<v-radio
|
|
:label="$ay.t('WorkOrderItemWorkOrderStatusID')"
|
|
value="3"
|
|
></v-radio>
|
|
<v-radio
|
|
:label="$ay.t('WorkOrderItemPriorityID')"
|
|
value="4"
|
|
></v-radio>
|
|
</v-radio-group>
|
|
</v-col>
|
|
</v-row>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-btn text color="primary" @click="settingsDialog = false">{{
|
|
$ay.t("Cancel")
|
|
}}</v-btn>
|
|
<v-spacer v-if="issmAndUp"></v-spacer>
|
|
<v-btn
|
|
color="primary"
|
|
text
|
|
class="ml-4"
|
|
@click="saveUserOptions()"
|
|
>{{ $ay.t("Save") }}</v-btn
|
|
>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
</v-row>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
const FORM_KEY = "home-schedule";
|
|
import GZDaysOfWeek from "../components/days-of-week-control.vue";
|
|
export default {
|
|
components: {
|
|
GZDaysOfWeek
|
|
},
|
|
data() {
|
|
return {
|
|
focus: "",
|
|
viewType: "month",
|
|
selectedEvent: {},
|
|
selectedElement: null,
|
|
events: [],
|
|
evInfo: {},
|
|
dragEvent: 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: {}
|
|
},
|
|
calendarHeight: 600,
|
|
settingsDialog: false,
|
|
newItemDialog: false,
|
|
moreInfoDialog: false,
|
|
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
|
languageName: window.$gz.locale.getResolvedLanguage(),
|
|
hour12: window.$gz.locale.getHour12(),
|
|
formUserOptions: {},
|
|
tempFirstTime: null,
|
|
lastMouseDownMS: null,
|
|
lastStart: null,
|
|
lastEnd: null
|
|
};
|
|
},
|
|
computed: {
|
|
weekdays() {
|
|
return window.$gz.util.DaysOfWeekToWeekdays(
|
|
this.formUserOptions.excludeDaysOfWeek
|
|
);
|
|
},
|
|
isXS() {
|
|
//console.log(this.$vuetify.breakpoint);
|
|
return this.$vuetify.breakpoint.xs;
|
|
},
|
|
issmAndUp() {
|
|
return this.$vuetify.breakpoint.smAndUp;
|
|
},
|
|
canCreateWorkOrder() {
|
|
if (
|
|
this.$store.getters.isScheduleableUser &&
|
|
!window.$gz.role.hasRole([
|
|
window.$gz.role.AUTHORIZATION_ROLES.TechRestricted
|
|
]) &&
|
|
!window.$gz.store.getters.isSubContractorUser
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
canCreateReminder() {
|
|
if (!window.$gz.store.getters.isSubContractorUser) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
async created() {
|
|
const 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);
|
|
},
|
|
methods: {
|
|
async refresh() {
|
|
await this.fetchEvents({ start: null, end: null });
|
|
},
|
|
openObject: function(type, id) {
|
|
window.$gz.eventBus.$emit("openobject", {
|
|
type: type,
|
|
id: id
|
|
});
|
|
},
|
|
newItem(atype) {
|
|
const 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) {
|
|
//console.log("extend bottom fired");
|
|
if (event.editable) {
|
|
//capture time to see if it's a click or a drag/extend
|
|
this.lastMouseDownMS = new Date().getTime();
|
|
this.extendEvent = event;
|
|
this.createStart = event.start;
|
|
this.extendOriginal = event.end;
|
|
}
|
|
},
|
|
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
|
|
if (this.extendEvent && this.extendEvent.type == 0) {
|
|
//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)
|
|
if (this.canCreateWorkOrder || this.canCreateReminder)
|
|
this.newItemDialog = true;
|
|
} else {
|
|
//MORE INFO
|
|
if (this.lastMouseDownMS != null && this.itWasAClickNotADrag()) {
|
|
await this.showMoreInfo(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;
|
|
}
|
|
|
|
//MODIFY existing event, drag or extend
|
|
if (this.dragEvent || this.extendEvent) {
|
|
const param = {
|
|
type: null,
|
|
id: null,
|
|
start: null,
|
|
end: null,
|
|
userId: this.$store.state.userId //Home-schedule the user id is always the current user, this is required due to same route handling svc-schedule where userid can change
|
|
};
|
|
|
|
if (this.dragEvent) {
|
|
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 {
|
|
let allowedToDoThis = true;
|
|
//only users who could create the object are allowed to modify it
|
|
if (
|
|
param.type == window.$gz.type.WorkOrderItemScheduledUser &&
|
|
!this.canCreateWorkOrder
|
|
) {
|
|
allowedToDoThis = false;
|
|
}
|
|
if (
|
|
param.type == window.$gz.type.Reminder &&
|
|
!this.canCreateReminder
|
|
) {
|
|
allowedToDoThis = false;
|
|
}
|
|
window.$gz.form.deleteAllErrorBoxErrors(this);
|
|
if (allowedToDoThis) {
|
|
const res = await window.$gz.api.post("schedule/adjust", param);
|
|
if (res.error) {
|
|
this.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(this);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
window.$gz.errorHandler.handleFormError(error, this);
|
|
}
|
|
}
|
|
}
|
|
this.dragTime = null;
|
|
this.dragEvent = null;
|
|
this.extendEvent = null;
|
|
this.createStart = null;
|
|
this.extendOriginal = null;
|
|
},
|
|
cancelDrag() {
|
|
if (this.extendEvent) {
|
|
if (this.extendOriginal) {
|
|
this.extendEvent.end = this.extendOriginal;
|
|
} else {
|
|
const i = this.events.indexOf(this.extendEvent);
|
|
if (i !== -1) {
|
|
this.events.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
this.extendEvent = null;
|
|
this.createStart = null;
|
|
this.dragTime = null;
|
|
this.dragEvent = null;
|
|
this.lastMouseDownMS = null;
|
|
},
|
|
startDrag({ event }) {
|
|
//# mouse down on an event triggers this call
|
|
if (event) {
|
|
this.lastMouseDownMS = new Date().getTime(); //snapshot time to disambiguate drag vs click
|
|
if (event.editable) {
|
|
this.dragged = false;
|
|
this.dragEvent = event;
|
|
this.dragTime = null;
|
|
this.extendOriginal = null;
|
|
} else {
|
|
this.dragged = false;
|
|
this.dragEvent = event;
|
|
this.dragTime = null;
|
|
}
|
|
}
|
|
},
|
|
itWasAClickNotADrag() {
|
|
if (this.lastMouseDownMS == null) {
|
|
if (this.$ay.dev) {
|
|
throw new Error("lastMouseDownMS is null!");
|
|
} else {
|
|
window.$gz.store.commit(
|
|
"logItem",
|
|
"home-schedule:lastMouseDownMS is unexpectedly null"
|
|
);
|
|
return true; //least dangerous option in production
|
|
}
|
|
}
|
|
const elapsed = new Date().getTime() - this.lastMouseDownMS;
|
|
this.lastMouseDownMS = null;
|
|
return elapsed < 200;
|
|
},
|
|
mouseMoveDayView(tms) {
|
|
//no event being dragged or exgtended?
|
|
if (!this.dragEvent && !this.extendEvent) {
|
|
return;
|
|
}
|
|
const mouse = this.toTime(tms);
|
|
if (this.dragEvent && this.dragTime !== null) {
|
|
//# 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) {
|
|
//# 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) {
|
|
const start = this.dragEvent.start;
|
|
this.dragTime = mouse - start;
|
|
}
|
|
} else {
|
|
//# DAY VIEW CREATE START EVENT
|
|
this.createStart = this.roundTime(mouse);
|
|
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();
|
|
},
|
|
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 }) {
|
|
//console.log("ViewDay clicked");
|
|
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(event) {
|
|
this.selectedEvent = event;
|
|
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) {
|
|
const res = await window.$gz.api.get(route);
|
|
if (!res.error) {
|
|
this.evInfo = res.data;
|
|
this.moreInfoDialog = true;
|
|
} else {
|
|
this.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(this);
|
|
}
|
|
}
|
|
},
|
|
async fetchEvents({ start, end }) {
|
|
if (start) {
|
|
this.lastStart = start;
|
|
this.lastEnd = end;
|
|
} else {
|
|
start = this.lastStart;
|
|
end = this.lastEnd;
|
|
}
|
|
try {
|
|
window.$gz.form.deleteAllErrorBoxErrors(this);
|
|
const 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.splice(0);
|
|
const timeZoneName = this.timeZoneName;
|
|
let i = res.data.length;
|
|
while (i--) {
|
|
const x = res.data[i];
|
|
this.events.push({
|
|
start: new Date(
|
|
new Date(x.start)
|
|
.toLocaleString("sv-SE", {
|
|
timeZone: timeZoneName
|
|
})
|
|
.replace(" ", "T")
|
|
).getTime(),
|
|
end: new Date(
|
|
new Date(x.end)
|
|
.toLocaleString("sv-SE", {
|
|
timeZone: timeZoneName
|
|
})
|
|
.replace(" ", "T")
|
|
).getTime(),
|
|
timed: true,
|
|
name: x.name,
|
|
color: x.color,
|
|
textColor: x.textColor,
|
|
type: x.type,
|
|
id: x.id,
|
|
editable: x.editable,
|
|
userId: x.userId
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
window.$gz.errorHandler.handleFormError(error, this);
|
|
}
|
|
},
|
|
async saveUserOptions() {
|
|
this.settingsDialog = false;
|
|
await saveFormUserOptions(this);
|
|
await this.refresh();
|
|
}
|
|
//eom
|
|
}
|
|
};
|
|
|
|
/////////////////////////////
|
|
//
|
|
//
|
|
async function clickHandler(menuItem) {
|
|
if (!menuItem) {
|
|
return;
|
|
}
|
|
const m = window.$gz.menu.parseMenuItem(menuItem);
|
|
if (m.owner == FORM_KEY && !m.disabled) {
|
|
switch (m.key) {
|
|
// case "report":
|
|
// const 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.setLastReportMenuItem(FORM_KEY, res, 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;
|
|
|
|
case "ReminderList":
|
|
m.vm.$router.push({
|
|
name: "home-reminders"
|
|
});
|
|
break;
|
|
|
|
case "ReviewList":
|
|
m.vm.$router.push({
|
|
name: "home-reviews"
|
|
});
|
|
break;
|
|
case "newcustomer":
|
|
m.vm.$router.push({
|
|
name: "customer-edit",
|
|
params: { recordid: 0 }
|
|
});
|
|
break;
|
|
default:
|
|
window.$gz.eventBus.$emit(
|
|
"notify-warning",
|
|
FORM_KEY + "::context click: [" + m.key + "]"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////
|
|
//
|
|
//
|
|
function generateMenu(vm) {
|
|
const menuOptions = {
|
|
isMain: true,
|
|
readOnly: vm.formState.readOnly,
|
|
icon: "$ayiCalendarDay",
|
|
title: "Schedule",
|
|
helpUrl: "home-schedule",
|
|
menuItems: []
|
|
};
|
|
|
|
// //
|
|
// menuOptions.menuItems.push({
|
|
// title: "Report",
|
|
// icon: "$ayiFileAlt",
|
|
// key: FORM_KEY + ":report",
|
|
// vm: vm
|
|
// });
|
|
|
|
//
|
|
// const 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
|
|
// });
|
|
// }
|
|
|
|
menuOptions.menuItems.push({
|
|
title: "ReminderList",
|
|
icon: "$ayiStickyNote",
|
|
key: FORM_KEY + ":ReminderList",
|
|
vm: vm
|
|
});
|
|
|
|
menuOptions.menuItems.push({
|
|
title: "ReviewList",
|
|
icon: "$ayiCalendarCheck",
|
|
key: FORM_KEY + ":ReviewList",
|
|
vm: vm
|
|
});
|
|
|
|
if (vm.$store.getters.isScheduleableUser) {
|
|
menuOptions.menuItems.push({ divider: true, inset: false });
|
|
|
|
if (window.$gz.role.canChange(window.$gz.type.Customer)) {
|
|
menuOptions.menuItems.push({
|
|
title: "NewCustomer",
|
|
icon: "$ayiAddressCard",
|
|
key: FORM_KEY + ":newcustomer",
|
|
vm: vm
|
|
});
|
|
}
|
|
|
|
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
|
|
});
|
|
}
|
|
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) {
|
|
const 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) {
|
|
const 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) {
|
|
//make a default
|
|
vm.formUserOptions = {
|
|
firstTime: "00:00",
|
|
excludeDaysOfWeek: 0,
|
|
wisuColorSource: "2",
|
|
wisu: true,
|
|
reviews: true,
|
|
reminders: true
|
|
};
|
|
} else {
|
|
vm.formUserOptions = JSON.parse(res.data.options);
|
|
}
|
|
//takes local time in "HH:MM" format and converts to ISO UTC format for picker consumption
|
|
const d = new Date();
|
|
const temp = new Date(
|
|
d.getFullYear(),
|
|
d.getMonth(),
|
|
d.getDate(),
|
|
vm.formUserOptions.firstTime.split(":")[0],
|
|
vm.formUserOptions.firstTime.split(":")[1]
|
|
);
|
|
vm.tempFirstTime = window.$gz.locale.localTimeDateStringToUTC8601String(
|
|
temp.toISOString(),
|
|
vm.timeZoneName
|
|
);
|
|
}
|
|
}
|
|
|
|
////////////////////
|
|
//
|
|
async function saveFormUserOptions(vm) {
|
|
//translate tempFirstTime to local time
|
|
if (vm.tempFirstTime) {
|
|
//convert picker's utc time to local hour:MM
|
|
const localTime = window.$gz.DateTime.fromISO(vm.tempFirstTime).toLocal();
|
|
vm.formUserOptions.firstTime = `${localTime.hour}:${localTime.minute}`;
|
|
}
|
|
const res = await window.$gz.api.post("form-user-options", {
|
|
formKey: FORM_KEY,
|
|
options: JSON.stringify(vm.formUserOptions)
|
|
});
|
|
if (res.error) {
|
|
vm.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(vm);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Ensures UI translated text is available
|
|
//
|
|
async function fetchTranslatedText() {
|
|
await window.$gz.translation.cacheTranslations([
|
|
"DateRangeToday",
|
|
"ExcludeDaysOfWeek",
|
|
"ScheduleMonth",
|
|
"ScheduleDay",
|
|
"ScheduleWeek",
|
|
"Schedule4Day",
|
|
"ScheduleFirstHour",
|
|
"ScheduleWOColorFrom",
|
|
"ScheduleOptions",
|
|
"ScheduleShowTypes",
|
|
"NoColor",
|
|
"WorkOrder",
|
|
"Reminder",
|
|
"WorkOrderList",
|
|
"ReminderList",
|
|
"ReviewList",
|
|
"DashboardScheduled",
|
|
"WorkOrderItemPriorityID",
|
|
"WorkOrderItemSummary",
|
|
"WorkOrderItemWorkOrderStatusID",
|
|
"WorkOrderStatus",
|
|
"WorkOrderItemScheduledUserEstimatedQuantity",
|
|
"WorkOrderItemScheduledUserServiceRateID",
|
|
"WorkOrderItemTags",
|
|
"ReminderName",
|
|
"ReviewDate",
|
|
"ReminderNotes",
|
|
"ReviewName",
|
|
"ReviewNotes",
|
|
"ReviewCompletedDate",
|
|
"ReviewCompletionNotes",
|
|
"NewCustomer"
|
|
]);
|
|
}
|
|
</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>
|