targo-frontend/src/modules/timesheets/components/timesheet-wrapper.vue
Nicolas Drolet 13c339953f feat(timesheet): add shift overlap verification to shift entries
Also refactor mobile UI/UX for timesheet: reduced header bloat, made only shifts scrollable, added left or right swipe to travel between pay periods, showing default 'no data' message when beyond 6-month-back 1-month-forward timesheet scope.
2025-12-18 10:05:31 -05:00

112 lines
4.4 KiB
Vue

<script
setup
lang="ts"
>
/* eslint-disable */
import ShiftList from 'src/modules/timesheets/components/shift-list.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 LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
import { computed, onMounted } 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 { date } from 'quasar';
const expenses_store = useExpensesStore();
const timesheet_store = useTimesheetStore();
const timesheet_api = useTimesheetApi();
const shift_api = useShiftApi();
const has_shift_errors = computed(() => timesheet_store.all_current_shifts.filter(shift => shift.has_error === true).length > 0);
const is_timesheets_approved = computed(() => timesheet_store.timesheets.every(timesheet => timesheet.is_approved))
const { mode = 'normal' } = defineProps<{
mode?: 'approval' | 'normal';
}>();
onMounted(async () => {
await timesheet_api.getTimesheetsByDate(date.formatDate(new Date(), 'YYYY-MM-DD'));
});
</script>
<template>
<div class="column items-center full-height">
<LoadingOverlay v-model="timesheet_store.is_loading" />
<div
class="col-auto row items-center full-width"
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'justify-between' : 'q-mt-md q-px-md'"
>
<!-- navigation btn -->
<PayPeriodNavigator
v-if="mode === 'normal'"
class="col-auto"
@date-selected="date_value => timesheet_api.getTimesheetsByDate(date_value)"
@pressed-previous-button="timesheet_api.getTimesheetsByCurrentPayPeriod"
@pressed-next-button="timesheet_api.getTimesheetsByCurrentPayPeriod"
/>
<!-- mobile expenses button -->
<q-btn
v-if="($q.platform.is.mobile && ($q.screen.width < $q.screen.height))"
push
rounded
color="accent"
icon="receipt_long"
:label="$t('timesheet_approvals.table.expenses')"
class="col-auto"
@click="expenses_store.open"
/>
<q-space v-if="!$q.platform.is.mobile" />
<!-- desktop save timesheet changes button -->
<q-btn
v-if="mode === 'normal' && !is_timesheets_approved && !$q.platform.is.mobile"
push
rounded
:disable="timesheet_store.is_loading || has_shift_errors"
:color="timesheet_store.is_loading || has_shift_errors ? 'grey-5' : 'accent'"
icon="upload"
:label="$t('shared.label.save')"
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'full-width' : 'q-mr-md'"
@click="shift_api.saveShiftChanges"
/>
<!-- 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="expenses_store.open"
/>
</div>
<TimesheetErrorWidget class="col-auto"/>
<ShiftList />
<q-btn
v-if="mode === 'approval' || $q.platform.is.mobile && $q.screen.width < $q.screen.height"
push
rounded
:disable="timesheet_store.is_loading"
color="accent"
icon="upload"
:label="$t('shared.label.save')"
class="col-auto"
:class="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'full-width q-mt-sm' : 'q-mr-md'"
@click="shift_api.saveShiftChanges"
/>
<ExpenseDialog :is-approved="is_timesheets_approved" />
</div>
</template>