275 lines
10 KiB
Vue
275 lines
10 KiB
Vue
<script
|
|
setup
|
|
lang="ts"
|
|
>
|
|
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
|
import ShiftListScrollable from 'src/modules/timesheets/components/shift-list-scrollable.vue';
|
|
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
|
|
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
|
|
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
|
import TimesheetErrorWidget from 'src/modules/timesheets/components/timesheet-error-widget.vue';
|
|
import ShiftListWeeklyOverview from 'src/modules/timesheets/components/shift-list-weekly-overview.vue';
|
|
import ShiftListWeeklyOverviewMobile from 'src/modules/timesheets/components/mobile/shift-list-weekly-overview-mobile.vue';
|
|
import UnsavedChangesDialog from 'src/modules/timesheets/components/unsaved-changes-dialog.vue';
|
|
|
|
import { date, Notify } from 'quasar';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useRouter } from 'vue-router';
|
|
import { computed, onMounted, provide } from 'vue';
|
|
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
|
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
|
import { useExpensesStore } from 'src/stores/expense-store';
|
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
|
import { RouteNames } from 'src/router/router-constants';
|
|
|
|
// ================= state ====================
|
|
|
|
const { mode = 'normal', employeeEmail } = defineProps<{
|
|
mode?: 'approval' | 'normal';
|
|
employeeEmail?: string | undefined;
|
|
}>();
|
|
|
|
const { t } = useI18n();
|
|
const router = useRouter();
|
|
const expenseStore = useExpensesStore();
|
|
const timesheetStore = useTimesheetStore();
|
|
const timesheetApi = useTimesheetApi();
|
|
const shiftApi = useShiftApi();
|
|
|
|
// ================== computed ====================
|
|
|
|
const hasShiftErrors = computed(() => timesheetStore.all_current_shifts.filter(shift => shift.has_error === true).length > 0);
|
|
const isTimesheetsApproved = computed(() => timesheetStore.timesheets.every(timesheet => timesheet.is_approved));
|
|
|
|
const weeklyHours = computed(() => timesheetStore.timesheets.map(timesheet =>
|
|
Object.values(timesheet.weekly_hours).reduce((sum, hoursPerType) => sum += hoursPerType, 0) - timesheet.weekly_hours.sick
|
|
));
|
|
const totalHours = computed(() => timesheetStore.timesheets.reduce((sum, timesheet) =>
|
|
sum += timesheet.weekly_hours.regular
|
|
+ timesheet.weekly_hours.evening
|
|
+ timesheet.weekly_hours.emergency
|
|
+ timesheet.weekly_hours.vacation
|
|
+ timesheet.weekly_hours.holiday
|
|
+ timesheet.weekly_hours.overtime,
|
|
0 //initial value
|
|
));
|
|
|
|
const totalExpenses = computed(() => timesheetStore.timesheets.reduce((sum, timesheet) =>
|
|
sum + timesheet.weekly_expenses.expenses
|
|
+ timesheet.weekly_expenses.on_call
|
|
+ timesheet.weekly_expenses.per_diem,
|
|
0 //initial value
|
|
));
|
|
|
|
// =================== methods ==========================
|
|
|
|
provide('employeeEmail', employeeEmail);
|
|
provide('mode', mode);
|
|
|
|
const onClickSaveTimesheets = async () => {
|
|
if (mode === 'normal') {
|
|
await shiftApi.saveShiftChanges();
|
|
Notify.create({
|
|
message: t('timesheet.save_successful'),
|
|
color: 'accent',
|
|
});
|
|
} else {
|
|
await shiftApi.saveShiftChanges(timesheetStore.current_pay_period_overview?.email);
|
|
}
|
|
}
|
|
|
|
const onClickLeave = async () => {
|
|
timesheetStore.isShowingUnsavedWarning = false;
|
|
timesheetStore.timesheets = [];
|
|
timesheetStore.initial_timesheets = [];
|
|
await router.push({ name: timesheetStore.nextPageNameAfterUnsaveWarning ?? RouteNames.DASHBOARD});
|
|
}
|
|
|
|
const onClickSaveBeforeLeaving = async () => {
|
|
if (mode === 'approval') return;
|
|
|
|
timesheetStore.isShowingUnsavedWarning = false;
|
|
await onClickSaveTimesheets();
|
|
await onClickLeave();
|
|
}
|
|
|
|
onMounted(async () => {
|
|
if (mode === 'normal')
|
|
await timesheetApi.getTimesheetsByDate(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="column items-center full-height"
|
|
:class="mode === 'normal' ? 'relative-position' : ' no-wrap'"
|
|
>
|
|
<LoadingOverlay v-model="timesheetStore.is_loading" />
|
|
|
|
<!-- label for approval mode to delimit that this is the timesheet -->
|
|
<div
|
|
v-if="mode === 'approval'"
|
|
class="col-auto row full-width q-px-xl"
|
|
>
|
|
<span class="col-auto text-uppercase text-bold text-h5">
|
|
{{ $t('timesheet.page_header') }}
|
|
</span>
|
|
|
|
<q-space />
|
|
</div>
|
|
|
|
|
|
<!-- weekly overview -->
|
|
<div class="col-auto row q-px-lg full-width">
|
|
<!-- supervisor weekly overview -->
|
|
<div
|
|
v-if="!$q.platform.is.mobile"
|
|
class="col-xs-6 col-md-4 col-xl-3 q-pa-md"
|
|
>
|
|
<ShiftListWeeklyOverview
|
|
mode="total-hours"
|
|
:timesheet-mode="mode"
|
|
:weekly-hours="weeklyHours"
|
|
:total-hours="totalHours"
|
|
:total-expenses="totalExpenses"
|
|
/>
|
|
</div>
|
|
|
|
<q-space v-if="!$q.platform.is.mobile" />
|
|
|
|
<!-- employee weekly overview -->
|
|
<div
|
|
v-if="!$q.platform.is.mobile"
|
|
class="col-xs-6 col-md-4 col-xl-3 q-pa-md"
|
|
>
|
|
<ShiftListWeeklyOverview
|
|
mode="off-hours"
|
|
:timesheet-mode="mode"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- top menu -->
|
|
<div
|
|
v-if="mode === 'normal'"
|
|
class="col-auto row items-center full-width"
|
|
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'justify-between q-px-md' : 'q-pb-sm q-px-xl'"
|
|
>
|
|
<!-- navigation btn -->
|
|
<PayPeriodNavigator
|
|
class="col-auto"
|
|
@date-selected="timesheetApi.getTimesheetsByDate"
|
|
@pressed-previous-button="timesheetApi.getTimesheetsByCurrentPayPeriod"
|
|
@pressed-next-button="timesheetApi.getTimesheetsByCurrentPayPeriod"
|
|
/>
|
|
|
|
<!-- mobile expenses button -->
|
|
<div
|
|
v-if="$q.screen.width < $q.screen.height && mode === 'normal'"
|
|
class="col q-pl-lg"
|
|
>
|
|
<q-btn
|
|
push
|
|
rounded
|
|
color="accent"
|
|
icon="receipt_long"
|
|
class="full-width"
|
|
@click="expenseStore.open"
|
|
/>
|
|
</div>
|
|
|
|
<q-space v-if="$q.screen.width > $q.screen.height" />
|
|
|
|
<!-- desktop expenses button -->
|
|
<q-btn
|
|
v-if="mode === 'normal' && $q.screen.width > $q.screen.height"
|
|
push
|
|
rounded
|
|
color="accent"
|
|
icon="receipt_long"
|
|
:label="$t('timesheet.expense.open_btn')"
|
|
@click="expenseStore.open"
|
|
/>
|
|
|
|
<!-- desktop save timesheet changes button -->
|
|
<q-btn
|
|
v-if="!isTimesheetsApproved && $q.screen.width > $q.screen.height"
|
|
push
|
|
rounded
|
|
:disable="timesheetStore.is_loading || hasShiftErrors || !timesheetStore.canSaveTimesheets"
|
|
:color="timesheetStore.is_loading || hasShiftErrors || !timesheetStore.canSaveTimesheets ? 'grey-5' : 'accent'"
|
|
icon="upload"
|
|
:label="$t('shared.label.save')"
|
|
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'full-width' : 'q-ml-md'"
|
|
@click="onClickSaveTimesheets"
|
|
/>
|
|
</div>
|
|
|
|
<!-- error message widget for potential backend-provided errors -->
|
|
<TimesheetErrorWidget class="col-auto" />
|
|
|
|
<!-- mobile weekly overview widget -->
|
|
<ShiftListWeeklyOverviewMobile class="col-auto" />
|
|
|
|
<!-- standard scrollable shift list for user input -->
|
|
<ShiftListScrollable
|
|
v-if="mode === 'normal'"
|
|
:mode="mode"
|
|
:class="mode === 'normal' ? 'col' : 'col-auto'"
|
|
/>
|
|
|
|
<!-- full shift list for timesheet approval details dialog -->
|
|
<div
|
|
v-else
|
|
class="col-auto column"
|
|
:class="$q.platform.is.mobile ? 'fit no-wrap' : 'full-width'"
|
|
:style="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'margin-bottom: 40px' : ''"
|
|
>
|
|
<!-- If no timesheets found -->
|
|
<div
|
|
v-if="timesheetStore.timesheets.length < 1 && !timesheetStore.is_loading"
|
|
class="col-auto column flex-center fit q-py-lg"
|
|
style="min-height: 20vh;"
|
|
>
|
|
<span class="text-uppercase text-weight-bolder text-center">{{ $t('shared.error.no_data_found')
|
|
}}</span>
|
|
<q-icon
|
|
name="las la-calendar"
|
|
color="accent"
|
|
size="10em"
|
|
class="absolute"
|
|
style="opacity: 0.2;"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Else show timesheets -->
|
|
<ShiftList
|
|
v-else
|
|
class="col-auto"
|
|
/>
|
|
</div>
|
|
|
|
<q-btn
|
|
v-if="$q.platform.is.mobile && $q.screen.width < $q.screen.height"
|
|
square
|
|
:disable="timesheetStore.is_loading || hasShiftErrors || !timesheetStore.canSaveTimesheets"
|
|
size="lg"
|
|
color="accent"
|
|
icon="upload"
|
|
:label="$t('shared.label.save')"
|
|
class="absolute-bottom shadow-up-10"
|
|
style="height: 40px;"
|
|
@click="onClickSaveTimesheets"
|
|
/>
|
|
|
|
<ExpenseDialog
|
|
:is-approved="isTimesheetsApproved"
|
|
class="z-top"
|
|
/>
|
|
|
|
<UnsavedChangesDialog
|
|
@click-save-no="onClickLeave"
|
|
@click-save-yes="onClickSaveBeforeLeaving"
|
|
/>
|
|
</div>
|
|
</template> |