Merge pull request 'dev/nicolas/approvals-overview-details' (#7) from dev/nicolas/approvals-overview-details into main
Reviewed-on: Targo/targo_frontend#7
This commit is contained in:
commit
40e95e9238
|
|
@ -127,6 +127,8 @@ export default defineConfig((ctx) => {
|
|||
// animations: 'all', // --- includes all animations
|
||||
// https://v2.quasar.dev/options/animations
|
||||
animations: [
|
||||
'fadeIn',
|
||||
'fadeOut',
|
||||
'fadeInUp',
|
||||
'zoomIn',
|
||||
'zoomOut',
|
||||
|
|
|
|||
|
|
@ -297,6 +297,7 @@ export default {
|
|||
},
|
||||
timeSheetValidations: {
|
||||
tableColumnLabelFullname: 'Full name',
|
||||
tableColumnLabelEmail: 'email address',
|
||||
tableColumnLabelRegularHours: 'regular hours',
|
||||
tableColumnLabelEveningHours: 'evening',
|
||||
tableColumnLabelEmergencyHours: 'emergency',
|
||||
|
|
@ -326,10 +327,8 @@ export default {
|
|||
consumedVacationTotalValidation: 'Consumed with vacation must be positive.',
|
||||
maxVacationPerYearValidation: 'Max Vacation Per Year must be positive.',
|
||||
resteVacationTotal: 'Rest of vacation',
|
||||
validateToolTip: 'Validate',
|
||||
unvalidateToolTip: 'Unvalidate',
|
||||
lockToolTip: 'Lock the week',
|
||||
unlockToolTip: 'Unlock the week',
|
||||
tooltipTimeline: 'Daily breakdown',
|
||||
tooltipTimesheet: 'Open timesheet',
|
||||
},
|
||||
shiftColumns: {
|
||||
title: 'Shifts',
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ export default {
|
|||
},
|
||||
timeSheetValidations: {
|
||||
tableColumnLabelFullname: 'nom complet',
|
||||
tableColumnLabelEmail: 'courriel',
|
||||
tableColumnLabelRegularHours: 'heures régulières',
|
||||
tableColumnLabelEveningHours: 'soir',
|
||||
tableColumnLabelEmergencyHours: 'urgence',
|
||||
|
|
@ -373,10 +374,8 @@ export default {
|
|||
consumedVacationTotalValidation: 'Vacances utilisées doit être positif',
|
||||
maxVacationPerYearValidation: 'Maximum vacances annuel doit être positif.',
|
||||
resteVacationTotal: 'Reste des vacances',
|
||||
validateToolTip: 'Valider',
|
||||
unvalidateToolTip: 'Invalider',
|
||||
lockToolTip: 'Verrouiller la semaine',
|
||||
unlockToolTip: 'Déverrouiller la semaine',
|
||||
tooltipTimeline: 'Vue journalière',
|
||||
tooltipTimesheet: 'Feuille de temps',
|
||||
},
|
||||
usersListPage: {
|
||||
tableHeader: 'Répertoire du personnel',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export interface EmployeeProfile {
|
|||
company_name: number;
|
||||
job_title: string;
|
||||
email: string;
|
||||
phone_number: number;
|
||||
phone_number: string;
|
||||
first_work_day: string;
|
||||
last_work_day: string;
|
||||
residence: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import type { Shift } from "src/modules/timesheets/types/timesheet-shift-interface";
|
||||
import { date } from 'quasar';
|
||||
|
||||
const total_minutes = 24 * 60;
|
||||
const props = defineProps<{
|
||||
weekdayShifts: (Shift | null)[];
|
||||
}>();
|
||||
|
||||
const toMinutes = (time: string): number => {
|
||||
const parsed_time = date.extractDate(time, 'HH:mm');
|
||||
return parsed_time.getHours() * 60 + parsed_time.getMinutes();
|
||||
};
|
||||
|
||||
const bars = computed(() => {
|
||||
return props.weekdayShifts
|
||||
.filter((s): s is Shift => s !== null) // skip null shifts
|
||||
.map((s) => {
|
||||
const start = toMinutes(s.start);
|
||||
const end = toMinutes(s.end);
|
||||
const hover = ref<boolean>(false);
|
||||
const start_percent = (start / total_minutes) * 100;
|
||||
const width_percent = ((end - start) / total_minutes) * 100;
|
||||
|
||||
return { start_percent, width_percent, s, hover };
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative bg-grey-5 rounded-10 no-wrap" style="height: 4px; margin: 6px 0;">
|
||||
<div
|
||||
v-for="(bar, index) in bars"
|
||||
:key="index"
|
||||
class="absolute bg-primary no-wrap"
|
||||
:style="{
|
||||
left: bar.start_percent + '%',
|
||||
width: bar.width_percent + '%',
|
||||
height: '10px',
|
||||
transform: 'translateY(-3px)',
|
||||
}"
|
||||
@mouseenter="bar.hover.value = true"
|
||||
@mouseleave="bar.hover.value = false"
|
||||
>
|
||||
<!-- hoverable timestamps -->
|
||||
<transition-group
|
||||
appear
|
||||
enter-active-class="animated fadeIn"
|
||||
leave-active-class="animated fadeOut"
|
||||
>
|
||||
<q-badge
|
||||
v-if="bar.hover.value"
|
||||
style="transform: translate(-110%, -40%);"
|
||||
>
|
||||
{{ bar.s.start }}
|
||||
</q-badge>
|
||||
<q-badge
|
||||
v-if="bar.hover.value"
|
||||
floating style="transform: translate(100%, 5%);"
|
||||
>
|
||||
{{ bar.s.end }}
|
||||
</q-badge>
|
||||
</transition-group>
|
||||
|
||||
<!-- total hours worked per day -->
|
||||
<!-- <q-badge> {{ bar.s.end }}</q-badge> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- <script setup lang="ts">
|
||||
import type { Shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
|
||||
import { date } from 'quasar';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
weekdayShifts: Shift[];
|
||||
}>();
|
||||
|
||||
const extractTimeValue = (time: string): number => {
|
||||
const parsed_time = date.extractDate(time, 'HH:mm');
|
||||
return parsed_time.getHours() + (parsed_time.getMinutes() / 60);
|
||||
};
|
||||
|
||||
const slider_model = ref<number>(0);
|
||||
const shifts = ref<Shift[]>(props.weekdayShifts);
|
||||
const ranges = ref(
|
||||
shifts.value.map( shift =>
|
||||
shift
|
||||
? {
|
||||
min: extractTimeValue(shift.start_time),
|
||||
max: extractTimeValue(shift.end_time)
|
||||
}
|
||||
: {
|
||||
min: 0,
|
||||
max: 0
|
||||
}
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative-position q-pa-none q-ma-none col">
|
||||
<div class="absolute-full row justify-between">
|
||||
<div></div>
|
||||
<q-separator vertical color="accent" />
|
||||
<q-separator vertical color="accent" />
|
||||
<q-separator vertical color="accent" />
|
||||
<q-separator vertical color="accent" />
|
||||
<q-separator vertical color="accent" />
|
||||
<div></div>
|
||||
</div>
|
||||
<q-slider
|
||||
v-model="slider_model"
|
||||
track-color="grey-6"
|
||||
track-size="8px"
|
||||
thumb-size="0px"
|
||||
:max="24"
|
||||
readonly
|
||||
class="absolute-full q-pt-xs"
|
||||
/>
|
||||
<div
|
||||
v-for="(_range, index) in ranges"
|
||||
:key="index"
|
||||
class="absolute-full"
|
||||
>
|
||||
<q-range
|
||||
v-model="ranges[index]"
|
||||
track-color="transparent"
|
||||
inner-track-color="transparent"
|
||||
color="primary"
|
||||
:max="24"
|
||||
thumb-size="0px"
|
||||
track-size="16px"
|
||||
label-color="accent"
|
||||
label-text-color="primary"
|
||||
label
|
||||
:left-label-value="shifts[index]?.start_time"
|
||||
:right-label-value="shifts[index]?.end_time"
|
||||
readonly
|
||||
class="z-top"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template> -->
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<script setup lang="ts">
|
||||
import type { PayPeriodEmployeeDetails } from '../types/timesheet-approval-pay-period-employee-details-interface';
|
||||
import ShiftPreviewBar from 'src/modules/timesheet-approval/components/shifts/shift-preview-bar.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
isLoading: boolean;
|
||||
employeeName: string;
|
||||
employeeDetails: PayPeriodEmployeeDetails | undefined;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-card class="q-pa-md bg-white shadow-12">
|
||||
<!-- loader -->
|
||||
<q-card-section
|
||||
v-if="props.isLoading"
|
||||
class="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 class="text-h5 text-weight-bolder text-center full-width text-primary q-pt-none">
|
||||
{{ props.employeeName }}
|
||||
</q-card-section>
|
||||
|
||||
<!-- employee timesheet details -->
|
||||
<q-card-section v-if="!props.isLoading" class="q-pa-none">
|
||||
<div class="relative column col no-wrap bg-transparent">
|
||||
<div class="row text-center full-width text-grey-5 text-weight-bolder text-caption no-wrap">
|
||||
<div class="col"></div>
|
||||
<div class="">4</div>
|
||||
<div class="col"></div>
|
||||
<div class="">8</div>
|
||||
<div class="col"></div>
|
||||
<div class="">12</div>
|
||||
<div class="col"></div>
|
||||
<div class="">4</div>
|
||||
<div class="col"></div>
|
||||
<div class="">8</div>
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
<ShiftPreviewBar
|
||||
v-for="(shifts, index) in employeeDetails?.week1.shifts"
|
||||
:key="index"
|
||||
:weekday-shifts="shifts"
|
||||
class="q-mt-xs z-top"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-separator class="q-mx-xl q-my-sm" style="height: 3px;"/>
|
||||
|
||||
<div class="relative column col">
|
||||
<ShiftPreviewBar
|
||||
v-for="(shifts, index) in employeeDetails?.week2.shifts"
|
||||
:key="index"
|
||||
:weekday-shifts="shifts"
|
||||
class="q-mt-xs z-top"
|
||||
/>
|
||||
<div class="row text-center full-width text-grey-5 text-caption no-wrap">
|
||||
<div class="col"></div>
|
||||
<div class="">4</div>
|
||||
<div class="col"></div>
|
||||
<div class="">8</div>
|
||||
<div class="col"></div>
|
||||
<div class="">12</div>
|
||||
<div class="col"></div>
|
||||
<div class="">4</div>
|
||||
<div class="col"></div>
|
||||
<div class="">8</div>
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column absolute-full q-py-lg" style="z-index: 0;">
|
||||
<div class="row col">
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
<div class="row col q-mt-lg">
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
<q-separator vertical />
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
|
@ -1,30 +1,70 @@
|
|||
<script setup lang="ts">
|
||||
import type { PayPeriodEmployeeOverview } from '../types/timesheet-approval-pay-period-employee-overview-interface';
|
||||
import type { PayPeriodOverviewEmployee } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-employee-interface';
|
||||
|
||||
interface TableColumn {
|
||||
type TableColumn = {
|
||||
name: string;
|
||||
label: string;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
type CardButton = {
|
||||
icon: string;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
cols: TableColumn[];
|
||||
row: PayPeriodEmployeeOverview;
|
||||
modelValue: boolean;
|
||||
row: PayPeriodOverviewEmployee;
|
||||
initialState: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
clickDetails: [email: string];
|
||||
'update:modelValue': [value: boolean | null];
|
||||
}>();
|
||||
|
||||
const card_buttons: CardButton[] = [
|
||||
{ icon: 'work_history', label: 'timeSheetValidations.tooltipTimeline', onClick: () => emit('clickDetails', props.row.email) },
|
||||
{ icon: 'open_in_new', label: 'timeSheetValidations.tooltipTimesheet', onClick: () => emit('clickDetails', props.row.email) }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="q-px-sm q-pb-sm col-xs-12 col-sm-6 col-md-4 col-lg-4 col-xl-3 grid-style-transition">
|
||||
<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 -->
|
||||
<!-- Card header with employee name and details button-->
|
||||
<q-card-section
|
||||
horizontal
|
||||
class="q-py-none q-px-md"
|
||||
class="q-py-none q-pl-md relative"
|
||||
>
|
||||
<div class="text-primary text-h5 text-weight-bolder q-pt-xs overflow-hidden">
|
||||
{{ props.row.employee_name }}
|
||||
</div>
|
||||
|
||||
<q-space />
|
||||
|
||||
<!-- Buttons to view detailed shifts or view employee timesheet -->
|
||||
<q-btn
|
||||
flat
|
||||
unelevated
|
||||
square
|
||||
dense
|
||||
v-for="(button, index) in card_buttons"
|
||||
:key="index"
|
||||
class="q-py-none bg-white q-my-xs"
|
||||
color="primary"
|
||||
:icon="button.icon"
|
||||
@click="button.onClick"
|
||||
>
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="center middle"
|
||||
class="bg-primary uppercase text-weight-bold"
|
||||
>
|
||||
{{$t(button.label)}}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-card-section>
|
||||
|
||||
<q-separator
|
||||
|
|
@ -64,7 +104,7 @@
|
|||
<q-item
|
||||
dense
|
||||
class="column ellipsis "
|
||||
v-for="col in props.cols.slice(2, 5)"
|
||||
v-for="col in props.cols.slice(3, 6)"
|
||||
:key="col.label"
|
||||
>
|
||||
<q-item-label
|
||||
|
|
@ -91,7 +131,7 @@
|
|||
<q-item
|
||||
dense
|
||||
class="column"
|
||||
v-for="col in props.cols.slice(5, )"
|
||||
v-for="col in props.cols.slice(6, )"
|
||||
:key="col.label"
|
||||
>
|
||||
<q-item-label
|
||||
|
|
@ -117,7 +157,7 @@
|
|||
<q-card-section
|
||||
horizontal
|
||||
class="q-pa-sm text-weight-bold"
|
||||
:class="props.modelValue ? 'text-white bg-primary' : 'text-primary bg-white'"
|
||||
:class="props.initialState ? 'text-white bg-primary' : 'text-primary bg-white'"
|
||||
>
|
||||
<q-space />
|
||||
<q-checkbox
|
||||
|
|
@ -126,10 +166,10 @@
|
|||
size="lg"
|
||||
checked-icon="lock"
|
||||
unchecked-icon="lock_open"
|
||||
:color="props.modelValue ? 'white' : 'primary'" keep-color
|
||||
:model-value="props.modelValue"
|
||||
@update:model-value="val => $emit('update:modelValue', val)"
|
||||
:label="props.modelValue ? $t('timeSheetValidations.timeSheetStatusVerified') : $t('timeSheetValidations.timeSheetStatusUnverified')"
|
||||
:color="props.initialState ? 'white' : 'primary'" keep-color
|
||||
:model-value="props.initialState"
|
||||
@update:model-value="val => emit('update:modelValue', val)"
|
||||
:label="props.initialState ? $t('timeSheetValidations.timeSheetStatusVerified') : $t('timeSheetValidations.timeSheetStatusUnverified')"
|
||||
class="text-uppercase"
|
||||
/>
|
||||
</q-card-section>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
import TimesheetApprovalPeriodPicker from '../components/timesheet-approval-period-picker.vue';
|
||||
import TimesheetApprovalEmployeeOverviewListItem from './timesheet-approval-employee-overview-list-item.vue';
|
||||
import TimesheetApprovalEmployeeDetails from '../components/timesheet-approval-employee-details.vue';
|
||||
|
||||
import { date, type QTableColumn } from 'quasar';
|
||||
import type { PayPeriodEmployeeOverview } from '../types/timesheet-approval-pay-period-employee-overview-interface';
|
||||
import type { PayPeriodOverviewEmployee } from '../types/timesheet-approval-pay-period-overview-employee-interface';
|
||||
|
||||
const { t } = useI18n();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
|
@ -21,14 +22,22 @@
|
|||
|
||||
const filter = ref<string | number | null>('');
|
||||
const original_approvals = ref<Record<string, boolean>>({});
|
||||
const is_showing_details = ref<boolean>(false);
|
||||
const clicked_employee_name = ref<string>('');
|
||||
|
||||
const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [
|
||||
const columns = computed((): QTableColumn<PayPeriodOverviewEmployee>[] => [
|
||||
{
|
||||
name: 'employee_name',
|
||||
label: t('timeSheetValidations.tableColumnLabelFullname'),
|
||||
field: 'employee_name',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: t('timeSheetValidations.tableColumnLabelEmail'),
|
||||
field: 'email',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'regular_hours',
|
||||
label: t('timeSheetValidations.tableColumnLabelRegularHours'),
|
||||
|
|
@ -65,7 +74,7 @@
|
|||
]);
|
||||
|
||||
const has_changes = computed(() => {
|
||||
return timesheet_store.pay_period_employee_overviews.some(emp => {
|
||||
return timesheet_store.pay_period_overview_employees.some(emp => {
|
||||
return emp.is_approved !== original_approvals.value[emp.email];
|
||||
});
|
||||
});
|
||||
|
|
@ -79,19 +88,37 @@
|
|||
await timesheet_approval_api.getPayPeriodOverviewByDate(date_string);
|
||||
}
|
||||
|
||||
const onClickedDetails = async (email: string, name: string) => {
|
||||
clicked_employee_name.value = name;
|
||||
is_showing_details.value = true;
|
||||
await timesheet_approval_api.getTimesheetsByPayPeriodAndEmail(email);
|
||||
}
|
||||
|
||||
onMounted( async () => {
|
||||
const today = date.formatDate(new Date(), 'YYYY-MM-DD');
|
||||
await timesheet_approval_api.getPayPeriodOverviewByDate(today);
|
||||
|
||||
const approvals = timesheet_store.pay_period_employee_overviews.map(emp => [emp.email, emp.is_approved]);
|
||||
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="scale"
|
||||
transition-hide="jump-down"
|
||||
>
|
||||
<TimesheetApprovalEmployeeDetails
|
||||
:is-loading="timesheet_store.is_loading"
|
||||
:employee-name="clicked_employee_name"
|
||||
:employee-details="timesheet_store.pay_period_employee_details"
|
||||
style="min-width: 300px;"
|
||||
/>
|
||||
</q-dialog>
|
||||
<div class="q-pa-md">
|
||||
<q-table
|
||||
:rows="timesheet_store.pay_period_employee_overviews"
|
||||
:rows="timesheet_store.pay_period_overview_employees"
|
||||
:columns="columns"
|
||||
row-key="email"
|
||||
:filter="filter"
|
||||
|
|
@ -126,13 +153,15 @@
|
|||
|
||||
<!-- Template for individual employee cards -->
|
||||
<template v-slot:item="props: {
|
||||
cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[],
|
||||
row: PayPeriodEmployeeOverview
|
||||
cols: (QTableColumn<PayPeriodOverviewEmployee> & { value: unknown })[],
|
||||
row: PayPeriodOverviewEmployee
|
||||
}">
|
||||
<TimesheetApprovalEmployeeOverviewListItem
|
||||
:cols="props.cols"
|
||||
:row="props.row"
|
||||
v-model="props.row.is_approved"/>
|
||||
:initial-state="props.row.is_approved"
|
||||
@click-details="email => onClickedDetails(email, props.row.employee_name)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Template for custome failed-to-load state -->
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
const emit = defineEmits<{
|
||||
'date-selected': [value: string, reason?: string, details?: QDateDetails]
|
||||
'pressed-previous-button': void
|
||||
'pressed-next-button': void
|
||||
'pressed-previous-button': []
|
||||
'pressed-next-button': []
|
||||
}>();
|
||||
|
||||
const onDateSelected = (value: string, reason: string, details: QDateDetails) => {
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
push rounded
|
||||
icon="keyboard_arrow_left"
|
||||
color="primary"
|
||||
@click="$emit('pressed-previous-button')"
|
||||
@click="emit('pressed-previous-button')"
|
||||
:disable="props.isPreviousLimit || props.isDisabled"
|
||||
class="q-mr-sm q-px-sm"
|
||||
/>
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
push rounded
|
||||
icon="keyboard_arrow_right"
|
||||
color="primary"
|
||||
@click="$emit('pressed-next-button')"
|
||||
@click="emit('pressed-next-button')"
|
||||
:disable="props.isDisabled"
|
||||
class="q-ml-sm q-px-sm"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -40,8 +40,13 @@ export const useTimesheetApprovalApi = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const getTimesheetsByPayPeriodAndEmail = async (employee_email: string) => {
|
||||
await timesheet_store.getTimesheetsByPayPeriodAndEmail(employee_email);
|
||||
}
|
||||
|
||||
return {
|
||||
getPayPeriodOverviewByDate,
|
||||
getNextPayPeriodOverview,
|
||||
getTimesheetsByPayPeriodAndEmail
|
||||
}
|
||||
};
|
||||
|
|
@ -13,12 +13,18 @@
|
|||
year: 'numeric',
|
||||
};
|
||||
|
||||
|
||||
const pay_period_label = computed(() => {
|
||||
const dates = timesheet_store.current_pay_period.label.split('.');
|
||||
const start_date = new Intl.DateTimeFormat(locale.value, date_options).format(date.extractDate(dates[0] as string, 'YYYY-MM-DD'));
|
||||
const end_date = new Intl.DateTimeFormat(locale.value, date_options).format(date.extractDate(dates[1] as string, 'YYYY-MM-DD'));
|
||||
|
||||
if ( dates.length === 1 ) {
|
||||
return {
|
||||
start_date: '—',
|
||||
end_date: '—'
|
||||
}
|
||||
}
|
||||
|
||||
return { start_date, end_date };
|
||||
});
|
||||
</script>
|
||||
|
|
@ -42,6 +48,7 @@
|
|||
{{ pay_period_label.end_date }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TimesheetApprovalEmployeeOverviewList />
|
||||
</q-page>
|
||||
</template>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { api } from "src/boot/axios";
|
||||
import type { PayPeriodOverview } from "../types/timesheet-approval-pay-period-overview-interface";
|
||||
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
||||
import type { PayPeriodEmployeeDetails } from "../types/timesheet-approval-pay-period-employee-details-interface";
|
||||
|
||||
export const timesheetApprovalService = {
|
||||
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
|
||||
|
|
@ -16,7 +17,12 @@ export const timesheetApprovalService = {
|
|||
|
||||
getPayPeriodEmployeeOverviews: async (year: number, period_number: number, supervisor_email: string): Promise<PayPeriodOverview> => {
|
||||
// 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;
|
||||
},
|
||||
|
||||
getTimesheetsByPayPeriodAndEmail: async (year: number, period_no: number, email: string): Promise<PayPeriodEmployeeDetails> => {
|
||||
const response = await api.get('timesheets', { params: { year, period_no, email, }});
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import type { TimesheetDetailsWeek } from "src/modules/timesheets/types/timesheet-details-interface";
|
||||
|
||||
export interface PayPeriodEmployeeDetails {
|
||||
// is_approved: boolean;
|
||||
week1: TimesheetDetailsWeek;
|
||||
week2: TimesheetDetailsWeek;
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export interface PayPeriodEmployeeOverview {
|
||||
export interface PayPeriodOverviewEmployee {
|
||||
email: string;
|
||||
employee_name: string;
|
||||
regular_hours: number;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { PayPeriodEmployeeOverview } from "./timesheet-approval-pay-period-employee-overview-interface";
|
||||
import type { PayPeriodOverviewEmployee } from "./timesheet-approval-pay-period-overview-employee-interface";
|
||||
|
||||
export interface PayPeriodOverview {
|
||||
pay_period_no: number;
|
||||
|
|
@ -7,5 +7,5 @@ export interface PayPeriodOverview {
|
|||
period_start: string;
|
||||
period_end: string;
|
||||
label: string;
|
||||
employees_overview: PayPeriodEmployeeOverview[];
|
||||
employees_overview: PayPeriodOverviewEmployee[];
|
||||
};
|
||||
30
src/modules/timesheets/types/timesheet-details-interface.ts
Normal file
30
src/modules/timesheets/types/timesheet-details-interface.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { Shift } from "./timesheet-shift-interface";
|
||||
|
||||
export interface TimesheetDetailsWeek {
|
||||
is_approved: boolean;
|
||||
shifts: WeekDay<TimesheetDetailsWeekDayShifts>;
|
||||
expenses: WeekDay<TimesheetDetailsWeekDayExpenses>;
|
||||
}
|
||||
|
||||
type WeekDay<T> = {
|
||||
sun: T;
|
||||
mon: T;
|
||||
tue: T;
|
||||
wed: T;
|
||||
thu: T;
|
||||
fri: T;
|
||||
sat: T;
|
||||
}
|
||||
|
||||
type TimesheetDetailsWeekDayShifts = Shift[];
|
||||
|
||||
interface TimesheetDetailsWeekDayExpenses {
|
||||
costs: Expense[];
|
||||
mileage: Expense[];
|
||||
[otherType: string]: Expense[]; //for possible future types of expenses
|
||||
}
|
||||
|
||||
interface Expense {
|
||||
is_approved: boolean;
|
||||
amount: number;
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export interface Shift {
|
||||
is_approved: boolean;
|
||||
start: string;
|
||||
end: string;
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@ import { defineStore } from 'pinia';
|
|||
import { ref } from 'vue';
|
||||
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/services-timesheet-approval';
|
||||
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
||||
import type { PayPeriodEmployeeOverview } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-overview-interface";
|
||||
import type { PayPeriodOverviewEmployee } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-employee-interface";
|
||||
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-details-interface';
|
||||
|
||||
const default_pay_period: PayPeriod = {
|
||||
pay_period_no: -1,
|
||||
|
|
@ -14,9 +15,10 @@ const default_pay_period: PayPeriod = {
|
|||
};
|
||||
|
||||
export const useTimesheetStore = defineStore('timesheet', () => {
|
||||
const current_pay_period = ref<PayPeriod>(default_pay_period);
|
||||
const pay_period_employee_overviews = ref<PayPeriodEmployeeOverview[]>([]);
|
||||
const is_loading = ref<boolean>(false);
|
||||
const current_pay_period = ref<PayPeriod>(default_pay_period);
|
||||
const pay_period_overview_employees = ref<PayPeriodOverviewEmployee[]>([]);
|
||||
const pay_period_employee_details = ref<PayPeriodEmployeeDetails | undefined>();
|
||||
|
||||
const getPayPeriodByDate = async (date_string: string): Promise<boolean> => {
|
||||
is_loading.value = true;
|
||||
|
|
@ -29,6 +31,8 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +52,8 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -60,23 +66,94 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
is_loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await timesheetApprovalService.getPayPeriodEmployeeOverviews(pay_year, period_number, supervisor_email);
|
||||
pay_period_employee_overviews.value = response.employees_overview;
|
||||
const response = await timesheetApprovalService.getPayPeriodEmployeeOverviews(
|
||||
pay_year,
|
||||
period_number,
|
||||
supervisor_email
|
||||
);
|
||||
pay_period_overview_employees.value = response.employees_overview;
|
||||
} catch (error) {
|
||||
console.error('There was an error retrieving Employee Pay Period overviews: ', error);
|
||||
pay_period_employee_overviews.value = [];
|
||||
// TODO: trigger an alert window with an error message here!
|
||||
pay_period_overview_employees.value = [];
|
||||
// TODO: More in-depth error-handling here
|
||||
}
|
||||
|
||||
is_loading.value = false;
|
||||
};
|
||||
|
||||
const getTimesheetsByPayPeriodAndEmail = async (employee_email: string) => {
|
||||
is_loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await timesheetApprovalService.getTimesheetsByPayPeriodAndEmail(
|
||||
current_pay_period.value.pay_year,
|
||||
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);
|
||||
pay_period_employee_details.value = MOCK_DATA_TIMESHEET_DETAILS;
|
||||
// TODO: More in-depth error-handling here
|
||||
}
|
||||
|
||||
is_loading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
current_pay_period,
|
||||
pay_period_employee_overviews,
|
||||
is_loading,
|
||||
pay_period_overview_employees,
|
||||
pay_period_employee_details,
|
||||
is_loading,
|
||||
getPayPeriodByDate,
|
||||
getPayPeriodByYearAndPeriodNumber,
|
||||
getTimesheetApprovalPayPeriodEmployeeOverviews,
|
||||
getTimesheetApprovalPayPeriodEmployeeOverviews,
|
||||
getTimesheetsByPayPeriodAndEmail,
|
||||
};
|
||||
});
|
||||
|
||||
const MOCK_DATA_TIMESHEET_DETAILS = {
|
||||
is_approved: false,
|
||||
week1: {
|
||||
is_approved: true,
|
||||
shifts: {
|
||||
sun: [],
|
||||
mon: [ { is_approved: true, start: '08:00', end: '12:00' }, { is_approved: true, start: '13:00', end: '17:00' } ],
|
||||
tue: [ { is_approved: true, start: '08:00', end: '11:45' }, { is_approved: true, start: '12:45', end: '17:00' } ],
|
||||
wed: [ { is_approved: true, start: '08:00', end: '12:00' }, { is_approved: true, start: '13:00', end: '17:00' } ],
|
||||
thu: [ { is_approved: false, start: '13:00', end: '17:00' } ],
|
||||
fri: [ { is_approved: true, start: '08:00', end: '12:00' }, { is_approved: true, start: '13:00', end: '17:00' } ],
|
||||
sat: []
|
||||
},
|
||||
expenses: {
|
||||
sun: { costs: [], mileage: [] },
|
||||
mon: { costs: [ { is_approved: true, amount: 156.49 } ], mileage: [] },
|
||||
tue: { costs: [], mileage: [] },
|
||||
wed: { costs: [], mileage: [] },
|
||||
thu: { costs: [], mileage: [ { is_approved: false, amount: 16 } ] },
|
||||
fri: { costs: [], mileage: [] },
|
||||
sat: { costs: [], mileage: [] }
|
||||
}
|
||||
},
|
||||
week2: {
|
||||
is_approved: true,
|
||||
shifts: {
|
||||
sun: [],
|
||||
mon: [],
|
||||
tue: [],
|
||||
wed: [],
|
||||
thu: [],
|
||||
fri: [],
|
||||
sat: []
|
||||
},
|
||||
expenses: {
|
||||
sun: { costs: [], mileage: [] },
|
||||
mon: { costs: [], mileage: [] },
|
||||
tue: { costs: [], mileage: [] },
|
||||
wed: { costs: [], mileage: [] },
|
||||
thu: { costs: [], mileage: [] },
|
||||
fri: { costs: [], mileage: [] },
|
||||
sat: { costs: [], mileage: [] }
|
||||
}
|
||||
},
|
||||
} as PayPeriodEmployeeDetails;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user