feat(details): add ui elements to shift rows in employee details

This commit is contained in:
Nicolas Drolet 2025-09-05 17:08:43 -04:00
parent 5bd19c4a9c
commit fbf8114666
10 changed files with 231 additions and 103 deletions

View File

@ -342,13 +342,13 @@ export default {
reportFilterVacation: 'Vacation',
},
shiftColumns: {
title: 'Shifts',
column_1: 'Type',
column_2: 'Start time',
column_3: 'End time',
column_4: 'Comment',
column_5: 'Status',
column_6: 'Supervisors report',
title: 'shifts',
labelType: 'type',
labelIn: 'start',
labelOut: 'end',
labelComment: 'comment',
labelState: 'state',
labelSupervisorReport: 'supervisor report',
},
expenseColumns: {
title: 'Expenses',

View File

@ -236,12 +236,12 @@ export default {
},
shiftColumns: {
title: 'Quarts de travail',
column_1: 'Type',
column_2: 'Entrée',
column_3: 'Sortie',
column_4: 'Commentaire',
column_5: tat',
column_6: 'Rapport du superviseur',
labelType: 'type',
labelIn: 'entrée',
labelOut: 'sortie',
labelComment: 'commentaire',
labelState: tat',
labelSupervisorReport: 'rapport du superviseur',
},
shiftsTemplate: {
tabTitle1: 'Quarts de travail',

View File

@ -0,0 +1,58 @@
<template>
<!-- This header component is placed in absolute top in order to keep the timestamps
below it centered. As such it must imitate the column width of the shift row below it
as much as possible, hence the empty q-card-sections. -->
<q-card-section
horizontal
class="text-uppercase text-center items-center q-px-xs"
>
<!-- Date widget -->
<q-card-section class="q-px-xs q-py-none col-auto" style="width: 75px;"></q-card-section>
<!-- shift row itself -->
<q-card-section horizontal class="q-px-xs col">
<!-- punch-in timestamps -->
<q-card-section class="col q-px-xs q-py-none">
<q-item-label class="text-weight-bolder text-primary">
{{ $t('shiftColumns.labelIn') }}
</q-item-label>
</q-card-section>
<!-- arrows pointing to punch-out timestamps -->
<q-card-section class="col-auto q-pa-none q-mx-sm">
<q-icon
name="double_arrow"
color="transparent"
size="24px"
style="transform: translateX(5px);"
/>
<q-icon
name="double_arrow"
color="transparent"
size="24px"
style="transform: translateX(-5px);"
/>
</q-card-section>
<!-- punch-out timestamps -->
<q-card-section class="col q-px-xs q-py-none">
<q-item-label class="text-weight-bolder text-primary">
{{ $t('shiftColumns.labelOut') }}
</q-item-label>
</q-card-section>
<!-- shift type badge -->
<q-card-section class="col q-pa-none"></q-card-section>
<!-- comment button -->
<q-card-section class="col-auto q-pa-none">
<q-btn
icon="chat_bubble_outline"
color="transparent"
flat
class="q-pa-sm"
/>
</q-card-section>
</q-card-section>
</q-card-section>
</template>

View File

@ -1,28 +1,112 @@
<script setup lang="ts">
import { date } from 'quasar';
import { computed } from 'vue';
import { default_shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
import type { TimesheetDetailsDailySchedule } from 'src/modules/timesheets/types/timesheet-details-interface';
const props = defineProps<{
dayData: TimesheetDetailsDailySchedule;
}>();
const shifts_or_placeholder = computed(() => {
return props.dayData.shifts.length > 0 ? props.dayData.shifts : [default_shift];
})
const getWeekDay = (shift_date: string): string => {
const d = date.extractDate(shift_date, 'YYYY/MM/DD');
return date.formatDate(d, 'dddd');
const getShiftColor = (type: string): string => {
switch(type) {
case 'REGULAR': return 'primary';
case 'EVENING': return 'info';
case 'EMERGENCY': return 'teal-14';
case 'OVERTIME': return 'negative';
case 'VACATION': return 'purple-10';
case 'HOLIDAY': return 'indigo-13';
case 'SICK': return 'grey-9';
default : return 'transparent';
}
};
</script>
<template>
<q-card>
<q-card-section
horizontal
class="q-pa-xs"
>
<q-card-section>
<div class="bg-primary rounded-10">
<span></span>
</div>
</q-card-section>
<q-card-section
horizontal
class="q-pa-xs text-uppercase text-center items-center"
>
<!-- punch-in timestamps -->
<q-card-section class="q-pa-none col">
<q-item-label
v-for="shift, index in props.dayData.shifts"
:key="index"
class="text-weight-bolder text-grey-8 bg-secondary q-pa-xs rounded-5"
style="font-size: 1.75em; line-height: 80% !important;"
>
{{ shift.start_time }}
</q-item-label>
<q-item-label v-if="props.dayData.shifts.length === 0"
class="text-weight-bolder text-grey-5"
style="font-size: 1.75em; line-height: 80% !important;"
>
---
</q-item-label>
</q-card-section>
</q-card>
<!-- arrows pointing to punch-out timestamps -->
<q-card-section
horizontal class="items-center justify-center q-ma-sm col-auto">
<q-icon
name="double_arrow"
:color=" props.dayData.shifts.length > 0 ? 'accent' : 'transparent'"
size="24px"
style="transform: translateX(5px);"
/>
<q-icon
name="double_arrow"
:color=" props.dayData.shifts.length > 0 ? 'primary' : 'transparent'"
size="24px"
style="transform: translateX(-5px);"
/>
</q-card-section>
<!-- punch-out timestamps -->
<q-card-section class="q-pa-none col">
<q-item-label
v-for="shift, index in props.dayData.shifts"
:key="index"
class="text-weight-bolder text-grey-8 bg-secondary q-pa-xs rounded-5"
style="font-size: 1.75em; line-height: 80% !important;"
>
{{ shift.end_time }}
</q-item-label>
<q-item-label v-if="props.dayData.shifts.length === 0"
class="text-weight-bolder text-grey-5"
style="font-size: 1.75em; line-height: 80% !important;"
>
---
</q-item-label>
</q-card-section>
<!-- shift type badge -->
<q-card-section class="col q-pa-none">
<q-badge
v-for="shift, index in shifts_or_placeholder"
:key="index"
:color="shift.type ? getShiftColor(shift.type) : 'transparent'"
:label="shift.type || ''"
class="text-weight-medium justify-center"
style="width: 80px; font-size: 0.8em;"
/>
</q-card-section>
<!-- comment button -->
<q-card-actions
class="col-auto q-pa-none"
>
<q-btn
v-for="(shift, index) in shifts_or_placeholder"
:key="index"
flat
:disable="shift.type === ''"
icon="chat_bubble_outline"
:color="shift.type === '' ? 'grey-5' : 'grey-8'"
class="q-pa-sm"
/>
</q-card-actions>
</q-card-section>
</template>

View File

@ -1,77 +1,57 @@
<script setup lang="ts">
/*eslint-disable*/
import { useI18n } from 'vue-i18n';
import TimesheetApprovalEmployeeDetailsShiftsRow from 'src/modules/timesheet-approval/components/timesheet-approval-employee-details-shifts-row.vue';
import TimesheetApprovalEmployeeDetailsShiftsRowHeader from 'src/modules/timesheet-approval/components/timesheet-approval-employee-details-shifts-row-header.vue';
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';
const { t } = useI18n();
const props = defineProps<{
rawData: PayPeriodEmployeeDetails;
currentPayPeriod: PayPeriod;
}>();
const weeks = [ props.rawData.week1, props.rawData?.week2 ];
const days = weeks.flatMap( week => Object.values(week.shifts) );
const getShiftColor = (type: string): string => {
switch(type) {
case 'REGULAR': return 'primary';
case 'EVENING': return 'info';
case 'EMERGENCY': return 'teal-14';
case 'OVERTIME': return 'negative';
case 'VACATION': return 'purple-10';
case 'HOLIDAY': return 'indigo-13';
case 'SICK': return 'grey-9';
default : return '';
}
const getDate = (shift_date: string): Date => {
return new Date(props.currentPayPeriod.pay_year.toString() + '/' + shift_date);
};
</script>
<template>
<div class="bg-secondary full-width q-pa-sm rounded-5">
<div
v-for="week, index in props.rawData"
:key="index"
class="q-px-xs q-pt-xs rounded-5 col"
>
<q-card
v-for="day, index in days"
v-for="day, day_index in week.shifts"
:key="day_index"
flat
bordered
class="row items-center rounded-10 q-mb-xs"
>
<q-card-section>
<TimesheetApprovalEmployeeDetailsShiftsRowHeader class="absolute-top" />
<q-card-section class="col-auto q-pa-xs text-white">
<div
class="bg-primary rounded-10 q-pa-xs text-center"
style="width: 75px;"
>
<q-item-label
style="font-size: 0.8em;"
class="text-uppercase"
>{{ $d(getDate(day.short_date), {weekday: 'long'}) }}</q-item-label>
<q-item-label
class="text-weight-bolder"
style="font-size: 3em; line-height: 90% !important;"
>{{ day.short_date.split('/')[1] }}</q-item-label>
<q-item-label
style="font-size: 0.8em;"
class="text-uppercase"
>{{ $d(getDate(day.short_date), {month: 'long'}) }}</q-item-label>
</div>
</q-card-section>
<q-card-section class="col q-pa-none">
<TimesheetApprovalEmployeeDetailsShiftsRow
:day-data="day"
/>
</q-card-section>
</q-card>
</div>
<!-- <q-card-section :horizontal="$q.screen.gt.sm" class="row">
<q-card-section v-for="week in props.rawData" class="q-pa-none q-ma-none col">
<div v-for="day, index in week.shifts">
<div class=" text-primary text-uppercase q-pa-none q-ma-none"> {{ day_labels[index] }} </div>
<div v-if="day.shifts.length === 0" class="q-pa-none q-ma-none">---</div>
<div v-for="shift in day.shifts">
<div class="text-caption row items-center">
<div class="col row">
{{ shift.start_time }}
<q-space />
<q-separator vertical :color="getShiftColor(shift.type)"/>
</div>
<q-separator class="col" :color="getShiftColor(shift.type)"/>
<div class="col-auto">
<q-badge
:color="getShiftColor(shift.type)"
class="text-lowercase text-center"
style="font-size: 0.8em;"
>
{{ shift.type }}
</q-badge>
</div>
<q-separator class="col" :color="getShiftColor(shift.type)"/>
<div class="col row">
<q-separator vertical :color="getShiftColor(shift.type)"/>
<q-space />
{{ shift.end_time }}
</div>
</div>
</div>
</div>
</q-card-section>
</q-card-section> -->
</template>

View File

@ -60,7 +60,7 @@
<q-tooltip
anchor="top middle"
self="center middle"
class="bg-primary uppercase text-weight-bold"
class="bg-primary text-uppercase text-weight-bold"
>
{{$t(button.label)}}
</q-tooltip>

View File

@ -2,24 +2,19 @@
/* eslint-disable */
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import { useTimesheetApprovalApi } from '../composables/use-timesheet-approval-api';
import TimesheetApprovalPeriodPicker from '../components/timesheet-approval-period-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 { date, type QTableColumn } from 'quasar';
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);
@ -28,7 +23,6 @@
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',
@ -76,25 +70,21 @@
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('timeSheetValidations.reportFilterShifts'),
t('timeSheetValidations.reportFilterExpenses'),
t('timeSheetValidations.reportFilterHoliday'),
t('timeSheetValidations.reportFilterVacation'),
]
const is_calendar_limit = computed( () => {
return timesheet_store.current_pay_period.pay_year === 2024 &&
timesheet_store.current_pay_period.pay_period_no <= 1;
@ -150,7 +140,7 @@
transition-show="jump-down"
transition-hide="jump-down"
@before-show="() => update_key += 1"
:full-width="$q.screen.gt.sm"
full-width
:full-height="$q.screen.gt.sm"
>
<TimesheetApprovalEmployeeDetails
@ -158,6 +148,7 @@
: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>

View File

@ -8,12 +8,14 @@
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 { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
const props = defineProps<{
isLoading: boolean;
employeeName: string;
employeeOverview: PayPeriodOverviewEmployee;
employeeDetails: PayPeriodEmployeeDetails;
currentPayPeriod: PayPeriod;
updateKey: number;
}>();
@ -23,7 +25,7 @@
<template>
<q-card
class="q-pa-md bg-white shadow-12 rounded-15 column no-wrap relative"
class="q-pa-sm bg-white shadow-12 rounded-15 column no-wrap relative"
:style="$q.screen.gt.sm ? 'width: 70vw !important; height: 90vh !important;' : '' "
>
<!-- loader -->
@ -58,7 +60,7 @@
v-model="is_showing_graph"
:options="[
{icon: 'bar_chart', value: true},
{icon: 'mode_edit_outline', value: false},
{icon: 'edit', value: false},
]"
/>
</q-card>
@ -66,9 +68,14 @@
</q-card-section>
<!-- employee timesheet details edit -->
<q-card-section v-if="!props.isLoading && !is_showing_graph">
<q-card-section
v-if="!props.isLoading && !is_showing_graph"
: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>

View File

@ -5,3 +5,11 @@ export interface Shift {
type : string;
is_approved : boolean;
}
export const default_shift: Shift = {
date: '',
start_time: '',
end_time: '',
type: '',
is_approved: false,
}