feat(filters): add possibility of hiding inactive users, filter structure set up for future filters

This commit is contained in:
Nicolas Drolet 2025-12-19 17:20:03 -05:00
parent e665cf87ab
commit 9a70875f78
10 changed files with 235 additions and 159 deletions

View File

@ -284,6 +284,7 @@ export default {
mileage: "mileage", mileage: "mileage",
verified: "approved", verified: "approved",
unverified: "pending", unverified: "pending",
inactive: "inactive",
}, },
tooltip: { tooltip: {
button_detailed_view: "detailed view", button_detailed_view: "detailed view",

View File

@ -285,6 +285,7 @@ export default {
mileage: "kilométrage", mileage: "kilométrage",
verified: "approuvé", verified: "approuvé",
unverified: "à vérifier", unverified: "à vérifier",
inactive: "inactif",
}, },
tooltip: { tooltip: {
button_detailed_view: "vue détaillée", button_detailed_view: "vue détaillée",

View File

@ -9,11 +9,11 @@
dense dense
rounded rounded
debounce="300" debounce="300"
class="right-rounded"
:label="$t('shared.label.search')" :label="$t('shared.label.search')"
label-color="accent"
bg-color="white"
color="accent" color="accent"
bg-color="white"
label-color="accent"
class="text-primary"
> >
<template #prepend> <template #prepend>
<q-icon <q-icon

View File

@ -2,9 +2,9 @@
setup setup
lang="ts" lang="ts"
> >
import { ref } from 'vue'; import type { PayPeriodOverviewFilters } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
const boolean_filters = ref([]) const filters = defineModel<PayPeriodOverviewFilters>('filters', { required: true })
</script> </script>
<template> <template>
@ -17,15 +17,14 @@ import { ref } from 'vue';
<div class="col row"> <div class="col row">
<q-checkbox <q-checkbox
v-model="boolean_filters" v-model="filters.is_showing_inactive"
size="lg" size="lg"
val="inactive"
color="accent" color="accent"
label="show inactive" label="show inactive"
class="col" class="col"
/> />
<q-checkbox <q-checkbox
v-model="boolean_filters" v-model="filters.is_showing_team_only"
size="lg" size="lg"
val="team" val="team"
color="accent" color="accent"

View File

@ -24,16 +24,20 @@
> >
<q-card <q-card
class="rounded-10 shadow-5" class="rounded-10 shadow-5"
:style="`animation-delay: ${index / 15}s;`" :style="`animation-delay: ${index / 15}s; opacity: ${row.is_active ? '1' : '0.5'}; transform: scale(${row.is_active ? '1' : '0.9'})`"
> >
<!-- Card header with employee name and details button--> <!-- Card header with employee name and details button-->
<q-card-section <q-card-section
horizontal horizontal
class="q-py-none q-px-sm q-ma-none justify-between items-center bg-primary text-white" class="q-py-xs q-px-sm q-ma-none justify-between items-center bg-primary text-white"
> >
<div> <div>
<span class="text-h5 text-uppercase text-weight-medium text-accent q-mr-xs">{{ row.employee_name.split(' ')[0] <span
}}</span> class="text-h5 text-uppercase text-weight-medium q-mr-xs"
:class="row.is_active ? 'text-accent' : 'text-negative'"
>
{{ row.employee_name.split(' ')[0] }}
</span>
<span class="text-uppercase text-weight-light">{{ row.employee_name.split(' ')[1] }}</span> <span class="text-uppercase text-weight-light">{{ row.employee_name.split(' ')[1] }}</span>
</div> </div>
@ -45,7 +49,7 @@
unelevated unelevated
class="col-auto q-pa-none q-ma-none" class="col-auto q-pa-none q-ma-none"
color="accent" color="accent"
icon="work_history" icon="las la-chart-pie"
@click="emit('clickDetails', row)" @click="emit('clickDetails', row)"
> >
<q-tooltip <q-tooltip
@ -55,6 +59,8 @@
> >
{{ $t('timesheet_approvals.tooltip.button_detailed_view') }} {{ $t('timesheet_approvals.tooltip.button_detailed_view') }}
</q-tooltip> </q-tooltip>
<q-icon name="las la-chart-bar" color="accent"/>
</q-btn> </q-btn>
</q-card-section> </q-card-section>
@ -67,12 +73,12 @@
<div class="col column"> <div class="col column">
<span <span
class="text-weight-bold text-uppercase q-pa-none q-my-none" class="text-weight-bold text-uppercase q-pa-none q-my-none"
:class="row.regular_hours > 80 ? 'text-negative' : 'text-accent'" :class="row.regular_hours > 80 || !row.is_active ? 'text-negative' : 'text-accent'"
> {{ > {{
$t('shared.shift_type.regular') }} </span> $t('shared.shift_type.regular') }} </span>
<span <span
class="text-weight-bolder text-h3 q-py-none" class="text-weight-bolder text-h3 q-py-none"
:class="row.regular_hours > 80 ? 'text-negative' : ''" :class="row.regular_hours > 80 || !row.is_active ? 'text-negative' : ''"
> {{ row.regular_hours }} </span> > {{ row.regular_hours }} </span>
<q-separator class="q-mr-sm" /> <q-separator class="q-mr-sm" />
</div> </div>
@ -133,16 +139,20 @@
<q-card-section <q-card-section
horizontal horizontal
class="justify-between items-center text-weight-bold q-pa-none" class="justify-between items-center text-weight-bold q-pa-none"
:class="row.is_approved ? 'text-white bg-accent' : 'bg-dark text-accent'" :class="row.is_active ? (row.is_approved ? 'text-white bg-accent' : 'bg-dark text-accent') : 'bg-transparent'"
> >
<div class="col-auto"> <div
<span class="text-uppercase text-h6 q-ml-sm text-weight-bolder"> {{ row.total_hours }} </span> v-if="row.is_active"
class="col row full-width"
>
<div class="col">
<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> <span class="text-uppercase text-weight-bold text-caption q-ml-xs"> total </span>
</div> </div>
<div <div
class="col-auto q-py-xs q-px-md" class="col-auto q-py-xs q-px-md"
style="border: 1px solid var(--q-accent);"
> >
<q-checkbox <q-checkbox
v-model="modelApproval" v-model="modelApproval"
@ -158,6 +168,21 @@
:class="row.is_approved ? '' : 'text-accent'" :class="row.is_approved ? '' : 'text-accent'"
/> />
</div> </div>
</div>
<div
v-else
class="col row flex-center q-px-sm full-width"
>
<q-icon
name="block"
color="negative"
class="col-auto"
size="lg"
/>
<span class="col q-pl-sm text-uppercase text-weight-bold text-h5">{{
$t('timesheet_approvals.table.inactive') }}</span>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</transition> </transition>

View File

@ -1,14 +1,24 @@
<script setup lang="ts"> <script
/* eslint-disable */ setup
import { computed, ref } from 'vue'; lang="ts"
import OverviewListItem from 'src/modules/timesheet-approval/components/overview-list-item.vue'; >
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue'; /* eslint-disable */
import { useTimesheetStore } from 'src/stores/timesheet-store'; import OverviewListItem from 'src/modules/timesheet-approval/components/overview-list-item.vue';
import { overview_column_names, pay_period_overview_columns, type TimesheetOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models'; import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
import QTableFilters from 'src/modules/shared/components/q-table-filters.vue';
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
import OverviewListFilters from 'src/modules/timesheet-approval/components/overview-list-filters.vue';
const timesheet_store = useTimesheetStore(); import { computed, ref } from 'vue';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
import { overview_column_names, pay_period_overview_columns, PayPeriodOverviewFilters, type TimesheetOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
const visible_columns = ref<string[]>([
const timesheet_store = useTimesheetStore();
const timesheet_approval_api = useTimesheetApprovalApi();
const visible_columns = ref<string[]>([
overview_column_names.REGULAR, overview_column_names.REGULAR,
overview_column_names.EVENING, overview_column_names.EVENING,
overview_column_names.EMERGENCY, overview_column_names.EMERGENCY,
@ -17,17 +27,38 @@ const visible_columns = ref<string[]>([
overview_column_names.HOLIDAY, overview_column_names.HOLIDAY,
overview_column_names.OVERTIME, overview_column_names.OVERTIME,
overview_column_names.IS_APPROVED, overview_column_names.IS_APPROVED,
]); ]);
const is_showing_filters = ref(false);
const search_string = ref('');
const onClickedDetails = async (row: TimesheetOverview) => { const overview_rows = computed(() => timesheet_store.pay_period_overviews.filter(overview => overview));
const overview_filters = ref<PayPeriodOverviewFilters>({
is_showing_inactive: false,
is_showing_team_only: false,
supervisors: [],
name_search_string: search_string.value,
});
const onClickedDetails = async (row: TimesheetOverview) => {
timesheet_store.current_pay_period_overview = row; timesheet_store.current_pay_period_overview = row;
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(row.email); await timesheet_store.getTimesheetsByOptionalEmployeeEmail(row.email);
timesheet_store.is_details_dialog_open = true; timesheet_store.is_details_dialog_open = true;
}; };
const overview_rows = computed(() => timesheet_store.pay_period_overviews.filter(overview => overview)) const filterEmployeeRows = (rows: readonly TimesheetOverview[], terms: PayPeriodOverviewFilters): TimesheetOverview[] => {
let result = [...rows];
if (!terms.is_showing_inactive) {
result = result.filter(row => row.is_active);
}
// if (terms.name_search_string) {
// result = result.filter(row => row.employee_name.includes(terms.name_search_string ?? ''));
// }
return result;
};
</script> </script>
<template> <template>
@ -45,10 +76,12 @@ const onClickedDetails = async (row: TimesheetOverview) => {
:rows="overview_rows" :rows="overview_rows"
:columns="pay_period_overview_columns" :columns="pay_period_overview_columns"
row-key="email" row-key="email"
:filter="timesheet_store.search_filter"
:grid="timesheet_store.is_approval_grid_mode" :grid="timesheet_store.is_approval_grid_mode"
:dense="timesheet_store.is_approval_grid_mode" :dense="timesheet_store.is_approval_grid_mode"
hide-pagination hide-pagination
:pagination="{ sortBy: 'is_active' }"
:filter="overview_filters"
:filter-method="filterEmployeeRows"
color="accent" color="accent"
:rows-per-page-options="[0]" :rows-per-page-options="[0]"
card-container-class="justify-center" card-container-class="justify-center"
@ -59,6 +92,87 @@ const onClickedDetails = async (row: TimesheetOverview) => {
:no-results-label="$t('shared.error.no_search_results')" :no-results-label="$t('shared.error.no_search_results')"
:loading-label="$t('shared.label.loading')" :loading-label="$t('shared.label.loading')"
> >
<template #top>
<div class="column full-width">
<div
class="col-auto row items-start full-width q-px-lg"
:class="($q.platform.is.mobile ? 'column flex-center' : 'row q-mt-md') + (timesheet_store.is_approval_grid_mode ? '' : ' q-mb-md')"
>
<PayPeriodNavigator
@date-selected="timesheet_approval_api.getTimesheetOverviews"
@pressed-next-button="timesheet_approval_api.getTimesheetOverviews"
@pressed-previous-button="timesheet_approval_api.getTimesheetOverviews"
:class="$q.platform.is.mobile ? 'q-mb-sm' : ''"
style="height: 40px;"
/>
<q-space />
<div
class="col-auto row no-wrap items-start"
:class="$q.platform.is.mobile ? 'q-mb-md' : ''"
>
<q-btn-toggle
v-model="timesheet_store.is_approval_grid_mode"
push
rounded
color="white"
text-color="accent"
toggle-color="accent"
class="col-auto"
:class="$q.platform.is.mobile ? 'q-mb-sm' : 'q-mr-sm'"
:options="[
{ icon: 'grid_view', value: true },
{ icon: 'view_list', value: false },
]"
style="height: 40px;"
/>
<q-btn
push
rounded
icon="download"
color="accent"
:label="$q.screen.lt.md ? '' : $t('shared.label.download')"
class="col-auto q-mr-sm"
style="height: 40px;"
@click="timesheet_store.is_report_dialog_open = true"
/>
<QTableFilters
v-model:search="search_string"
class="col-auto q-mb-xs"
/>
<q-btn
flat
icon="filter_alt"
color="white"
:label="$q.platform.is.mobile ? '' : $t('shared.label.filter')"
class="col q-ml-sm self-stretch bg-accent"
style="border-radius: 5px 5px 0 0;"
@click="is_showing_filters = !is_showing_filters"
/>
</div>
</div>
<q-slide-transition>
<OverviewListFilters
v-if="is_showing_filters"
v-model:filters="overview_filters"
class="q-mx-lg col-auto"
/>
</q-slide-transition>
<q-separator
color="accent"
size="5px"
class="q-mx-lg"
/>
</div>
</template>
<template #header="props"> <template #header="props">
<q-tr <q-tr
:props="props" :props="props"

View File

@ -1,8 +1,9 @@
import type { QTableColumn } from "quasar"; import type { QTableColumn } from "quasar";
export interface TimesheetOverview { export class TimesheetOverview {
email: string; email: string;
employee_name: string; employee_name: string;
is_active: boolean;
regular_hours: number; regular_hours: number;
other_hours: { other_hours: {
evening_hours: number; evening_hours: number;
@ -16,6 +17,25 @@ export interface TimesheetOverview {
expenses: number; expenses: number;
mileage: number; mileage: number;
is_approved: boolean; is_approved: boolean;
constructor() {
this.email = '';
this.employee_name = 'John Doe';
this.is_active = true;
this.regular_hours = 0;
this.other_hours = {
evening_hours: 0,
emergency_hours: 0,
overtime_hours: 0,
sick_hours: 0,
holiday_hours: 0,
vacation_hours: 0,
}
this.total_hours = 0;
this.expenses = 0;
this.mileage = 0;
this.is_approved = false;
};
} }
export interface PayPeriodOverviewResponse { export interface PayPeriodOverviewResponse {
@ -28,22 +48,11 @@ export interface PayPeriodOverviewResponse {
employees_overview: TimesheetOverview[]; employees_overview: TimesheetOverview[];
} }
export const default_pay_period_overview: TimesheetOverview = { export interface PayPeriodOverviewFilters {
email: '', is_showing_inactive: boolean;
employee_name: '', is_showing_team_only: boolean;
regular_hours: -1, supervisors: string[];
other_hours: { name_search_string: string | number | null;
evening_hours: -1,
emergency_hours: -1,
overtime_hours: -1,
sick_hours: -1,
holiday_hours: -1,
vacation_hours: -1,
},
total_hours: -1,
expenses: -1,
mileage: -1,
is_approved: false
} }
export const overview_column_names = { export const overview_column_names = {
@ -59,6 +68,7 @@ export const overview_column_names = {
EXPENSES: 'expenses', EXPENSES: 'expenses',
MILEAGE: 'mileage', MILEAGE: 'mileage',
IS_APPROVED: 'is_approved', IS_APPROVED: 'is_approved',
IS_ACTIVE: 'is_active',
} }
export const pay_period_overview_columns: QTableColumn[] = [ export const pay_period_overview_columns: QTableColumn[] = [
@ -145,5 +155,11 @@ export const pay_period_overview_columns: QTableColumn[] = [
label: 'timesheet_approvals.table.is_approved', label: 'timesheet_approvals.table.is_approved',
field: 'is_approved', field: 'is_approved',
sortable: true, sortable: true,
},
{
name: overview_column_names.IS_ACTIVE,
label: 'timesheet_approvals.table.is_active',
field: 'is_active',
sortable: true,
} }
] ]

View File

@ -31,6 +31,7 @@
}>(); }>();
onMounted(async () => { onMounted(async () => {
if (mode === 'normal')
await timesheet_api.getTimesheetsByDate(date.formatDate(new Date(), 'YYYY-MM-DD')); await timesheet_api.getTimesheetsByDate(date.formatDate(new Date(), 'YYYY-MM-DD'));
}); });
</script> </script>

View File

@ -6,21 +6,16 @@
import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue'; import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue';
import OverviewList from 'src/modules/timesheet-approval/components/overview-list.vue'; import OverviewList from 'src/modules/timesheet-approval/components/overview-list.vue';
import DetailsDialog from 'src/modules/timesheet-approval/components/details-dialog.vue'; import DetailsDialog from 'src/modules/timesheet-approval/components/details-dialog.vue';
import QTableFilters from 'src/modules/shared/components/q-table-filters.vue';
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
import OverviewListFilters from 'src/modules/timesheet-approval/components/overview-list-filters.vue';
import OverviewReport from 'src/modules/timesheet-approval/components/overview-report.vue'; import OverviewReport from 'src/modules/timesheet-approval/components/overview-report.vue';
import { date } from 'quasar'; import { date } from 'quasar';
import { onMounted, ref } from 'vue'; import { onMounted } from 'vue';
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api'; 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';
const timesheet_approval_api = useTimesheetApprovalApi(); const timesheet_approval_api = useTimesheetApprovalApi();
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const is_showing_filters = ref(false);
onMounted(async () => { onMounted(async () => {
await timesheet_approval_api.getTimesheetOverviewsByDate(date.formatDate(new Date(), 'YYYY-MM-DD')); await timesheet_approval_api.getTimesheetOverviewsByDate(date.formatDate(new Date(), 'YYYY-MM-DD'));
}); });
@ -44,80 +39,6 @@
:timesheets="timesheet_store.timesheets" :timesheets="timesheet_store.timesheets"
/> />
<div
class="col-auto row items-start full-width q-px-lg"
:class="($q.platform.is.mobile ? 'column flex-center' : 'row q-mt-md') + (timesheet_store.is_approval_grid_mode ? '' : ' q-mb-md')"
>
<PayPeriodNavigator
@date-selected="timesheet_approval_api.getTimesheetOverviews"
@pressed-next-button="timesheet_approval_api.getTimesheetOverviews"
@pressed-previous-button="timesheet_approval_api.getTimesheetOverviews"
:class="$q.platform.is.mobile ? 'q-mb-sm' : ''"
style="height: 40px;"
/>
<q-space />
<div
class="col-auto row no-wrap items-start"
:class="$q.platform.is.mobile ? 'q-mb-md' : ''"
>
<q-btn-toggle
v-model="timesheet_store.is_approval_grid_mode"
push
rounded
color="white"
text-color="accent"
toggle-color="accent"
class="col-auto"
:class="$q.platform.is.mobile ? 'q-mb-sm' : 'q-mr-sm'"
:options="[
{ icon: 'grid_view', value: true },
{ icon: 'view_list', value: false },
]"
style="height: 40px;"
/>
<q-btn
push
rounded
icon="download"
color="accent"
:label="$q.screen.lt.md ? '' : $t('shared.label.download')"
class="col-auto q-mr-sm"
style="height: 40px;"
@click="timesheet_store.is_report_dialog_open = true"
/>
<QTableFilters
v-model:search="timesheet_store.search_filter"
class="col-auto q-mb-xs"
/>
<q-btn
flat
icon="filter_alt"
color="white"
:label="$q.platform.is.mobile ? '' : $t('shared.label.filter')"
class="col q-ml-sm self-stretch bg-accent"
style="border-radius: 5px 5px 0 0;"
@click="is_showing_filters = !is_showing_filters"
/>
</div>
</div>
<q-slide-transition class="col-auto">
<OverviewListFilters
v-if="is_showing_filters"
class="q-mx-lg col-auto"
/>
</q-slide-transition>
<q-separator
color="accent"
size="5px"
class="q-mx-lg"
/>
<OverviewReport /> <OverviewReport />
<OverviewList class="col" /> <OverviewList class="col" />

View File

@ -23,7 +23,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const is_details_dialog_open = ref(false); const is_details_dialog_open = ref(false);
const selected_employee_name = ref<string>(); const selected_employee_name = ref<string>();
const current_pay_period_overview = ref<TimesheetOverview>(); const current_pay_period_overview = ref<TimesheetOverview>();
const search_filter = ref<string | number | null>('');
const is_approval_grid_mode = ref<boolean>(true); const is_approval_grid_mode = ref<boolean>(true);
const pay_period_report = ref(); const pay_period_report = ref();
@ -147,7 +146,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
is_report_dialog_open, is_report_dialog_open,
is_approval_grid_mode, is_approval_grid_mode,
is_details_dialog_open, is_details_dialog_open,
search_filter,
pay_period, pay_period,
pay_period_overviews, pay_period_overviews,
current_pay_period_overview, current_pay_period_overview,