refactor(approvals): massive refactor of names, DRY component scripts, separation of concern, trim unnecessary code
This commit is contained in:
parent
f91a664a92
commit
3bf8c57f74
|
|
@ -31,5 +31,5 @@ body.body--dark {
|
||||||
|
|
||||||
.body--light {
|
.body--light {
|
||||||
--q-dark: #FFF;
|
--q-dark: #FFF;
|
||||||
color: $grey-8;
|
color: $blue-grey-8;
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +74,7 @@ export default {
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
search: "search",
|
search: "search",
|
||||||
|
filter: "filters",
|
||||||
loading: "loading...",
|
loading: "loading...",
|
||||||
language: "Language",
|
language: "Language",
|
||||||
add: "ajouter",
|
add: "ajouter",
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ export default {
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
search: 'recherche',
|
search: 'recherche',
|
||||||
|
filter: "filtres",
|
||||||
loading: 'chargement en cours...',
|
loading: 'chargement en cours...',
|
||||||
language: 'langue',
|
language: 'langue',
|
||||||
add: "ajouter",
|
add: "ajouter",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
import HeaderBarAvatar from './header-bar-avatar.vue';
|
import HeaderBarNotification from './main-layout-header-bar-notification.vue';
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
<q-item class="q-pa-none">
|
<q-item class="q-pa-none">
|
||||||
<HeaderBarAvatar />
|
<HeaderBarNotification />
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
|
|
@ -26,8 +26,16 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-drawer overlay elevated side="left" :mini="miniState" @mouseenter="miniState = false"
|
<q-drawer
|
||||||
@mouseleave="miniState = true" v-model="uiStore.isRightDrawerOpen">
|
v-model="uiStore.isRightDrawerOpen"
|
||||||
|
overlay
|
||||||
|
elevated
|
||||||
|
side="left"
|
||||||
|
:mini="miniState"
|
||||||
|
@mouseenter="miniState = false"
|
||||||
|
@mouseleave="miniState = true"
|
||||||
|
class="bg-dark"
|
||||||
|
>
|
||||||
<q-scroll-area class="fit">
|
<q-scroll-area class="fit">
|
||||||
<q-list>
|
<q-list>
|
||||||
<!-- Home -->
|
<!-- Home -->
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
import HeaderBar from 'src/modules/shared/components/navigation/header-bar.vue';
|
import HeaderBar from 'src/modules/layouts/components/main-layout-header-bar.vue';
|
||||||
import FooterBar from 'src/modules/shared/components/navigation/footer-bar.vue';
|
import FooterBar from 'src/modules/layouts/components/main-layout-footer-bar.vue';
|
||||||
import RightDrawer from 'src/modules/shared/components/navigation/right-drawer.vue';
|
import RightDrawer from 'src/modules/layouts/components/main-layout-right-drawer.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
30
src/modules/shared/components/page-header-template.vue
Normal file
30
src/modules/shared/components/page-header-template.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { title, startDate = "", endDate = "" } = defineProps<{
|
||||||
|
title: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const date_format_options = { day: 'numeric', month: 'long', year: 'numeric', };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="column q-mt-lg text-uppercase text-center text-weight-bolder text-h4">
|
||||||
|
<span class="col">{{ $t(title) }}</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="startDate.length > 0"
|
||||||
|
class="col row flex-center full-width q-py-none q-my-none"
|
||||||
|
>
|
||||||
|
<div class="text-primary text-weight-bold text-h6">
|
||||||
|
{{ $d(new Date(startDate), date_format_options) }}
|
||||||
|
</div>
|
||||||
|
<div class="text-body2 q-mx-md text-weight-medium">
|
||||||
|
{{ $t('shared.misc.to') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-primary text-weight-bold text-h6">
|
||||||
|
{{ $d(new Date(endDate), date_format_options) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
43
src/modules/shared/components/q-table-filters.vue
Normal file
43
src/modules/shared/components/q-table-filters.vue
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const search_model = defineModel<string | number | null>({ default: null, required: true });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Filters toggle -->
|
||||||
|
<q-btn-dropdown
|
||||||
|
push
|
||||||
|
class="q-mr-md bg-white text-primary left-rounded"
|
||||||
|
:label="$t('shared.label.filter')"
|
||||||
|
icon="filter_alt"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Search bar -->
|
||||||
|
<q-input
|
||||||
|
v-model="search_model"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
debounce="300"
|
||||||
|
class="right-rounded"
|
||||||
|
:label="$t('shared.label.search')"
|
||||||
|
label-color="primary"
|
||||||
|
bg-color="white"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<template #before>
|
||||||
|
<q-icon
|
||||||
|
name="search"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.left-rounded {
|
||||||
|
border-radius: 50% 0 0 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-rounded {
|
||||||
|
border-radius: 0 50% 50% 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
/* eslint-disable */
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { date } from 'quasar';
|
|
||||||
import type { QDateDetails } from 'src/modules/shared/types/q-date-details';
|
|
||||||
|
|
||||||
const is_showing_calendar_picker = ref(false);
|
|
||||||
const calendar_date = ref(date.formatDate( Date.now(), 'YYYY-MM-DD' ));
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
isDisabled: boolean,
|
|
||||||
isPreviousLimit: boolean,
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'date-selected': [value: string, reason?: string, details?: QDateDetails]
|
|
||||||
'pressed-previous-button': []
|
|
||||||
'pressed-next-button': []
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const onDateSelected = (value: string, reason: string, details: QDateDetails) => {
|
|
||||||
calendar_date.value = value;
|
|
||||||
is_showing_calendar_picker.value = false;
|
|
||||||
emit('date-selected', value, reason, details);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="row justify-center">
|
|
||||||
<q-btn
|
|
||||||
push rounded
|
|
||||||
icon="keyboard_arrow_left"
|
|
||||||
color="primary"
|
|
||||||
@click="emit('pressed-previous-button')"
|
|
||||||
:disable="props.isPreviousLimit || props.isDisabled"
|
|
||||||
class="q-mr-sm q-px-sm"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
push rounded
|
|
||||||
icon="date_range"
|
|
||||||
color="primary"
|
|
||||||
@click="is_showing_calendar_picker = true"
|
|
||||||
:disable="props.isDisabled"
|
|
||||||
class="q-px-lg"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
push rounded
|
|
||||||
icon="keyboard_arrow_right"
|
|
||||||
color="primary"
|
|
||||||
@click="emit('pressed-next-button')"
|
|
||||||
:disable="props.isDisabled"
|
|
||||||
class="q-ml-sm q-px-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-dialog v-model="is_showing_calendar_picker" transition-show="jump-down" transition-hide="jump-up" position="top">
|
|
||||||
<q-date
|
|
||||||
v-model="calendar_date"
|
|
||||||
color="primary"
|
|
||||||
class="q-mt-xl"
|
|
||||||
today-btn
|
|
||||||
mask="YYYY-MM-DD"
|
|
||||||
:options="date => date > '2023/12/16'"
|
|
||||||
@update:model-value="onDateSelected"
|
|
||||||
/>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
searchModel: string | number | null
|
|
||||||
}>();
|
|
||||||
const emit = defineEmits<{
|
|
||||||
onSearchValueUpdated: [value: string | number | null]
|
|
||||||
}>();
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- Filters toggle -->
|
|
||||||
<q-btn-dropdown
|
|
||||||
rounded
|
|
||||||
push
|
|
||||||
class="q-mr-md bg-white text-primary"
|
|
||||||
label="filters"
|
|
||||||
icon="filter_alt"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Search bar -->
|
|
||||||
<q-input
|
|
||||||
outlined
|
|
||||||
dense
|
|
||||||
rounded
|
|
||||||
debounce="300"
|
|
||||||
:label="$t('shared.label.search')"
|
|
||||||
label-color="primary"
|
|
||||||
bg-color="white"
|
|
||||||
color="primary"
|
|
||||||
:model-value="props.searchModel"
|
|
||||||
@update:model-value="value => emit('onSearchValueUpdated', value)"
|
|
||||||
>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon
|
|
||||||
name="search"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
|
|
@ -5,4 +5,13 @@ export interface PayPeriod {
|
||||||
payday: string;
|
payday: string;
|
||||||
pay_year: number;
|
pay_year: number;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const default_pay_period: PayPeriod = {
|
||||||
|
pay_period_no: -1,
|
||||||
|
period_start: '',
|
||||||
|
period_end: '',
|
||||||
|
payday: '',
|
||||||
|
pay_year: -1,
|
||||||
|
label: ''
|
||||||
|
};
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
export interface QDateDetails {
|
|
||||||
year: number;
|
|
||||||
month: number;
|
|
||||||
day: number;
|
|
||||||
from?: {
|
|
||||||
year: number;
|
|
||||||
month: number;
|
|
||||||
day: number;
|
|
||||||
};
|
|
||||||
to?: {
|
|
||||||
year: number;
|
|
||||||
month: number;
|
|
||||||
day: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartData, type ChartDataset } from 'chart.js';
|
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartData, type ChartDataset } from 'chart.js';
|
||||||
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-details-interface';
|
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
|
||||||
import type { Expense } from 'src/modules/timesheets/types/timesheet-details-interface';
|
import type { Expense } from 'src/modules/timesheets/types/timesheet-details-interface';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, type ChartData, type ChartOptions, type Plugin, type ChartDataset } from 'chart.js';
|
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, type ChartData, type ChartOptions, type Plugin, type ChartDataset } from 'chart.js';
|
||||||
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-details-interface';
|
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { Doughnut } from 'vue-chartjs';
|
import { Doughnut } from 'vue-chartjs';
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale, LinearScale, type ChartDataset } from 'chart.js';
|
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale, LinearScale, type ChartDataset } from 'chart.js';
|
||||||
import type { PayPeriodOverviewEmployee } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-employee-interface';
|
import type { PayPeriodEmployeeOverview } from 'src/modules/timesheet-approval/types/pay-period-employee-overview';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
ChartJS.defaults.color = $q.dark.isActive ? '#F5F5F5' : '#616161';
|
ChartJS.defaults.color = $q.dark.isActive ? '#F5F5F5' : '#616161';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
rawData: PayPeriodOverviewEmployee | undefined;
|
rawData: PayPeriodEmployeeOverview | undefined;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const shift_type_labels = ref<string[]>([]);
|
const shift_type_labels = ref<string[]>([]);
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
|
import type { Shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
shift: Shift;
|
shift: Shift;
|
||||||
|
|
@ -1,27 +1,29 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TimesheetApprovalEmployeeDetailsShiftsRow from 'src/modules/timesheet-approval/components/timesheet-approval-employee-details-shifts-row.vue';
|
import DetailedShiftListRow from 'src/modules/timesheet-approval/components/detailed-shift-list-row.vue';
|
||||||
import TimesheetApprovalEmployeeDetailsShiftsRowHeader from 'src/modules/timesheet-approval/components/timesheet-approval-employee-details-shifts-row-header.vue';
|
import DetailedShiftListHeader from 'src/modules/timesheet-approval/components/detailed-shift-list-header.vue';
|
||||||
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
||||||
import { default_shift, type Shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
|
import { default_shift, type Shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
|
||||||
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-details-interface';
|
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
|
||||||
|
|
||||||
const props = defineProps<{
|
const { rawData, currentPayPeriod } = defineProps<{
|
||||||
rawData: PayPeriodEmployeeDetails;
|
rawData: PayPeriodEmployeeDetails;
|
||||||
currentPayPeriod: PayPeriod;
|
currentPayPeriod: PayPeriod;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const weeks = [ rawData.week1, rawData.week2 ];
|
||||||
|
|
||||||
const shifts_or_placeholder = (shifts: Shift[]): Shift[] => {
|
const shifts_or_placeholder = (shifts: Shift[]): Shift[] => {
|
||||||
return shifts.length > 0 ? shifts : [default_shift];
|
return shifts.length > 0 ? shifts : [default_shift];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDate = (shift_date: string): Date => {
|
const getDate = (shift_date: string): Date => {
|
||||||
return new Date(props.currentPayPeriod.pay_year.toString() + '/' + shift_date);
|
return new Date(currentPayPeriod.pay_year.toString() + '/' + shift_date);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-for="week, index in props.rawData"
|
v-for="week, index in weeks"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="q-px-xs q-pt-xs rounded-5 col"
|
class="q-px-xs q-pt-xs rounded-5 col"
|
||||||
>
|
>
|
||||||
|
|
@ -40,20 +42,27 @@
|
||||||
<q-item-label
|
<q-item-label
|
||||||
style="font-size: 0.7em;"
|
style="font-size: 0.7em;"
|
||||||
class="text-uppercase"
|
class="text-uppercase"
|
||||||
>{{ $d(getDate(day.short_date), {weekday: $q.screen.lt.md ? 'short' : 'long'}) }}</q-item-label>
|
>
|
||||||
|
{{ $d(getDate(day.short_date), {weekday: $q.screen.lt.md ? 'short' : 'long'}) }}
|
||||||
|
</q-item-label>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
class="text-weight-bolder"
|
class="text-weight-bolder"
|
||||||
style="font-size: 2.5em; line-height: 90% !important;"
|
style="font-size: 2.5em; line-height: 90% !important;"
|
||||||
>{{ day.short_date.split('/')[1] }}</q-item-label>
|
>
|
||||||
|
{{ day.short_date.split('/')[1] }}
|
||||||
|
</q-item-label>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
style="font-size: 0.7em;"
|
style="font-size: 0.7em;"
|
||||||
class="text-uppercase"
|
class="text-uppercase"
|
||||||
>{{ $d(getDate(day.short_date), {month: $q.screen.lt.md ? 'short' : 'long'}) }}</q-item-label>
|
>
|
||||||
|
{{ $d(getDate(day.short_date), {month: $q.screen.lt.md ? 'short' : 'long'}) }}
|
||||||
|
</q-item-label>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section class="col q-pa-none">
|
<q-card-section class="col q-pa-none">
|
||||||
<TimesheetApprovalEmployeeDetailsShiftsRowHeader />
|
<DetailedShiftListHeader />
|
||||||
<TimesheetApprovalEmployeeDetailsShiftsRow
|
<DetailedShiftListRow
|
||||||
v-for="shift, shift_index in shifts_or_placeholder(day.shifts)"
|
v-for="shift, shift_index in shifts_or_placeholder(day.shifts)"
|
||||||
:key="shift_index"
|
:key="shift_index"
|
||||||
:shift="shift"
|
:shift="shift"
|
||||||
123
src/modules/timesheet-approval/components/overview-list-item.vue
Normal file
123
src/modules/timesheet-approval/components/overview-list-item.vue
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PayPeriodEmployeeOverview } from 'src/modules/timesheet-approval/types/pay-period-employee-overview';
|
||||||
|
|
||||||
|
const modelApproval = defineModel<boolean>();
|
||||||
|
const { row } = defineProps<{ row: PayPeriodEmployeeOverview; }>();
|
||||||
|
const emit = defineEmits<{ clickDetails: []; }>();
|
||||||
|
|
||||||
|
const stack_label_class = "text-weight-bold text-primary text-uppercase text-caption q-pa-none q-my-none ellipsis";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="q-px-sm q-pb-sm q-mt-sm col-xs-12 col-sm-6 col-md-4 col-lg-4 col-xl-3 grid-style-transition">
|
||||||
|
<q-card class="rounded-10">
|
||||||
|
|
||||||
|
<!-- Card header with employee name and details button-->
|
||||||
|
<q-card-section horizontal class="q-py-none q-px-sm q-ma-none justify-between items-center">
|
||||||
|
<span class="col text-primary text-h5 text-weight-bolder q-pt-xs"> {{ row.employee_name }} </span>
|
||||||
|
|
||||||
|
<!-- Buttons to view detailed shifts or view employee timesheet -->
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
square
|
||||||
|
unelevated
|
||||||
|
class="col-auto q-pa-none q-ma-none"
|
||||||
|
color="primary"
|
||||||
|
icon="work_history"
|
||||||
|
@click="emit('clickDetails')"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
self="center middle"
|
||||||
|
class="bg-primary text-uppercase text-weight-bold"
|
||||||
|
>
|
||||||
|
{{ $t('timesheet_approvals.tooltip.button_detailed_view') }}
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-separator size="2px" />
|
||||||
|
|
||||||
|
<!-- Main body of pay period card -->
|
||||||
|
<q-card-section class="q-py-none q-px-sm q-mt-sm q-mb-md">
|
||||||
|
<div class="row no-wrap">
|
||||||
|
|
||||||
|
<!-- left portion of pay period card -->
|
||||||
|
<div class="col column no-wrap q-px-sm">
|
||||||
|
|
||||||
|
<!-- Regular hours segment -->
|
||||||
|
<div class="column" :class="$q.screen.lt.md ? 'col' : 'col-8'">
|
||||||
|
<span :class="stack_label_class"> {{ $t('shared.shift_type.regular') }} </span>
|
||||||
|
<span class="text-weight-bolder text-h3 q-py-none"> {{ row.regular_hours }} </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-separator class="q-mx-sm" />
|
||||||
|
|
||||||
|
<!-- Other hour types segment -->
|
||||||
|
<div class="row q-px-xs">
|
||||||
|
<div class="col column no-wrap">
|
||||||
|
<span :class="stack_label_class" style="font-size: 0.65em;"> {{ $t('shared.shift_type.evening') }} </span>
|
||||||
|
<span class="text-weight-bolder text-h6 q-pa-none" style="line-height: 0.9em;"> {{ row.evening_hours }} </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col column no-wrap">
|
||||||
|
<span :class="stack_label_class" style="font-size: 0.65em;"> {{ $t('shared.shift_type.emergency') }} </span>
|
||||||
|
<span class="text-weight-bolder text-h6 q-pa-none" style="line-height: 0.9em;"> {{ row.emergency_hours }} </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col column no-wrap">
|
||||||
|
<span :class="stack_label_class" style="font-size: 0.65em;"> {{ $t('shared.shift_type.overtime') }} </span>
|
||||||
|
<span class="text-weight-bolder text-h6 q-pa-none" style="line-height: 0.9em;"> {{ row.overtime_hours }} </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-separator
|
||||||
|
vertical
|
||||||
|
class="q-mt-xs q-mb-none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Right portion of pay period card -->
|
||||||
|
<div class="col-auto column q-px-sm">
|
||||||
|
<div class="col column no-wrap">
|
||||||
|
<span :class="stack_label_class" style="font-size: 0.8em;"> {{ $t('timesheet.expense.types.EXPENSES') }} </span>
|
||||||
|
<span class="text-weight-bolder text-h6 q-pa-none" style="line-height: 0.9em;"> {{ row.expenses }} </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col column no-wrap">
|
||||||
|
<span :class="stack_label_class" style="font-size: 0.8em;"> {{ $t('timesheet.expense.types.MILEAGE') }} </span>
|
||||||
|
<span class="text-weight-bolder text-h6 q-pa-none" style="line-height: 0.9em;"> {{ row.mileage }} </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-separator color="primary" size="2px" />
|
||||||
|
|
||||||
|
<!-- Validate Pay Period section -->
|
||||||
|
<q-card-section
|
||||||
|
horizontal
|
||||||
|
class="justify-between items-center text-weight-bold q-px-sm"
|
||||||
|
:class="row.is_approved ? 'text-white bg-primary' : 'bg-dark'"
|
||||||
|
>
|
||||||
|
<div class="col-auto">
|
||||||
|
<span class="text-uppercase text-h6 q-ml-sm text-weight-bolder"> {{ row.total_hours }} </span>
|
||||||
|
<span class="text-uppercase text-weight-bold text-caption q-ml-xs"> total </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-checkbox
|
||||||
|
v-model="modelApproval"
|
||||||
|
dense
|
||||||
|
left-label
|
||||||
|
size="lg"
|
||||||
|
checked-icon="lock"
|
||||||
|
unchecked-icon="lock_open"
|
||||||
|
:color="row.is_approved ? 'white' : 'primary'"
|
||||||
|
:label="row.is_approved ? $t('timesheet_approvals.table.verified') : $t('timesheet_approvals.table.unverified')"
|
||||||
|
class="col-auto text-uppercase"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
72
src/modules/timesheet-approval/components/overview-list.vue
Normal file
72
src/modules/timesheet-approval/components/overview-list.vue
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
import OverviewListItem from 'src/modules/timesheet-approval/components/employee-overview/overview-list-item.vue';
|
||||||
|
import QTableFilters from 'src/modules/shared/components/utils/q-table-filters.vue';
|
||||||
|
import { pay_period_employee_overview_columns, type PayPeriodEmployeeOverview } from 'src/modules/timesheet-approval/types/pay-period-employee-overview';
|
||||||
|
|
||||||
|
const timesheet_store = useTimesheetStore();
|
||||||
|
|
||||||
|
// const FORWARD = 1
|
||||||
|
// const BACKWARD = -1
|
||||||
|
const filter = ref<string | number | null>('');
|
||||||
|
|
||||||
|
const onClickedDetails = async ( employee_email: string ) => {
|
||||||
|
await timesheet_store.getPayPeriodEmployeeDetailsByEmployeeEmail(employee_email);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<q-table
|
||||||
|
:rows="timesheet_store.pay_period_employee_overview_list"
|
||||||
|
:columns="pay_period_employee_overview_columns"
|
||||||
|
row-key="email"
|
||||||
|
:filter="filter"
|
||||||
|
grid
|
||||||
|
dense
|
||||||
|
hide-pagination
|
||||||
|
color="primary"
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
card-container-class="justify-center"
|
||||||
|
:loading="timesheet_store.is_loading"
|
||||||
|
:no-data-label="$t('shared.error.no_data_found')"
|
||||||
|
:no-results-label="$t('shared.error.no_search_results')"
|
||||||
|
:loading-label="$t('shared.label.loading')"
|
||||||
|
>
|
||||||
|
<template #top>
|
||||||
|
<div class="full-width" :class="$q.screen.lt.md ? 'text-center q-gutter-sm' : 'row'">
|
||||||
|
<!-- Calendar Picker goes here -->
|
||||||
|
|
||||||
|
<q-space />
|
||||||
|
|
||||||
|
<!-- Grid-or-List toggle goes here -->
|
||||||
|
|
||||||
|
<QTableFilters v-model="filter"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Template for individual employee cards -->
|
||||||
|
<template #item="props: { row: PayPeriodEmployeeOverview, key: string }">
|
||||||
|
<OverviewListItem
|
||||||
|
v-model="props.row.is_approved"
|
||||||
|
:row="props.row"
|
||||||
|
@click-details="onClickedDetails(props.row.email)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Template for custome failed-to-load state -->
|
||||||
|
<template #no-data="{ message, filter }">
|
||||||
|
<div class="full-width column items-center text-primary q-gutter-sm">
|
||||||
|
<span class="text-h6 q-mt-xl">
|
||||||
|
{{ message }}
|
||||||
|
</span>
|
||||||
|
<q-icon
|
||||||
|
size="4em"
|
||||||
|
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { default_pay_period_report_filters, type PayPeriodReportFilters } from 'src/modules/timesheet-approval/types/pay-period-report-options';
|
||||||
|
|
||||||
|
const report_filter_options = ref<PayPeriodReportFilters>(default_pay_period_report_filters);
|
||||||
|
|
||||||
|
const company_options = [
|
||||||
|
{ label: 'Targo', value: report_filter_options.value.companies.targo },
|
||||||
|
{ label: 'Solucom', value: report_filter_options.value.companies.solucom },
|
||||||
|
];
|
||||||
|
|
||||||
|
const type_options = [
|
||||||
|
{ label: 'timesheet_approvals.print_report.shifts', value: report_filter_options.value.types.shifts },
|
||||||
|
{ label: 'timesheet_approvals.print_report.expenses', value: report_filter_options.value.types.expenses },
|
||||||
|
{ label: 'shared.shift_type.holiday', value: report_filter_options.value.types.holiday },
|
||||||
|
{ label: 'shared.shift_type.vacation', value: report_filter_options.value.types.vacation },
|
||||||
|
];
|
||||||
|
|
||||||
|
const is_download_button_disabled = computed(() => {
|
||||||
|
return company_options.map( option => option.value ).filter( value => value === true ).length > 0 ||
|
||||||
|
type_options.map( option => option.value ).filter( value => value === true ).length > 0;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-btn-group rounded push>
|
||||||
|
<q-btn
|
||||||
|
rounded
|
||||||
|
push
|
||||||
|
color="primary"
|
||||||
|
icon="print"
|
||||||
|
:disable="is_download_button_disabled"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn-dropdown
|
||||||
|
rounded
|
||||||
|
push
|
||||||
|
color="white"
|
||||||
|
text-color="primary"
|
||||||
|
icon="checklist"
|
||||||
|
>
|
||||||
|
<q-list class="row">
|
||||||
|
<q-item class="col">
|
||||||
|
<q-item-label class="text-weight-bolder text-primary q-ma-none q-pa-none text-uppercase">
|
||||||
|
{{$t('timesheet_approvals.print_report.company')}}
|
||||||
|
</q-item-label>
|
||||||
|
|
||||||
|
<q-item-section row no-wrap>
|
||||||
|
<q-checkbox
|
||||||
|
v-for="option, index in company_options"
|
||||||
|
:key="index"
|
||||||
|
v-model="option.value"
|
||||||
|
:val="option.label"
|
||||||
|
:label="option.label"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator
|
||||||
|
spaced
|
||||||
|
vertical
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-item class="col">
|
||||||
|
<q-item-section row no-wrap>
|
||||||
|
<p class="text-weight-bolder text-primary q-ma-none q-pa-none text-uppercase">{{$t('timesheet_approvals.print_report.type')}}</p>
|
||||||
|
<q-checkbox
|
||||||
|
v-for="option, index in type_options"
|
||||||
|
:key="index"
|
||||||
|
v-model="option.value"
|
||||||
|
:val="option.label"
|
||||||
|
:label="option.label"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
</q-btn-group>
|
||||||
|
</template>
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import type { PayPeriodOverviewEmployee } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-employee-interface';
|
|
||||||
|
|
||||||
type TableColumn = {
|
|
||||||
name: string;
|
|
||||||
label: string;
|
|
||||||
value: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { cols, row, initialState } = defineProps<{
|
|
||||||
cols: TableColumn[];
|
|
||||||
row: PayPeriodOverviewEmployee;
|
|
||||||
initialState: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
clickDetails: [ email: string ];
|
|
||||||
updateApproval: [ value: boolean ];
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="q-px-sm q-pb-sm q-mt-sm col-xs-12 col-sm-6 col-md-4 col-lg-4 col-xl-3 grid-style-transition">
|
|
||||||
<q-card class="rounded-10">
|
|
||||||
<!-- Card header with employee name and details button-->
|
|
||||||
<q-card-section
|
|
||||||
horizontal
|
|
||||||
class="q-py-none q-pl-md relative"
|
|
||||||
>
|
|
||||||
<div class="text-primary text-h5 text-weight-bolder q-pt-xs overflow-hidden">
|
|
||||||
{{ row.employee_name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-space />
|
|
||||||
|
|
||||||
<!-- Buttons to view detailed shifts or view employee timesheet -->
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
square
|
|
||||||
unelevated
|
|
||||||
class="q-py-none q-my-xs"
|
|
||||||
color="primary"
|
|
||||||
icon="work_history"
|
|
||||||
@click="emit('clickDetails', row.email)"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
anchor="top middle"
|
|
||||||
self="center middle"
|
|
||||||
class="bg-primary text-uppercase text-weight-bold"
|
|
||||||
>
|
|
||||||
{{ $t('timesheet_approvals.tooltip.button_detailed_view') }}
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-separator size="2px" />
|
|
||||||
|
|
||||||
<!-- Main body of pay period card -->
|
|
||||||
<q-card-section class="q-pa-none q-mt-xs q-mb-sm">
|
|
||||||
<div class="row no-wrap">
|
|
||||||
<!-- left portion of pay period card -->
|
|
||||||
<div
|
|
||||||
class="column no-wrap"
|
|
||||||
:class="$q.screen.lt.md ? 'col' : 'col-8'"
|
|
||||||
>
|
|
||||||
<!-- Regular hours segment -->
|
|
||||||
<q-item
|
|
||||||
dense
|
|
||||||
class="column"
|
|
||||||
:class="$q.screen.lt.md ? 'col' : 'col-8'"
|
|
||||||
>
|
|
||||||
<q-item-label class="text-weight-bold text-primary q-pa-none text-uppercase text-caption">
|
|
||||||
{{ cols.find(c => c.name === 'regular_hours')?.label }}
|
|
||||||
</q-item-label>
|
|
||||||
<q-item-label class="text-weight-bolder text-h3 q-py-none">
|
|
||||||
{{ cols.find(c => c.name === 'regular_hours')?.value }}
|
|
||||||
</q-item-label>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-separator class="q-mx-sm" />
|
|
||||||
|
|
||||||
<!-- Other hour types segment -->
|
|
||||||
<div :class="$q.screen.lt.md ? 'column' : 'row no-wrap'">
|
|
||||||
<q-item
|
|
||||||
dense
|
|
||||||
class="column ellipsis "
|
|
||||||
v-for="col in cols.slice(3, 6)"
|
|
||||||
:key="col.label"
|
|
||||||
>
|
|
||||||
<q-item-label
|
|
||||||
class="text-weight-bold text-primary q-pa-none text-uppercase text-caption"
|
|
||||||
style="font-size: 0.65em;"
|
|
||||||
>
|
|
||||||
{{ col.label }}
|
|
||||||
</q-item-label>
|
|
||||||
<q-item-label class="text-weight-bolder q-pa-none text-h6 ">
|
|
||||||
{{ col.value }}
|
|
||||||
</q-item-label>
|
|
||||||
</q-item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-separator
|
|
||||||
vertical
|
|
||||||
class="q-mt-xs q-mb-none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Right portion of pay period card -->
|
|
||||||
<div class="no-wrap ellipsis col">
|
|
||||||
<q-item
|
|
||||||
dense
|
|
||||||
class="column"
|
|
||||||
v-for="col in cols.slice(6, )"
|
|
||||||
:key="col.label"
|
|
||||||
>
|
|
||||||
<q-item-label
|
|
||||||
class="text-weight-bold text-primary q-pa-none text-uppercase text-caption ellipsis"
|
|
||||||
style="font-size: 0.8em;"
|
|
||||||
>
|
|
||||||
{{ col.label }}
|
|
||||||
</q-item-label>
|
|
||||||
<q-item-label class="text-weight-bolder q-pa-none text-h6 ">
|
|
||||||
{{ col.value }}
|
|
||||||
</q-item-label>
|
|
||||||
</q-item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-separator
|
|
||||||
color="primary"
|
|
||||||
style="height: 2px;"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Validate entire Pay Period section -->
|
|
||||||
<q-card-section
|
|
||||||
horizontal
|
|
||||||
class="q-pa-sm text-weight-bold"
|
|
||||||
:class="initialState ? 'text-white bg-primary' : 'bg-dark'"
|
|
||||||
>
|
|
||||||
<q-item-label class="text-uppercase text-h6 q-ml-sm text-weight-bolder"> {{ row.total_hours + ' h' }} </q-item-label>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold q-ml-xs"> total </q-item-label>
|
|
||||||
<q-space />
|
|
||||||
<q-checkbox
|
|
||||||
dense
|
|
||||||
left-label
|
|
||||||
size="lg"
|
|
||||||
checked-icon="lock"
|
|
||||||
unchecked-icon="lock_open"
|
|
||||||
:color="initialState ? 'white' : 'primary'" keep-color
|
|
||||||
:model-value="initialState"
|
|
||||||
@update:model-value="val => emit('updateApproval', val)"
|
|
||||||
:label="initialState ? $t('timesheet_approvals.table.verified') : $t('timesheet_approvals.table.unverified')"
|
|
||||||
class="text-uppercase"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
/* eslint-disable */
|
|
||||||
import { computed, onMounted, ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { date, type QTableColumn } from 'quasar';
|
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
|
||||||
import { useTimesheetApprovalApi } from '../composables/use-timesheet-approval-api';
|
|
||||||
import PayPeriodCalendarPicker from 'src/modules/shared/components/utils/pay-period-calendar-picker.vue';
|
|
||||||
import TimesheetApprovalEmployeeOverviewListItem from './timesheet-approval-employee-overview-list-item.vue';
|
|
||||||
import TimesheetApprovalEmployeeDetails from 'src/modules/timesheet-approval/pages/timesheet-approval-employee-details.vue';
|
|
||||||
import { type PayPeriodOverviewEmployee } from '../types/timesheet-approval-pay-period-overview-employee-interface';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const timesheet_store = useTimesheetStore();
|
|
||||||
const timesheet_approval_api = useTimesheetApprovalApi();
|
|
||||||
const FORWARD = 1
|
|
||||||
const BACKWARD = -1
|
|
||||||
const filter = ref<string | number | null>('');
|
|
||||||
const original_approvals = ref<Record<string, boolean>>({});
|
|
||||||
const is_showing_details = ref<boolean>(false);
|
|
||||||
const report_filter_company = ref<boolean[]>([true, true]);
|
|
||||||
const report_filter_type = ref<boolean[]>([true, true, true, true]);
|
|
||||||
const clicked_employee_name = ref<string>('');
|
|
||||||
const clicked_employee_email = ref<string>('');
|
|
||||||
const update_key = ref<number>(0);
|
|
||||||
const columns = computed((): QTableColumn<PayPeriodOverviewEmployee>[] => [
|
|
||||||
{
|
|
||||||
name: 'employee_name',
|
|
||||||
label: t('timesheet_approvals.table.full_name'),
|
|
||||||
field: 'employee_name',
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'email',
|
|
||||||
label: t('timesheet_approvals.table.email'),
|
|
||||||
field: 'email',
|
|
||||||
sortable: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'regular_hours',
|
|
||||||
label: t('shared.shift_type.regular'),
|
|
||||||
field: 'regular_hours',
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'evening_hours',
|
|
||||||
label: t('shared.shift_type.evening'),
|
|
||||||
field: 'evening_hours'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'emergency_hours',
|
|
||||||
label: t('shared.shift_type.emergency'),
|
|
||||||
field: 'emergency_hours'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'overtime_hours',
|
|
||||||
label: t('shared.shift_type.overtime'),
|
|
||||||
field: 'overtime_hours'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'expenses',
|
|
||||||
label: t('timesheet_approvals.table.expenses'),
|
|
||||||
field: 'expenses',
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mileage',
|
|
||||||
label: t('timesheet_approvals.table.mileage'),
|
|
||||||
field: 'mileage',
|
|
||||||
sortable: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
// const has_changes = computed(() => {
|
|
||||||
// return timesheet_store.pay_period_overview_employees.some(emp => {
|
|
||||||
// return emp.is_approved !== original_approvals.value[emp.email];
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
const is_not_enough_filters = computed(() => {
|
|
||||||
return report_filter_company.value.filter(val => val === true).length < 1 ||
|
|
||||||
report_filter_type.value.filter(val => val === true).length < 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const filter_types_labels = [
|
|
||||||
t('timesheet_approvals.print_report.shifts'),
|
|
||||||
t('timesheet_approvals.print_report.expenses'),
|
|
||||||
t('shared.shift_type.holiday'),
|
|
||||||
t('shared.shift_type.vacation'),
|
|
||||||
];
|
|
||||||
|
|
||||||
const is_calendar_limit = computed( () => {
|
|
||||||
return timesheet_store.current_pay_period.pay_year === 2024 &&
|
|
||||||
timesheet_store.current_pay_period.pay_period_no <= 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getEmployeeApprovalStatusReference = (email: string): boolean => {
|
|
||||||
const approval_status = timesheet_store.pay_period_overview_employee_approval_statuses?.find( status => status.key === email);
|
|
||||||
if (approval_status) {
|
|
||||||
return approval_status.value;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateEmployeeApprovalStatus = (email: string, value: boolean) => {
|
|
||||||
const approval_status = timesheet_store.pay_period_overview_employee_approval_statuses?.find( status => status.key === email);
|
|
||||||
if (approval_status) {
|
|
||||||
approval_status.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getEmployeeOverview = (email: string): PayPeriodOverviewEmployee => {
|
|
||||||
return timesheet_approval_api.getPayPeriodOverviewByEmployeeEmail(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDateSelected = async (date_string: string) => {
|
|
||||||
await timesheet_approval_api.getPayPeriodOverviewByDate(date_string);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickedDetails = async (email: string, name: string) => {
|
|
||||||
clicked_employee_name.value = name;
|
|
||||||
clicked_employee_email.value = email;
|
|
||||||
is_showing_details.value = true;
|
|
||||||
|
|
||||||
await timesheet_approval_api.getTimesheetsByPayPeriodAndEmail(email);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickPrintReport = async () => {
|
|
||||||
await timesheet_approval_api.getTimesheetApprovalCSVReport(report_filter_company.value, report_filter_type.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted( async () => {
|
|
||||||
const today = date.formatDate(new Date(), 'YYYY-MM-DD');
|
|
||||||
await timesheet_approval_api.getPayPeriodOverviewByDate(today);
|
|
||||||
|
|
||||||
const approvals = timesheet_store.pay_period_overview_employees.map(emp => [emp.email, emp.is_approved]);
|
|
||||||
original_approvals.value = Object.fromEntries(approvals);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-dialog
|
|
||||||
v-model="is_showing_details"
|
|
||||||
transition-show="jump-down"
|
|
||||||
transition-hide="jump-down"
|
|
||||||
@before-show="() => update_key += 1"
|
|
||||||
full-width
|
|
||||||
:full-height="$q.screen.gt.sm"
|
|
||||||
>
|
|
||||||
<TimesheetApprovalEmployeeDetails
|
|
||||||
:is-loading="timesheet_store.is_loading"
|
|
||||||
:employee-name="clicked_employee_name"
|
|
||||||
:employee-overview="getEmployeeOverview(clicked_employee_email)"
|
|
||||||
:employee-details="timesheet_store.pay_period_employee_details"
|
|
||||||
:current-pay-period="timesheet_store.current_pay_period"
|
|
||||||
:update-key="update_key"
|
|
||||||
/>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<q-table
|
|
||||||
:rows="timesheet_store.pay_period_overview_employees"
|
|
||||||
:columns="columns"
|
|
||||||
row-key="email"
|
|
||||||
:filter="filter"
|
|
||||||
grid
|
|
||||||
dense
|
|
||||||
hide-pagination
|
|
||||||
color="primary"
|
|
||||||
:rows-per-page-options="[0]"
|
|
||||||
card-container-class="justify-center"
|
|
||||||
:loading="timesheet_store.is_loading"
|
|
||||||
:no-data-label="$t('shared.error.no_data_found')"
|
|
||||||
:no-results-label="$t('shared.error.no_search_results')"
|
|
||||||
:loading-label="$t('shared.label.loading')"
|
|
||||||
>
|
|
||||||
<!-- Top Bar that contains Date Picker, Search, Filters, Print Report, etc -->
|
|
||||||
<template #top>
|
|
||||||
<div class="full-width" :class="$q.screen.lt.md ? 'text-center q-gutter-sm' : 'row'">
|
|
||||||
<!-- Date Picker -->
|
|
||||||
<PayPeriodCalendarPicker
|
|
||||||
:is-disabled="timesheet_store.is_loading"
|
|
||||||
:is-previous-limit="is_calendar_limit"
|
|
||||||
@date-selected="onDateSelected"
|
|
||||||
@pressed-previous-button="timesheet_approval_api.getNextPayPeriodOverview(BACKWARD)"
|
|
||||||
@pressed-next-button="timesheet_approval_api.getNextPayPeriodOverview(FORWARD)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-space />
|
|
||||||
|
|
||||||
<q-btn-group rounded push>
|
|
||||||
<q-btn
|
|
||||||
rounded
|
|
||||||
push
|
|
||||||
color="primary"
|
|
||||||
icon="print"
|
|
||||||
:disable="is_not_enough_filters"
|
|
||||||
@click="onClickPrintReport"
|
|
||||||
/>
|
|
||||||
<q-btn-dropdown
|
|
||||||
rounded
|
|
||||||
push
|
|
||||||
color="white"
|
|
||||||
text-color="primary"
|
|
||||||
icon="checklist"
|
|
||||||
>
|
|
||||||
<q-list>
|
|
||||||
<q-item>
|
|
||||||
<q-item-section row no-wrap>
|
|
||||||
<p class="text-weight-bolder text-primary q-ma-none q-pa-none text-uppercase">{{$t('timesheet_approvals.print_report.company')}}</p>
|
|
||||||
<q-checkbox
|
|
||||||
v-for="label, index in ['Targo', 'Solucom']"
|
|
||||||
v-model="report_filter_company[index]"
|
|
||||||
:val="label"
|
|
||||||
:label=label
|
|
||||||
:key="index"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator color="primary" class="q-mx-md"/>
|
|
||||||
<q-item>
|
|
||||||
<q-item-section row no-wrap>
|
|
||||||
<p class="text-weight-bolder text-primary q-ma-none q-pa-none text-uppercase">{{$t('timesheet_approvals.print_report.type')}}</p>
|
|
||||||
<q-checkbox
|
|
||||||
v-for="label, index in filter_types_labels"
|
|
||||||
v-model="report_filter_type[index]"
|
|
||||||
:val="label"
|
|
||||||
:label="label"
|
|
||||||
:key="index"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-btn-dropdown>
|
|
||||||
</q-btn-group>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Template for individual employee cards -->
|
|
||||||
<template #item="props: {
|
|
||||||
cols: (QTableColumn<PayPeriodOverviewEmployee> & { value: unknown })[],
|
|
||||||
row: PayPeriodOverviewEmployee,
|
|
||||||
key: string,
|
|
||||||
}">
|
|
||||||
<TimesheetApprovalEmployeeOverviewListItem
|
|
||||||
:cols="props.cols"
|
|
||||||
:row="props.row"
|
|
||||||
:initial-state="getEmployeeApprovalStatusReference(props.key)"
|
|
||||||
@click-details="email => onClickedDetails(email, props.row.employee_name)"
|
|
||||||
@update-approval="value => updateEmployeeApprovalStatus(props.key, value)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Template for custome failed-to-load state -->
|
|
||||||
<template #no-data="{ message, filter }">
|
|
||||||
<div class="full-width column items-center text-primary q-gutter-sm">
|
|
||||||
<span class="text-h6 q-mt-xl">
|
|
||||||
{{ message }}
|
|
||||||
</span>
|
|
||||||
<q-icon
|
|
||||||
size="4em"
|
|
||||||
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,36 +1,39 @@
|
||||||
import { useTimesheetStore } from "src/stores/timesheet-store";
|
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||||
import { useAuthStore } from "src/stores/auth-store";
|
import { useAuthStore } from "src/stores/auth-store";
|
||||||
import type { PayPeriodReportFilters } from "../types/timesheet-approval-pay-period-report-interface";
|
import type { PayPeriodReportFilters } from "src/modules/timesheet-approval/types/pay-period-report";
|
||||||
import { default_pay_period_overview_employee, type PayPeriodOverviewEmployee } from "../types/timesheet-approval-pay-period-overview-employee-interface";
|
import { default_pay_period_employee_overview, type PayPeriodEmployeeOverview } from "src/modules/timesheet-approval/types/pay-period-employee-overview";
|
||||||
import { date } from "quasar";
|
import { date } from "quasar";
|
||||||
|
|
||||||
export const useTimesheetApprovalApi = () => {
|
export const useTimesheetApprovalApi = () => {
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
|
|
||||||
const getPayPeriodOverviewByDate = async (date_string: string) => {
|
const getPayPeriodOverviewByDate = async (date_string: string): Promise<void> => {
|
||||||
const success = await timesheet_store.getPayPeriodByDate(date_string);
|
const success = await timesheet_store.getPayPeriodByDateOrYearAndNumber(date_string);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
const current_pay_period = timesheet_store.current_pay_period;
|
await timesheet_store.getPayPeriodEmployeeOverviewListBySupervisorEmail(
|
||||||
await timesheet_store.getTimesheetApprovalPayPeriodEmployeeOverviews(current_pay_period.pay_year, current_pay_period.pay_period_no, auth_store.user.email);
|
timesheet_store.pay_period.pay_year,
|
||||||
|
timesheet_store.pay_period.pay_period_no,
|
||||||
|
auth_store.user.email
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getPayPeriodOverviewByEmployeeEmail = (email: string): PayPeriodOverviewEmployee => {
|
const getPayPeriodOverviewByEmployeeEmail = (email: string): void => {
|
||||||
const employee_overview = timesheet_store.pay_period_overview_employees.find(overview => overview.email === email);
|
const employee_overview = timesheet_store.getPayPeriodOverviewByEmployeeEmail(email);
|
||||||
if (employee_overview !== undefined) return employee_overview;
|
|
||||||
return default_pay_period_overview_employee;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This method attempts to get the next or previous pay period.
|
/* This method attempts to get the next or previous pay period.
|
||||||
It checks if pay period number is within a certain range, adjusts pay period and year accordingly.
|
It checks if pay period number is within a certain range, adjusts pay period and year accordingly.
|
||||||
It then requests the matching pay period object to set as current pay period from server.
|
It then requests the matching pay period object to set as current pay period from server.
|
||||||
If successful, it then requests pay period overviews from that new pay period. */
|
If successful, it then requests pay period overviews from that new pay period, using either the current user or
|
||||||
const getNextPayPeriodOverview = async (direction: number) => {
|
any other supervisor email provided. */
|
||||||
const current_pay_period = timesheet_store.current_pay_period;
|
const getNextOrPreviousPayPeriodOverviewList = async (direction: number, supervisor_email?: string): Promise<void> => {
|
||||||
let new_pay_period_no = current_pay_period.pay_period_no + direction;
|
const email = supervisor_email ?? auth_store.user.email;
|
||||||
let new_pay_year = current_pay_period.pay_year;
|
|
||||||
|
let new_pay_period_no = timesheet_store.pay_period.pay_period_no + direction;
|
||||||
|
let new_pay_year = timesheet_store.pay_period.pay_year;
|
||||||
|
|
||||||
if (new_pay_period_no > 26) {
|
if (new_pay_period_no > 26) {
|
||||||
new_pay_period_no = 1;
|
new_pay_period_no = 1;
|
||||||
|
|
@ -42,17 +45,13 @@ export const useTimesheetApprovalApi = () => {
|
||||||
new_pay_year -= 1;
|
new_pay_year -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await timesheet_store.getPayPeriodByYearAndPeriodNumber(new_pay_year, new_pay_period_no);
|
const success = await timesheet_store.getPayPeriodByDateOrYearAndNumber(new_pay_year, new_pay_period_no);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await timesheet_store.getTimesheetApprovalPayPeriodEmployeeOverviews(new_pay_year, new_pay_period_no, auth_store.user.email);
|
await timesheet_store.getPayPeriodEmployeeOverviewListBySupervisorEmail(new_pay_year, new_pay_period_no, email);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTimesheetsByPayPeriodAndEmail = async (employee_email: string) => {
|
|
||||||
await timesheet_store.getTimesheetsByPayPeriodAndEmail(employee_email);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTimesheetApprovalCSVReport = async ( report_filter_company: boolean[], report_filter_type: boolean[] ) => {
|
const getTimesheetApprovalCSVReport = async ( report_filter_company: boolean[], report_filter_type: boolean[] ) => {
|
||||||
const [ targo, solucom ] = report_filter_company;
|
const [ targo, solucom ] = report_filter_company;
|
||||||
const [ shifts, expenses, holiday, vacation ] = report_filter_type;
|
const [ shifts, expenses, holiday, vacation ] = report_filter_type;
|
||||||
|
|
@ -64,29 +63,10 @@ export const useTimesheetApprovalApi = () => {
|
||||||
await timesheet_store.getTimesheetApprovalCSVReport(options);
|
await timesheet_store.getTimesheetApprovalCSVReport(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCurrentPayPerdioOverview = async (): Promise<void> => {
|
|
||||||
const today = date.formatDate(new Date(), 'YYYY-MM-DD');
|
|
||||||
const success = await timesheet_store.getPayPeriodByDate(today);
|
|
||||||
if(!success) return;
|
|
||||||
|
|
||||||
const { pay_year, pay_period_no } = timesheet_store.current_pay_period;
|
|
||||||
|
|
||||||
await timesheet_store.getTimesheetApprovalPayPeriodEmployeeOverviews(
|
|
||||||
pay_year,
|
|
||||||
pay_period_no,
|
|
||||||
auth_store.user.email
|
|
||||||
);
|
|
||||||
|
|
||||||
await timesheet_store.getTimesheetsByPayPeriodAndEmail(auth_store.user.email);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getPayPeriodOverviewByDate,
|
getPayPeriodOverviewByDate,
|
||||||
getNextPayPeriodOverview,
|
getNextOrPreviousPayPeriodOverviewList,
|
||||||
getPayPeriodOverviewByEmployeeEmail,
|
getPayPeriodOverviewByEmployeeEmail,
|
||||||
getTimesheetsByPayPeriodAndEmail,
|
|
||||||
getTimesheetApprovalCSVReport,
|
getTimesheetApprovalCSVReport,
|
||||||
getCurrentPayPerdioOverview
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import DetailedShiftList from 'src/modules/timesheet-approval/components/detailed-shift-list.vue';
|
||||||
|
import DetailedChartHoursWorked from 'src/modules/timesheet-approval/components/graphs/detailed-chart-hours-worked.vue';
|
||||||
|
import DetailedChartShiftTypes from 'src/modules/timesheet-approval/components/graphs/detailed-chart-shift-types.vue';
|
||||||
|
import DetailedChartExpenses from 'src/modules/timesheet-approval/components/graphs/detailed-chart-expenses.vue';
|
||||||
|
import type { PayPeriodEmployeeOverview } from 'src/modules/timesheet-approval/types/pay-period-employee-overview';
|
||||||
|
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
|
||||||
|
import { shift_type_legend } from 'src/modules/timesheet-approval/types/detailed-shift-color';
|
||||||
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
|
||||||
|
const dialog_model = defineModel<boolean>('dialog', { default: false });
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
isLoading: boolean;
|
||||||
|
employeeOverview: PayPeriodEmployeeOverview;
|
||||||
|
employeeDetails: PayPeriodEmployeeDetails;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const timesheet_store = useTimesheetStore();
|
||||||
|
const is_showing_graph = ref<boolean>(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-dialog
|
||||||
|
v-model="dialog_model"
|
||||||
|
full-width
|
||||||
|
transition-show="jump-down"
|
||||||
|
transition-hide="jump-down"
|
||||||
|
>
|
||||||
|
<q-card
|
||||||
|
class="q-pa-sm shadow-12 rounded-15 column no-wrap relative"
|
||||||
|
:style="$q.screen.lt.md ? '' : 'width: 60vw !important;'"
|
||||||
|
>
|
||||||
|
<!-- loader -->
|
||||||
|
<q-card-section
|
||||||
|
v-if="isLoading"
|
||||||
|
class="column flex-center text-center"
|
||||||
|
>
|
||||||
|
<q-spinner
|
||||||
|
color="primary"
|
||||||
|
size="5em"
|
||||||
|
:thickness="10"
|
||||||
|
class="col-auto"
|
||||||
|
/>
|
||||||
|
<div class="col-auto text-primary text-h6 text-weight-bold text-center ">
|
||||||
|
{{ $t('shared.loading') }}
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<!-- employee name -->
|
||||||
|
<q-card-section
|
||||||
|
v-if="!isLoading"
|
||||||
|
class="text-h5 text-weight-bolder text-center text-primary q-pa-none text-uppercase col-auto"
|
||||||
|
>
|
||||||
|
<span> {{ employeeDetails.employee_full_name }} </span>
|
||||||
|
|
||||||
|
<q-separator
|
||||||
|
spaced
|
||||||
|
size="2px"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-card-actions
|
||||||
|
align="center"
|
||||||
|
class="q-pa-none"
|
||||||
|
>
|
||||||
|
<q-btn-toggle
|
||||||
|
v-model="is_showing_graph"
|
||||||
|
color="white"
|
||||||
|
text-color="primary"
|
||||||
|
toggle-color="primary"
|
||||||
|
:options="[
|
||||||
|
{ icon: 'bar_chart', value: true },
|
||||||
|
{ icon: 'edit', value: false },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<!-- employee timesheet details edit -->
|
||||||
|
<q-card-section
|
||||||
|
v-if="!is_showing_graph"
|
||||||
|
class="q-pa-none"
|
||||||
|
>
|
||||||
|
<!-- shift type color legend -->
|
||||||
|
<q-card-section class="q-py-xs q-px-none text-center q-my-s">
|
||||||
|
<q-badge
|
||||||
|
v-for="shift_type, index in shift_type_legend"
|
||||||
|
:key="index"
|
||||||
|
:color="shift_type.background_color"
|
||||||
|
:label="$t(shift_type.type_label)"
|
||||||
|
:text-color="shift_type.font_color"
|
||||||
|
class="q-px-md q-py-xs q-mx-xs q-my-none text-uppercase text-weight-bolder justify-center"
|
||||||
|
style="width: 120px; font-size: 0.8em;"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<!-- list of shifts, broken down into weekly columns -->
|
||||||
|
<q-card-section
|
||||||
|
:horizontal="$q.screen.gt.sm"
|
||||||
|
class="q-pa-none bg-secondary rounded-10"
|
||||||
|
>
|
||||||
|
<DetailedShiftList
|
||||||
|
:raw-data="employeeDetails"
|
||||||
|
:current-pay-period="timesheet_store.pay_period"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<!-- employee timesheet details with chart -->
|
||||||
|
<q-card-section
|
||||||
|
v-if="is_showing_graph"
|
||||||
|
class="q-pa-md col column full-width no-wrap"
|
||||||
|
>
|
||||||
|
<q-card-section
|
||||||
|
:horizontal="!$q.screen.lt.md"
|
||||||
|
class="q-pa-none col no-wrap"
|
||||||
|
style="min-height: 300px;"
|
||||||
|
>
|
||||||
|
<DetailedChartHoursWorked
|
||||||
|
:raw-data="employeeDetails"
|
||||||
|
class="col-7"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-separator
|
||||||
|
spaced
|
||||||
|
:vertical="!$q.screen.lt.md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="column col justify-center no-wrap q-pa-none">
|
||||||
|
<DetailedChartShiftTypes
|
||||||
|
:raw-data="employeeOverview"
|
||||||
|
class="col-5"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-separator
|
||||||
|
spaced
|
||||||
|
:vertical="!$q.screen.lt.md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DetailedChartExpenses
|
||||||
|
:raw-data="employeeDetails"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
/*eslint-disable*/
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import TimesheetApprovalEmployeeDetailsShifts from 'src/modules/timesheet-approval/components/timesheet-approval-employee-details-shifts.vue';
|
|
||||||
import TimesheetApprovalEmployeeDetailsHoursWorkedChart from 'src/modules/timesheet-approval/components/graphs/timesheet-approval-employee-details-hours-worked-chart.vue';
|
|
||||||
import TimesheetApprovalEmployeeDetailsShiftTypesChart from 'src/modules/timesheet-approval/components/graphs/timesheet-approval-employee-details-shift-types-chart.vue';
|
|
||||||
import TimesheetApprovalEmployeeExpensesChart from 'src/modules/timesheet-approval/components/graphs/timesheet-approval-employee-expenses-chart.vue';
|
|
||||||
import type { PayPeriodOverviewEmployee } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-employee-interface';
|
|
||||||
import type { PayPeriodEmployeeDetails } from '../types/timesheet-approval-pay-period-employee-details-interface';
|
|
||||||
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
isLoading: boolean;
|
|
||||||
employeeName: string;
|
|
||||||
employeeOverview: PayPeriodOverviewEmployee;
|
|
||||||
employeeDetails: PayPeriodEmployeeDetails;
|
|
||||||
currentPayPeriod: PayPeriod;
|
|
||||||
updateKey: number;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const is_showing_graph = ref<boolean>(true);
|
|
||||||
|
|
||||||
type shiftColor = {
|
|
||||||
type: string;
|
|
||||||
color: string;
|
|
||||||
text_color?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const shift_type_legend: shiftColor[] = [
|
|
||||||
{
|
|
||||||
type: t('shared.shift_type.regular'),
|
|
||||||
color: 'secondary',
|
|
||||||
text_color: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: t('shared.shift_type.evening'),
|
|
||||||
color: 'warning',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: t('shared.shift_type.emergency'),
|
|
||||||
color: 'amber-10',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: t('shared.shift_type.overtime'),
|
|
||||||
color: 'negative',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: t('shared.shift_type.vacation'),
|
|
||||||
color: 'purple-10',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: t('shared.shift_type.holiday'),
|
|
||||||
color: 'purple-8',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: t('shared.shift_type.sick'),
|
|
||||||
color: 'grey-8',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-card
|
|
||||||
class="q-pa-sm shadow-12 rounded-15 column no-wrap relative"
|
|
||||||
:style="$q.screen.lt.md ? '' : 'width: 60vw !important; height: 70vh !important;' "
|
|
||||||
>
|
|
||||||
<!-- loader -->
|
|
||||||
<q-card-section
|
|
||||||
v-if="props.isLoading"
|
|
||||||
class="absolute-center text-center"
|
|
||||||
>
|
|
||||||
<q-spinner
|
|
||||||
color="primary"
|
|
||||||
size="5em"
|
|
||||||
:thickness="10"
|
|
||||||
/>
|
|
||||||
<div class="text-primary text-h6 text-weight-bold">
|
|
||||||
{{ $t('shared.loading') }}
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<!-- employee name -->
|
|
||||||
<q-card-section
|
|
||||||
v-if="!props.isLoading"
|
|
||||||
class="text-h5 text-weight-bolder text-center text-primary q-pa-none text-uppercase col-auto"
|
|
||||||
>
|
|
||||||
{{ props.employeeName }}
|
|
||||||
|
|
||||||
<q-separator spaced size="2px" />
|
|
||||||
<q-card-actions align="center" class="q-pa-none">
|
|
||||||
<q-card flat class="bg-secondary rounded-5 q-pa-xs">
|
|
||||||
<q-btn-toggle
|
|
||||||
color="white"
|
|
||||||
text-color="primary"
|
|
||||||
toggle-color="primary"
|
|
||||||
v-model="is_showing_graph"
|
|
||||||
:options="[
|
|
||||||
{icon: 'bar_chart', value: true},
|
|
||||||
{icon: 'edit', value: false},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</q-card>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<!-- employee timesheet details edit -->
|
|
||||||
<q-card-section
|
|
||||||
v-if="!props.isLoading && !is_showing_graph"
|
|
||||||
class="q-pa-none"
|
|
||||||
>
|
|
||||||
<!-- shift type color legend -->
|
|
||||||
<q-card-section class="q-py-xs q-px-none text-center q-my-s">
|
|
||||||
<q-badge
|
|
||||||
v-for="shift_type in shift_type_legend"
|
|
||||||
:color="shift_type.color"
|
|
||||||
:label="shift_type.type"
|
|
||||||
:text-color="shift_type.text_color || 'white'"
|
|
||||||
class="q-px-md q-py-xs q-mx-xs q-my-none text-uppercase text-weight-bolder justify-center"
|
|
||||||
style="width: 120px; font-size: 0.8em;"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<!-- list of shifts, broken down into weekly columns -->
|
|
||||||
<q-card-section
|
|
||||||
:horizontal="$q.screen.gt.sm"
|
|
||||||
class="q-pa-none bg-secondary rounded-10"
|
|
||||||
>
|
|
||||||
<TimesheetApprovalEmployeeDetailsShifts
|
|
||||||
:raw-data="props.employeeDetails"
|
|
||||||
:current-pay-period="props.currentPayPeriod"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<!-- employee timesheet details with graphs -->
|
|
||||||
<q-card-section v-if="!props.isLoading && is_showing_graph" class="q-pa-md col column full-width no-wrap">
|
|
||||||
<q-card-section :horizontal="!$q.screen.lt.md" class="q-pa-none col no-wrap" style="min-height: 300px;">
|
|
||||||
<TimesheetApprovalEmployeeDetailsHoursWorkedChart
|
|
||||||
:raw-data="props.employeeDetails"
|
|
||||||
class="col-7"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-separator vertical spaced />
|
|
||||||
|
|
||||||
<div class="column col justify-center no-wrap q-pa-none">
|
|
||||||
<TimesheetApprovalEmployeeDetailsShiftTypesChart
|
|
||||||
:raw-data="props.employeeOverview"
|
|
||||||
class="col-5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-separator :vertical="$q.screen.lt.md" spaced />
|
|
||||||
|
|
||||||
<TimesheetApprovalEmployeeExpensesChart
|
|
||||||
:raw-data="props.employeeDetails"
|
|
||||||
class="col"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,23 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TimesheetApprovalEmployeeOverviewList from '../components/timesheet-approval-employee-overview-list.vue';
|
import { onMounted } from 'vue';
|
||||||
import { computed } from 'vue';
|
import { date } from 'quasar';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
import EmployeeOverviewList from 'src/modules/timesheet-approval/components/employee-overview/overview-list.vue';
|
||||||
|
import TimesheetApprovalDetailed from 'src/modules/timesheet-approval/pages/timesheet-approval-detailed.vue';
|
||||||
|
import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue';
|
||||||
|
|
||||||
const { d } = useI18n();
|
const timesheet_approval_api = useTimesheetApprovalApi();
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
|
|
||||||
const pay_period_label = computed(() => {
|
onMounted( async () => {
|
||||||
const dates = timesheet_store.current_pay_period.label.split('.');
|
await timesheet_approval_api.getPayPeriodOverviewByDate(date.formatDate( new Date(), 'YYYY-MM-DD'));
|
||||||
|
|
||||||
if ( dates.length < 2 ) {
|
|
||||||
return { start_date: '—', end_date: '—' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const start_date = d(new Date(dates[0] as string), { day: 'numeric', month: 'long', year: 'numeric', });
|
|
||||||
const end_date = d(new Date(dates[1] as string), { day: 'numeric', month: 'long', year: 'numeric', });
|
|
||||||
|
|
||||||
return { start_date, end_date };
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -26,21 +20,18 @@
|
||||||
padding
|
padding
|
||||||
class="q-pa-md bg-secondary "
|
class="q-pa-md bg-secondary "
|
||||||
>
|
>
|
||||||
<div class="column q-mt-lg text-uppercase text-center text-weight-bolder text-h4">
|
<PageHeaderTemplate
|
||||||
{{ $t('timesheet_approvals.page_title') }}
|
title="timesheet_approvals.page_title"
|
||||||
<div class="col row items-center justify-center full-width q-py-none q-my-none">
|
:start-date="timesheet_store.pay_period.period_start"
|
||||||
<div class="text-primary text-weight-bold text-h6">
|
:end-date="timesheet_store.pay_period.period_end"
|
||||||
{{ pay_period_label.start_date }}
|
/>
|
||||||
</div>
|
|
||||||
<div class="text-body2 q-mx-md text-weight-medium">
|
<TimesheetApprovalDetailed
|
||||||
{{ $t('shared.misc.to') }}
|
:is-loading="timesheet_store.is_loading"
|
||||||
</div>
|
:employee-overview="timesheet_store.pay_period_employee_overview"
|
||||||
<div class="text-primary text-weight-bold text-h6">
|
:employee-details="timesheet_store.pay_period_employee_details"
|
||||||
{{ pay_period_label.end_date }}
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TimesheetApprovalEmployeeOverviewList />
|
<EmployeeOverviewList />
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { api } from "src/boot/axios";
|
import { api } from "src/boot/axios";
|
||||||
import type { PayPeriodOverview } from "../types/timesheet-approval-pay-period-overview-interface";
|
import type { PayPeriodOverview } from "src/modules/timesheet-approval/types/pay-period-overview";
|
||||||
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
||||||
import type { PayPeriodEmployeeDetails } from "../types/timesheet-approval-pay-period-employee-details-interface";
|
import type { PayPeriodEmployeeDetails } from "src/modules/timesheet-approval/types/pay-period-employee-details";
|
||||||
import type { PayPeriodReportFilters } from "../types/timesheet-approval-pay-period-report-interface";
|
import type { PayPeriodReportFilters } from "src/modules/timesheet-approval/types/pay-period-report";
|
||||||
|
|
||||||
export const timesheetApprovalService = {
|
export const timesheetApprovalService = {
|
||||||
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
|
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
|
||||||
|
|
@ -16,13 +16,13 @@ export const timesheetApprovalService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
getPayPeriodEmployeeOverviews: async (year: number, period_number: number, supervisor_email: string): Promise<PayPeriodOverview> => {
|
getPayPeriodEmployeeOverviewListBySupervisorEmail: async (year: number, period_number: number, supervisor_email: string): Promise<PayPeriodOverview> => {
|
||||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||||
const response = await api.get(`pay-periods/${year}/${period_number}/${supervisor_email}`);
|
const response = await api.get(`pay-periods/${year}/${period_number}/${supervisor_email}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
getTimesheetsByPayPeriodAndEmail: async (year: number, period_no: number, email: string): Promise<PayPeriodEmployeeDetails> => {
|
getPayPeriodEmployeeDetailsByPayPeriodAndEmail: async (year: number, period_no: number, email: string): Promise<PayPeriodEmployeeDetails> => {
|
||||||
const response = await api.get('timesheets', { params: { year, period_no, email, }});
|
const response = await api.get('timesheets', { params: { year, period_no, email, }});
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
@ -1,287 +0,0 @@
|
||||||
// import type { PayPeriod } from "../shared/types/pay-period-interface";
|
|
||||||
// import type { PayPeriodEmployeeOverview } from "./types/timesheet-approval-pay-period-employee-overview-interface"
|
|
||||||
|
|
||||||
// export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-001',
|
|
||||||
// "employee_name": 'Alice Johnson',
|
|
||||||
// "regular_hours": 75,
|
|
||||||
// "evening_hours": 12,
|
|
||||||
// "emergency_hours": 3,
|
|
||||||
// "overtime_hours": 5,
|
|
||||||
// "expenses": 120.50,
|
|
||||||
// "mileage": 45,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-002',
|
|
||||||
// "employee_name": 'Brian Smith',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 8,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 2,
|
|
||||||
// "expenses": 75.00,
|
|
||||||
// "mileage": 12,
|
|
||||||
// "is_approved": true
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-003',
|
|
||||||
// "employee_name": 'Chloe Ramirez',
|
|
||||||
// "regular_hours": 68,
|
|
||||||
// "evening_hours": 15,
|
|
||||||
// "emergency_hours": 1,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 200.00,
|
|
||||||
// "mileage": 88,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-004',
|
|
||||||
// "employee_name": 'David Lee',
|
|
||||||
// "regular_hours": 82,
|
|
||||||
// "evening_hours": 5,
|
|
||||||
// "emergency_hours": 4,
|
|
||||||
// "overtime_hours": 6,
|
|
||||||
// "expenses": 50.75,
|
|
||||||
// "mileage": 20,
|
|
||||||
// "is_approved": true
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-005',
|
|
||||||
// "employee_name": 'Emily Carter',
|
|
||||||
// "regular_hours": 78,
|
|
||||||
// "evening_hours": 10,
|
|
||||||
// "emergency_hours": 2,
|
|
||||||
// "overtime_hours": 3,
|
|
||||||
// "expenses": 95.25,
|
|
||||||
// "mileage": 60,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-006',
|
|
||||||
// "employee_name": 'Maxime Murray Gendron',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-007',
|
|
||||||
// "employee_name": 'Marc-André Henrico',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-008',
|
|
||||||
// "employee_name": 'Jessy Sharock',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-009',
|
|
||||||
// "employee_name": 'David Richer',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-010',
|
|
||||||
// "employee_name": 'Nicolas Drolet',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-011',
|
|
||||||
// "employee_name": 'Frederick Pruneau',
|
|
||||||
// "regular_hours": 16,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-012',
|
|
||||||
// "employee_name": 'Matthieu Haineault Gervais',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-013',
|
|
||||||
// "employee_name": 'Robinson Viaud',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-014',
|
|
||||||
// "employee_name": 'Geneviève Bourdon',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-015',
|
|
||||||
// "employee_name": 'Frédérique Soulard',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-016',
|
|
||||||
// "employee_name": 'Patrick Doucet',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-017',
|
|
||||||
// "employee_name": 'Dahlia Tremblay',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-018',
|
|
||||||
// "employee_name": 'Louis Morneau',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "email": 'EMP-019',
|
|
||||||
// "employee_name": 'Michel Blais',
|
|
||||||
// "regular_hours": 80,
|
|
||||||
// "evening_hours": 0,
|
|
||||||
// "emergency_hours": 0,
|
|
||||||
// "overtime_hours": 0,
|
|
||||||
// "expenses": 0,
|
|
||||||
// "mileage": 0,
|
|
||||||
// "is_approved": false
|
|
||||||
// }
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// export const mock_pay_periods: PayPeriod[] = [
|
|
||||||
// {
|
|
||||||
// "period_number": 15,
|
|
||||||
// "start_date": "2025-07-27",
|
|
||||||
// "end_date": "2025-08-09",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-07-27 → 2025-08-09"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 14,
|
|
||||||
// "start_date": "2025-07-13",
|
|
||||||
// "end_date": "2025-07-26",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-07-13 → 2025-07-26"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 13,
|
|
||||||
// "start_date": "2025-06-29",
|
|
||||||
// "end_date": "2025-07-12",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-06-29 → 2025-07-12"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 12,
|
|
||||||
// "start_date": "2025-06-15",
|
|
||||||
// "end_date": "2025-06-28",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-06-15 → 2025-06-28"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 11,
|
|
||||||
// "start_date": "2025-06-01",
|
|
||||||
// "end_date": "2025-06-14",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-06-01 → 2025-06-14"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 10,
|
|
||||||
// "start_date": "2025-05-18",
|
|
||||||
// "end_date": "2025-05-31",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-05-18 → 2025-05-31"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 9,
|
|
||||||
// "start_date": "2025-05-04",
|
|
||||||
// "end_date": "2025-05-17",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-05-04 → 2025-05-17"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 8,
|
|
||||||
// "start_date": "2025-04-20",
|
|
||||||
// "end_date": "2025-05-03",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-04-20 → 2025-05-03"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 7,
|
|
||||||
// "start_date": "2025-04-06",
|
|
||||||
// "end_date": "2025-04-19",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-04-06 → 2025-04-19"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "period_number": 6,
|
|
||||||
// "start_date": "2025-03-23",
|
|
||||||
// "end_date": "2025-04-05",
|
|
||||||
// "year": 2025,
|
|
||||||
// "label": "2025-03-23 → 2025-04-05"
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
43
src/modules/timesheet-approval/types/detailed-shift-color.ts
Normal file
43
src/modules/timesheet-approval/types/detailed-shift-color.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
export interface shiftColor {
|
||||||
|
type_label: string;
|
||||||
|
background_color: string;
|
||||||
|
font_color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const shift_type_legend: shiftColor[] = [
|
||||||
|
{
|
||||||
|
type_label: 'shared.shift_type.regular',
|
||||||
|
background_color: 'blue-grey-4',
|
||||||
|
font_color: 'blue-grey-8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_label: 'shared.shift_type.evening',
|
||||||
|
background_color: 'warning',
|
||||||
|
font_color: 'blue-grey-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_label: 'shared.shift_type.emergency',
|
||||||
|
background_color: 'amber-10',
|
||||||
|
font_color: 'blue-grey-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_label: 'shared.shift_type.overtime',
|
||||||
|
background_color: 'negative',
|
||||||
|
font_color: 'blue-grey-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_label: 'shared.shift_type.vacation',
|
||||||
|
background_color: 'purple-10',
|
||||||
|
font_color: 'blue-grey-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_label: 'shared.shift_type.holiday',
|
||||||
|
background_color: 'purple-8',
|
||||||
|
font_color: 'blue-grey-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_label: 'shared.shift_type.sick',
|
||||||
|
background_color: 'grey-8',
|
||||||
|
font_color: 'blue-grey-2',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -3,9 +3,11 @@ import { default_timesheet_details_week, type TimesheetDetailsWeek } from "src/m
|
||||||
export interface PayPeriodEmployeeDetails {
|
export interface PayPeriodEmployeeDetails {
|
||||||
week1: TimesheetDetailsWeek;
|
week1: TimesheetDetailsWeek;
|
||||||
week2: TimesheetDetailsWeek;
|
week2: TimesheetDetailsWeek;
|
||||||
|
employee_full_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const default_pay_period_employee_details = {
|
export const default_pay_period_employee_details = {
|
||||||
week1: default_timesheet_details_week(),
|
week1: default_timesheet_details_week(),
|
||||||
week2: default_timesheet_details_week(),
|
week2: default_timesheet_details_week(),
|
||||||
|
employee_full_name: "",
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
export interface PayPeriodEmployeeOverview {
|
||||||
|
email: string;
|
||||||
|
employee_name: string;
|
||||||
|
regular_hours: number;
|
||||||
|
evening_hours: number;
|
||||||
|
emergency_hours: number;
|
||||||
|
overtime_hours: number;
|
||||||
|
total_hours: number;
|
||||||
|
expenses: number;
|
||||||
|
mileage: number;
|
||||||
|
is_approved: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const default_pay_period_employee_overview: PayPeriodEmployeeOverview = {
|
||||||
|
email: '',
|
||||||
|
employee_name: '',
|
||||||
|
regular_hours: -1,
|
||||||
|
evening_hours: -1,
|
||||||
|
emergency_hours: -1,
|
||||||
|
overtime_hours: -1,
|
||||||
|
total_hours: -1,
|
||||||
|
expenses: -1,
|
||||||
|
mileage: -1,
|
||||||
|
is_approved: false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pay_period_employee_overview_columns = [
|
||||||
|
{
|
||||||
|
name: 'employee_name',
|
||||||
|
label: 'timesheet_approvals.table.full_name',
|
||||||
|
field: 'employee_name',
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
label: 'timesheet_approvals.table.email',
|
||||||
|
field: 'email',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'regular_hours',
|
||||||
|
label: 'shared.shift_type.regular',
|
||||||
|
field: 'regular_hours',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'evening_hours',
|
||||||
|
label: 'shared.shift_type.evening',
|
||||||
|
field: 'evening_hours',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emergency_hours',
|
||||||
|
label: 'shared.shift_type.emergency',
|
||||||
|
field: 'emergency_hours',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'overtime_hours',
|
||||||
|
label: 'shared.shift_type.overtime',
|
||||||
|
field: 'overtime_hours',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expenses',
|
||||||
|
label: 'timesheet_approvals.table.expenses',
|
||||||
|
field: 'expenses',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mileage',
|
||||||
|
label: 'timesheet_approvals.table.mileage',
|
||||||
|
field: 'mileage',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'is_approved',
|
||||||
|
label: 'timesheet_approvals.table.is_approved',
|
||||||
|
field: 'is_approved',
|
||||||
|
sortable: true,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { PayPeriodOverviewEmployee } from "./timesheet-approval-pay-period-overview-employee-interface";
|
import type { PayPeriodEmployeeOverview } from "./pay-period-employee-overview";
|
||||||
|
|
||||||
export interface PayPeriodOverview {
|
export interface PayPeriodOverview {
|
||||||
pay_period_no: number;
|
pay_period_no: number;
|
||||||
|
|
@ -7,5 +7,5 @@ export interface PayPeriodOverview {
|
||||||
period_start: string;
|
period_start: string;
|
||||||
period_end: string;
|
period_end: string;
|
||||||
label: string;
|
label: string;
|
||||||
employees_overview: PayPeriodOverviewEmployee[];
|
employees_overview: PayPeriodEmployeeOverview[];
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
export interface PayPeriodReportFilters {
|
||||||
|
types: {
|
||||||
|
shifts: boolean;
|
||||||
|
expenses: boolean;
|
||||||
|
holiday: boolean;
|
||||||
|
vacation: boolean;
|
||||||
|
};
|
||||||
|
companies: {
|
||||||
|
targo: boolean;
|
||||||
|
solucom: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const default_pay_period_report_filters: PayPeriodReportFilters = {
|
||||||
|
types: {
|
||||||
|
shifts: true,
|
||||||
|
expenses: true,
|
||||||
|
holiday: true,
|
||||||
|
vacation: true,
|
||||||
|
},
|
||||||
|
companies: {
|
||||||
|
targo: true,
|
||||||
|
solucom: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
export interface PayPeriodOverviewEmployee {
|
|
||||||
email: string;
|
|
||||||
employee_name: string;
|
|
||||||
regular_hours: number;
|
|
||||||
evening_hours: number;
|
|
||||||
emergency_hours: number;
|
|
||||||
overtime_hours: number;
|
|
||||||
total_hours: number;
|
|
||||||
expenses: number;
|
|
||||||
mileage: number;
|
|
||||||
is_approved: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const default_pay_period_overview_employee: PayPeriodOverviewEmployee = {
|
|
||||||
email: '',
|
|
||||||
employee_name: '',
|
|
||||||
regular_hours: -1,
|
|
||||||
evening_hours: -1,
|
|
||||||
emergency_hours: -1,
|
|
||||||
overtime_hours: -1,
|
|
||||||
total_hours: -1,
|
|
||||||
expenses: -1,
|
|
||||||
mileage: -1,
|
|
||||||
is_approved: false
|
|
||||||
}
|
|
||||||
|
|
@ -2,9 +2,9 @@ import { api } from "src/boot/axios";
|
||||||
import type {Timesheet} from "src/modules/timesheets/types/timesheet-interface";
|
import type {Timesheet} from "src/modules/timesheets/types/timesheet-interface";
|
||||||
import type { CreateShiftPayload, CreateWeekShiftPayload } from "../types/timesheet-shifts-payload-interface";
|
import type { CreateShiftPayload, CreateWeekShiftPayload } from "../types/timesheet-shifts-payload-interface";
|
||||||
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
||||||
import type { PayPeriodEmployeeDetails } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-details-interface";
|
import type { PayPeriodEmployeeDetails } from "src/modules/timesheet-approval/types/pay-period-employee-details";
|
||||||
import type { PayPeriodOverview } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-interface";
|
import type { PayPeriodOverview } from "src/modules/timesheet-approval/types/pay-period-overview";
|
||||||
import type { PayPeriodReportFilters } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-report-interface";
|
import type { PayPeriodReportFilters } from "src/modules/timesheet-approval/types/pay-period-report";
|
||||||
|
|
||||||
export const timesheetTempService = {
|
export const timesheetTempService = {
|
||||||
//GET
|
//GET
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,13 @@ type Expenses = {
|
||||||
comment: string;
|
comment: string;
|
||||||
supervisor_comment: string;
|
supervisor_comment: string;
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const default_timesheet: Timesheet = {
|
||||||
|
start_day: '',
|
||||||
|
end_day: '',
|
||||||
|
label: '',
|
||||||
|
is_approved: false,
|
||||||
|
shifts: [],
|
||||||
|
expenses: [],
|
||||||
|
};
|
||||||
|
|
@ -1,181 +1,166 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/services-timesheet-approval';
|
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
|
||||||
import { timesheetTempService } from 'src/modules/timesheets/services/timesheet-services';
|
import { timesheetTempService } from 'src/modules/timesheets/services/timesheet-services';
|
||||||
import { default_pay_period_employee_details, type PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-details-interface';
|
import { default_pay_period_employee_details, type PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
|
||||||
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
import { default_pay_period, type PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
||||||
import type { PayPeriodOverviewEmployee } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-employee-interface";
|
import { default_pay_period_employee_overview, type PayPeriodEmployeeOverview } from "src/modules/timesheet-approval/types/pay-period-employee-overview";
|
||||||
import type { PayPeriodReportFilters } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-report-interface';
|
import type { PayPeriodReportFilters } from 'src/modules/timesheet-approval/types/pay-period-report';
|
||||||
import type { Timesheet } from 'src/modules/timesheets/types/timesheet-interface';
|
import { default_timesheet, type Timesheet } from 'src/modules/timesheets/types/timesheet-interface';
|
||||||
import type { CreateShiftPayload } from 'src/modules/timesheets/types/timesheet-shifts-payload-interface';
|
import type { CreateShiftPayload } from 'src/modules/timesheets/types/timesheet-shifts-payload-interface';
|
||||||
|
import { withLoading } from 'src/utils/store-helpers';
|
||||||
const default_pay_period: PayPeriod = {
|
|
||||||
pay_period_no: -1,
|
|
||||||
period_start: '',
|
|
||||||
period_end: '',
|
|
||||||
payday: '',
|
|
||||||
pay_year: -1,
|
|
||||||
label: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
//employee timesheet
|
|
||||||
const default_timesheet: Timesheet = {
|
|
||||||
start_day: '',
|
|
||||||
end_day: '',
|
|
||||||
label: '',
|
|
||||||
is_approved: false,
|
|
||||||
shifts: [],
|
|
||||||
expenses: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTimesheetStore = defineStore('timesheet', () => {
|
export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
const is_loading = ref<boolean>(false);
|
const is_loading = ref<boolean>(false);
|
||||||
const current_pay_period = ref<PayPeriod>(default_pay_period);
|
const pay_period = ref<PayPeriod>(default_pay_period);
|
||||||
const pay_period_overview_employees = ref<PayPeriodOverviewEmployee[]>([]);
|
const pay_period_employee_overview_list = ref<PayPeriodEmployeeOverview[]>([]);
|
||||||
const pay_period_overview_employee_approval_statuses = ref<{key: string, value: boolean}[] | undefined>();
|
const pay_period_employee_overview = ref<PayPeriodEmployeeOverview>(default_pay_period_employee_overview);
|
||||||
const pay_period_employee_details = ref<PayPeriodEmployeeDetails>(default_pay_period_employee_details);
|
const pay_period_employee_details = ref<PayPeriodEmployeeDetails>(default_pay_period_employee_details);
|
||||||
const pay_period_report = ref();
|
const pay_period_report = ref();
|
||||||
|
const timesheet = ref<Timesheet>(default_timesheet);
|
||||||
|
|
||||||
//employee timesheet
|
const getPayPeriodByDateOrYearAndNumber = (date_or_year: string | number, period_number?: number): Promise<boolean> => {
|
||||||
const current_timesheet = ref<Timesheet>(default_timesheet);
|
return withLoading( is_loading, async () => {
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
|
||||||
const getPayPeriodByDate = async (date_string: string): Promise<boolean> => {
|
if (typeof date_or_year === 'string') {
|
||||||
is_loading.value = true;
|
response = await timesheetApprovalService.getPayPeriodByDate(date_or_year);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ( typeof date_or_year === 'number' && period_number ) {
|
||||||
|
response = await timesheetApprovalService.getPayPeriodByYearAndPeriodNumber(date_or_year, period_number);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else response = default_pay_period;
|
||||||
|
|
||||||
try {
|
pay_period.value = response;
|
||||||
const response = await timesheetApprovalService.getPayPeriodByDate(date_string);
|
return false;
|
||||||
current_pay_period.value = response;
|
} catch(error){
|
||||||
is_loading.value = false;
|
console.error('Could not get current pay period: ', error );
|
||||||
|
pay_period.value = default_pay_period;
|
||||||
|
pay_period_employee_overview_list.value = [];
|
||||||
|
//TODO: More in-depth error-handling here
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
} catch(error){
|
});
|
||||||
console.error('Could not get current pay period: ', error );
|
|
||||||
current_pay_period.value = default_pay_period;
|
|
||||||
pay_period_overview_employees.value = [];
|
|
||||||
//TODO: More in-depth error-handling here
|
|
||||||
}
|
|
||||||
|
|
||||||
is_loading.value = false;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPayPeriodByYearAndPeriodNumber = async (year: number, period_number: number): Promise<boolean> => {
|
const getPayPeriodEmployeeOverviewListBySupervisorEmail = async (pay_year: number, period_number: number, supervisor_email: string): Promise<boolean> => {
|
||||||
is_loading.value = true;
|
return withLoading( is_loading, async () => {
|
||||||
|
try {
|
||||||
|
const response = await timesheetApprovalService.getPayPeriodEmployeeOverviewListBySupervisorEmail( pay_year, period_number, supervisor_email );
|
||||||
|
pay_period_employee_overview_list.value = response.employees_overview;
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There was an error retrieving Employee Pay Period overviews: ', error);
|
||||||
|
pay_period_employee_overview_list.value = [];
|
||||||
|
// TODO: More in-depth error-handling here
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
return false;
|
||||||
const response = await timesheetApprovalService.getPayPeriodByYearAndPeriodNumber(year, period_number);
|
});
|
||||||
current_pay_period.value = response;
|
|
||||||
is_loading.value = false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch(error){
|
|
||||||
console.error('Could not get current pay period: ', error );
|
|
||||||
current_pay_period.value = default_pay_period;
|
|
||||||
pay_period_overview_employees.value = [];
|
|
||||||
//TODO: More in-depth error-handling here
|
|
||||||
}
|
|
||||||
|
|
||||||
is_loading.value = false;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTimesheetApprovalPayPeriodEmployeeOverviews = async (pay_year: number, period_number: number, supervisor_email: string) => {
|
const getPayPeriodOverviewByEmployeeEmail = (email: string): PayPeriodEmployeeOverview => {
|
||||||
is_loading.value = true;
|
const response = pay_period_employee_overview_list.value?.find( employee_overview => employee_overview.email === email);
|
||||||
|
if (typeof response === 'undefined') {
|
||||||
try {
|
pay_period_employee_overview.value = default_pay_period_employee_overview;
|
||||||
const response = await timesheetApprovalService.getPayPeriodEmployeeOverviews(
|
} else {
|
||||||
pay_year,
|
pay_period_employee_overview.value = response;
|
||||||
period_number,
|
|
||||||
supervisor_email
|
|
||||||
);
|
|
||||||
pay_period_overview_employees.value = response.employees_overview;
|
|
||||||
pay_period_overview_employee_approval_statuses.value = response.employees_overview.map( employee => ({ key: employee.email, value: employee.is_approved }) );
|
|
||||||
} catch (error) {
|
|
||||||
console.error('There was an error retrieving Employee Pay Period overviews: ', error);
|
|
||||||
pay_period_overview_employees.value = [];
|
|
||||||
// TODO: More in-depth error-handling here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is_loading.value = false;
|
return pay_period_employee_overview.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
//employee timesheet
|
//employee timesheet
|
||||||
const getTimesheetByEmail = async (employee_email: string) => {
|
const getTimesheetByEmail = async (employee_email: string) => {
|
||||||
is_loading.value = true;
|
return withLoading( is_loading, async () => {
|
||||||
try{
|
try{
|
||||||
const response = await timesheetTempService.getTimesheetsByEmail(employee_email);
|
const response = await timesheetTempService.getTimesheetsByEmail(employee_email);
|
||||||
current_timesheet.value = response;
|
timesheet.value = response;
|
||||||
}catch (error) {
|
|
||||||
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
return true;
|
||||||
current_timesheet.value = { ...default_timesheet }
|
}catch (error) {
|
||||||
} finally {
|
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
||||||
is_loading.value = false;
|
timesheet.value = { ...default_timesheet }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
//employee timesheet
|
return false;
|
||||||
const createTimesheetShifts = async (employee_email: string, shifts: CreateShiftPayload[], offset = 0) => {
|
});
|
||||||
is_loading.value = true;
|
|
||||||
try{
|
|
||||||
const timesheet = await timesheetTempService.createTimesheetShifts(employee_email, shifts, offset);
|
|
||||||
current_timesheet.value = timesheet;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('createTimesheetShifts error: ', err);
|
|
||||||
} finally {
|
|
||||||
is_loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTimesheetsByPayPeriodAndEmail = async (employee_email: string) => {
|
//employee timesheet
|
||||||
is_loading.value = true;
|
const createTimesheetShifts = async (employee_email: string, shifts: CreateShiftPayload[], offset = 0) => {
|
||||||
|
return withLoading( is_loading, async () => {
|
||||||
|
try{
|
||||||
|
const response = await timesheetTempService.createTimesheetShifts(employee_email, shifts, offset);
|
||||||
|
timesheet.value = response;
|
||||||
|
|
||||||
try {
|
return true;
|
||||||
const response = await timesheetApprovalService.getTimesheetsByPayPeriodAndEmail(
|
} catch (err) {
|
||||||
current_pay_period.value.pay_year,
|
console.error('createTimesheetShifts error: ', err);
|
||||||
current_pay_period.value.pay_period_no,
|
}
|
||||||
employee_email
|
|
||||||
);
|
|
||||||
pay_period_employee_details.value = response;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
|
||||||
// TODO: More in-depth error-handling here
|
|
||||||
}
|
|
||||||
|
|
||||||
is_loading.value = false;
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPayPeriodEmployeeDetailsByEmployeeEmail = async (employee_email: string) => {
|
||||||
|
return withLoading( is_loading, async () => {
|
||||||
|
try {
|
||||||
|
const response = await timesheetApprovalService.getPayPeriodEmployeeDetailsByPayPeriodAndEmail(
|
||||||
|
pay_period.value.pay_year,
|
||||||
|
pay_period.value.pay_period_no,
|
||||||
|
employee_email
|
||||||
|
);
|
||||||
|
pay_period_employee_details.value = response;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
||||||
|
// TODO: More in-depth error-handling here
|
||||||
|
}
|
||||||
|
|
||||||
|
pay_period_employee_details.value = default_pay_period_employee_details;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTimesheetApprovalCSVReport = async (report_filters?: PayPeriodReportFilters) => {
|
const getTimesheetApprovalCSVReport = async (report_filters?: PayPeriodReportFilters) => {
|
||||||
is_loading.value = true;
|
return withLoading( is_loading, async () => {
|
||||||
|
try {
|
||||||
|
const response = await timesheetApprovalService.getTimesheetApprovalCSVReport(
|
||||||
|
pay_period.value.pay_year,
|
||||||
|
pay_period.value.pay_period_no,
|
||||||
|
report_filters
|
||||||
|
);
|
||||||
|
pay_period_report.value = response;
|
||||||
|
|
||||||
try {
|
return true;
|
||||||
const response = await timesheetApprovalService.getTimesheetApprovalCSVReport(
|
} catch (error) {
|
||||||
current_pay_period.value.pay_year,
|
console.error('There was an error retrieving the report CSV: ', error);
|
||||||
current_pay_period.value.pay_period_no,
|
// TODO: More in-depth error-handling here
|
||||||
report_filters
|
}
|
||||||
);
|
|
||||||
pay_period_report.value = response;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('There was an error retrieving the report CSV: ', error);
|
|
||||||
// TODO: More in-depth error-handling here
|
|
||||||
}
|
|
||||||
|
|
||||||
is_loading.value = false;
|
return false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
current_pay_period,
|
pay_period,
|
||||||
pay_period_overview_employees,
|
pay_period_employee_overview_list,
|
||||||
pay_period_overview_employee_approval_statuses,
|
pay_period_employee_overview,
|
||||||
pay_period_employee_details,
|
pay_period_employee_details,
|
||||||
current_timesheet,
|
timesheet,
|
||||||
is_loading,
|
is_loading,
|
||||||
getPayPeriodByDate,
|
getPayPeriodByDateOrYearAndNumber,
|
||||||
getTimesheetByEmail,
|
getTimesheetByEmail,
|
||||||
createTimesheetShifts,
|
createTimesheetShifts,
|
||||||
getPayPeriodByYearAndPeriodNumber,
|
getPayPeriodEmployeeOverviewListBySupervisorEmail,
|
||||||
getTimesheetApprovalPayPeriodEmployeeOverviews,
|
getPayPeriodOverviewByEmployeeEmail,
|
||||||
getTimesheetsByPayPeriodAndEmail,
|
getPayPeriodEmployeeDetailsByEmployeeEmail,
|
||||||
getTimesheetApprovalCSVReport,
|
getTimesheetApprovalCSVReport,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
10
src/utils/store-helpers.ts
Normal file
10
src/utils/store-helpers.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import type { Ref } from "vue";
|
||||||
|
|
||||||
|
export const withLoading = async <T>( loading_state: Ref<boolean>, fn: () => Promise<T> ) => {
|
||||||
|
loading_state.value = true;
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} finally {
|
||||||
|
loading_state.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user