refactor(approvals): add table with grid setup, customize card (item) appearance

This commit is contained in:
Nicolas Drolet 2025-08-12 17:01:46 -04:00
parent 2fb7b4a4c1
commit 4ae33dfcf1
7 changed files with 389 additions and 75 deletions

View File

@ -24,7 +24,7 @@ $dark: #000;
$dark-page: #323232;
$positive: #21ba45;
$negative: #c10015;
$negative: #ff586c71;
$info: #31ccec;
$warning: #eeb10a;
$warning: #ffde82c2;
$white: white;

View File

@ -293,12 +293,12 @@ export default {
timeSheetValidations: {
tableHeader: 'List of employees',
tableColumnLabelFullname: 'Full name',
tableColumnLabelRegularHours: 'Regular hours',
tableColumnLabelEveningHours: 'Evening hours',
tableColumnLabelEmergencyHours: 'Emergency hours',
tableColumnLabelOvertime: 'Overtime hours',
tableColumnLabelExpenses: 'Expenses',
tableColumnLabelMileage: 'Mileage',
tableColumnLabelRegularHours: 'regular hours',
tableColumnLabelEveningHours: 'evening hours',
tableColumnLabelEmergencyHours: 'emergency hours',
tableColumnLabelOvertime: 'overtime hours',
tableColumnLabelExpenses: 'of expenses',
tableColumnLabelMileage: 'of mileage',
actionTitle: 'Please save the changes made.',
actionButton: 'Save',
timeSheetStatusVerified: 'Verified',

View File

@ -339,12 +339,12 @@ export default {
timeSheetValidations: {
tableHeader: 'Liste des employés',
tableColumnLabelFullname: 'nom complet',
tableColumnLabelRegularHours: 'Heures régulières',
tableColumnLabelEveningHours: 'Heures de soir',
tableColumnLabelEmergencyHours: 'Heures durgence',
tableColumnLabelOvertime: 'Heures supplémentaires',
tableColumnLabelExpenses: 'Dépenses',
tableColumnLabelMileage: 'Kilométrage',
tableColumnLabelRegularHours: 'heures régulières',
tableColumnLabelEveningHours: 'heures de soir',
tableColumnLabelEmergencyHours: 'heures durgence',
tableColumnLabelOvertime: 'heures supplémentaires',
tableColumnLabelExpenses: 'de dépenses',
tableColumnLabelMileage: 'de kilométrage',
actionTitle: 'Veuillez enregistrer les changements effectués.',
actionButton: 'Enregistrer',
timeSheetStatusVerified: 'Vérifié',

View File

@ -1,76 +1,81 @@
<script setup lang="ts">
/* eslint-disable */
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import type { PayPeriodEmployeeOverview } from '../types/timesheet-approval-pay-period-employee-overview-interface';
import type { QTableColumn } from 'quasar';
import { mock_pay_period_employee_overviews } from '../timesheet-approval-test-constants';
const { t } = useI18n();
const columns: QTableColumn<PayPeriodEmployeeOverview>[] = [
{ name: 'employee_name', label: t('tableColumnLabelFullname'), field: 'employee_name', sortable: true },
{ name: 'regular_hours', label: t('tableColumnLabelRegularHours'), field: 'regular_hours', sortable: true },
{ name: 'evening_hours', label: t('tableColumnLabelEveningHours'), field: 'evening_hours' },
{ name: 'emergency_hours', label: t('tableColumnLabelEmergencyHours'), field: 'emergency_hours' },
{ name: 'overtime_hours', label: t('tableColumnLabelOvertimeHours'), field: 'overtime_hours' },
{ name: 'expenses', label: t('tableColumnLabelExpenses'), field: 'expenses', sortable: true },
{ name: 'mileage', label: t('tableColumnLabelMileage'), field: 'mileage', sortable: true }
]
const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [
{ name: 'employee_name', label: t('timeSheetValidations.tableColumnLabelFullname'), field: 'employee_name', sortable: true },
{ name: 'regular_hours', label: t('timeSheetValidations.tableColumnLabelRegularHours'), field: 'regular_hours', sortable: true },
{ name: 'evening_hours', label: t('timeSheetValidations.tableColumnLabelEveningHours'), field: 'evening_hours' },
{ name: 'emergency_hours', label: t('timeSheetValidations.tableColumnLabelEmergencyHours'), field: 'emergency_hours' },
{ name: 'overtime_hours', label: t('timeSheetValidations.tableColumnLabelOvertime'), field: 'overtime_hours' },
{ name: 'expenses', label: t('timeSheetValidations.tableColumnLabelExpenses'), field: 'expenses', sortable: true },
{ name: 'mileage', label: t('timeSheetValidations.tableColumnLabelMileage'), field: 'mileage', sortable: true }
]);
const filter = ref('');
const selected = ref([]);
const props = defineProps<{
rows: PayPeriodEmployeeOverview[];
}>();
const selectedRows = ref<PayPeriodEmployeeOverview[]>();
const rows: PayPeriodEmployeeOverview[] = mock_pay_period_employee_overviews;
</script>
<template>
<div class="q-pa-md">
<q-table
:title="$t('timeSheetValidations.tableHeader')"
:rows="rows"
:columns="columns"
row-key="name"
selection="multiple"
v-model:selected="selected"
:filter="filter"
grid
hide-header
>
<template v-slot:top-right>
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:item="props: { cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[], row: PayPeriodEmployeeOverview, selected: boolean }">
<div
class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3 grid-style-transition"
:style="props.selected ? 'transform: scale(0.95);' : ''"
<div class="q-pa-md">
<q-table
:title="$t('timeSheetValidations.tableHeader')"
:rows="rows"
:columns="columns"
row-key="employee_id"
selection="multiple"
v-model:selected="selectedRows"
:filter="filter"
grid
virtual-scroll
dense
:rows-per-page-options="[0]"
card-container-class="justify-center q-gutter-md"
>
<q-card>
<q-card-section>
<q-checkbox dense v-model="props.selected" :label="props.row.is_approved ? $t('timeSheetValidations.timeSheetStatusVerified') : $t('timeSheetValidations.timeSheetStatusUnverified')" />
</q-card-section>
<q-separator />
<q-list dense>
<q-item v-for="col in props.cols.filter(col => col.name !== 'desc')" :key="col.name">
<q-item-section>
<q-item-label>{{ col.label }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label caption>{{ col.value }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
</template>
<template v-slot:top-right>
<q-input rounded standout="bg-white text-primary" dense debounce="300" v-model="filter"
placeholder="Search" clearable>
<template v-slot:append>
<q-icon name="search" color="primary" />
</template>
</q-input>
</template>
</q-table>
</div>
<template v-slot:item="props: { cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[], row: PayPeriodEmployeeOverview, selected: boolean }">
<div class="q-px-sm q-pb-sm col-xs-6 col-sm-4 col-md-3 col-lg-2 grid-style-transition">
<q-card class="rounded-15">
<q-card-section class="q-pb-sm">
<div class="text-primary text-h5 text-weight-bolder ellipsis">{{ props.row.employee_name }}</div>
</q-card-section>
<div v-for="col in props.cols.filter(col => col.name !== 'employee_name')" class="q-pa-none q-mx-sm items-center row" :class="{ 'bg-warning': col.name == 'overtime_hours' && col.value as number > 0 }" >
<q-card-section class="text-right text-weight-bolder text-subtitle1 text-primary q-pr-sm q-py-none col-3" style="line-height: 1.2em;">{{ col.value }}</q-card-section>
<q-card-section class="text-weight-bold q-pa-none col-9" >{{ col.label }}</q-card-section>
</div>
<q-card-section horizontal class="q-pa-sm q-mt-sm" :class="{ 'bg-primary text-white': props.selected}">
<q-space />
<!-- TODO: Checkbox should be linked to store's employeeOverview.is_approved, not to table's selected ref!! -->
<q-checkbox
dense
left-label
size="lg"
checked-icon="lock"
unchecked-icon="lock_open"
:color="props.selected ? 'white' : 'primary'" keep-color
v-model="props.selected"
:label="props.selected ? $t('timeSheetValidations.timeSheetStatusVerified') : ''" />
</q-card-section>
</q-card>
</div>
</template>
</q-table>
</div>
</template>

View File

@ -1,11 +1,12 @@
<script setup lang="ts">
// const testDates: string[] = ['25 Juin 2025', '12 Juillet 2025', '13 Juillet 2025', '26 Juillet 2025', '27 Juillet 2025', '9 Aout 2025']
import TimesheetApprovalPeriodPicker from '../components/timesheet-approval-period-picker.vue';
import TimesheetApprovalEmployeeOverviewList from '../components/timesheet-approval-employee-overview-list-item.vue';
import TimesheetApprovalEmployeeOverviewList from '../components/timesheet-approval-employee-overview-list.vue';
</script>
<template>
<q-page padding class="q-pa-md bg-secondary">
<TimesheetApprovalPeriodPicker />
<TimesheetApprovalEmployeeOverviewList />
</q-page>
</template>

View File

@ -56,6 +56,160 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
"expenses": 95.25,
"mileage": 60,
"is_approved": false
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
}
];

View File

@ -62,6 +62,160 @@ const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
"expenses": 95.25,
"mileage": 60,
"is_approved": false
},
{
"employee_id": 'EMP-006',
"employee_name": 'Maxime Murray Gendron',
"regular_hours": 80,
"evening_hours": 0,
"emergency_hours": 0,
"overtime_hours": 0,
"expenses": 20000,
"mileage": 0,
"is_approved": false
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
},
{
"employee_id": '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
}
];