feat(employee-list): complete functional advanced search for employee list
enabling or disabling hide-inactive-employees will hide them or show them at the top. Also added more functionality to the search bar-- it can match many columns for terms separated by spaces in the search field. i.e. typing Bourdo and Solucom separated by commas will show all employees that have those words in any of the columns
This commit is contained in:
parent
5bdf1e5eaa
commit
a0d87a0013
|
|
@ -63,14 +63,17 @@ export default defineConfigWithVueTs(
|
|||
}
|
||||
},
|
||||
|
||||
files: ['**/*.ts', '**/*.vue'],
|
||||
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
|
||||
// warn about unused but underscored variables
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
'no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_' }
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||
],
|
||||
|
||||
// allow debugger during development only
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable */
|
||||
import { defineBoot } from '#q-app/wrappers';
|
||||
import axios, { type AxiosInstance } from 'axios';
|
||||
|
||||
|
|
|
|||
2
src/env.d.ts
vendored
2
src/env.d.ts
vendored
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable */
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: string;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export default {
|
|||
supervisor: "Supervisor",
|
||||
company: "Company",
|
||||
is_supervisor: "is a supervisor",
|
||||
active: "active",
|
||||
inactive: "inactive",
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export default {
|
|||
supervisor: "superviseur",
|
||||
company: "Compagnie",
|
||||
is_supervisor: "est un superviseur",
|
||||
active: "actif",
|
||||
inactive: "inactif",
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -21,19 +21,21 @@
|
|||
<template>
|
||||
<transition
|
||||
appear
|
||||
enter-active-class="animated fadeInUp slow"
|
||||
enter-active-class="animated fadeInUp fast"
|
||||
leave-active-class="animated fadeOutDown fast"
|
||||
mode="out-in"
|
||||
>
|
||||
<q-card
|
||||
|
||||
v-ripple
|
||||
class="column col-xs-6 col-sm-4 col-md-3 col-lg-2 no-wrap rounded-15 cursor-pointer q-ma-sm"
|
||||
style="max-width: 230px;"
|
||||
:style="`animation-delay: ${index / 25}s;`"
|
||||
:style="(`animation-delay: ${index / 25}s; `) + (row.last_work_day === null ? '' : 'opacity: 0.6;')"
|
||||
@click="emit('onProfileClick', row.email)"
|
||||
>
|
||||
<q-card-section class="col-6 text-center">
|
||||
<q-avatar
|
||||
:color="row.last_work_day === undefined ? 'accent' : 'negative'"
|
||||
:color="row.last_work_day === null ? 'accent' : 'negative'"
|
||||
size="8em"
|
||||
class="shadow-3 q-mb-md"
|
||||
>
|
||||
|
|
@ -51,12 +53,12 @@
|
|||
>
|
||||
<div
|
||||
class="ellipsis"
|
||||
:class="row.last_work_day === undefined ? 'text-accent' : 'text-negative'"
|
||||
:class="row.last_work_day === null ? 'text-accent' : 'text-negative'"
|
||||
>
|
||||
{{ row.first_name }} {{ row.last_name }}
|
||||
</div>
|
||||
<q-separator
|
||||
color="accent"
|
||||
:color="row.last_work_day === null ? 'accent' : 'negative'"
|
||||
class="q-mx-sm q-mt-xs"
|
||||
/>
|
||||
<div class=" ellipsis-2-lines text-caption">{{ row.job_title }}</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
/* eslint-disable */
|
||||
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
|
@ -16,68 +17,59 @@
|
|||
const employee_store = useEmployeeStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const ui_store = useUiStore();
|
||||
const is_loading_list = ref<boolean>(true);
|
||||
const visible_columns = ref<(keyof EmployeeProfile)[]>(['first_name', 'email', 'company_name', 'supervisor_full_name', 'company_name', 'job_title', 'last_work_day']);
|
||||
|
||||
const filters = ref<EmployeeListFilters>({
|
||||
search_bar_string: '',
|
||||
hide_inactive_users: true,
|
||||
});
|
||||
|
||||
const filterEmployeeRows = (
|
||||
rows: readonly EmployeeProfile[],
|
||||
terms: EmployeeListFilters,
|
||||
_cols: readonly QTableColumn<EmployeeProfile>[],
|
||||
_getCellValue: (col: QTableColumn<EmployeeProfile>, row: EmployeeProfile) => unknown
|
||||
): EmployeeProfile[] => {
|
||||
let active_rows: EmployeeProfile[] = Array.from(rows);
|
||||
console.log('active rows at start: ', active_rows);
|
||||
const filterEmployeeRows = (rows: readonly EmployeeProfile[], terms: EmployeeListFilters): EmployeeProfile[] => {
|
||||
let result = [...rows];
|
||||
|
||||
if (terms.hide_inactive_users === true) {
|
||||
active_rows = active_rows.filter(row =>
|
||||
row.last_work_day === null
|
||||
// {
|
||||
// if (row.last_work_day === null) return true;
|
||||
|
||||
// const inactive_date = date.extractDate(row.last_work_day!, 'YYYY-MM-DD');
|
||||
// const inactive_date_limit = new Date();
|
||||
// inactive_date_limit.setDate(inactive_date.getDate() + 14)
|
||||
|
||||
// return inactive_date_limit > inactive_date
|
||||
// }
|
||||
);
|
||||
if (terms.hide_inactive_users) {
|
||||
const now = new Date();
|
||||
result = result.filter(row => {
|
||||
if (!row.last_work_day) return true;
|
||||
const inactiveDate = date.extractDate(row.last_work_day, 'YYYY-MM-DD');
|
||||
const limit = new Date(inactiveDate);
|
||||
limit.setDate(limit.getDate() + 14);
|
||||
return limit >= now;
|
||||
});
|
||||
}
|
||||
|
||||
let filtered_rows: EmployeeProfile[] = Array.from(active_rows);
|
||||
if (terms.search_bar_string.trim().length > 0) {
|
||||
const searchTerms = terms.search_bar_string.split(' ').map(s => s.trim().toLowerCase());
|
||||
|
||||
if (terms.search_bar_string.length > 0) {
|
||||
console.log('more filtering!!')
|
||||
const search_terms = terms.search_bar_string.split(',');
|
||||
result = result.filter(row => {
|
||||
const rowValues = Object.values(row).map(v => String(v ?? '').toLowerCase());
|
||||
return searchTerms.every(term =>
|
||||
rowValues.some(value => value.includes(term))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
filtered_rows = active_rows.filter(row => Object.values(row).some(value => search_terms.includes(value)));
|
||||
};
|
||||
|
||||
return filtered_rows;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
is_loading_list.value = true;
|
||||
await employee_list_api.getEmployeeList();
|
||||
is_loading_list.value = false;
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="q-pa-lg">
|
||||
<q-table
|
||||
:key="filters.hide_inactive_users ? '1' : '0'"
|
||||
dense
|
||||
hide-pagination
|
||||
virtual-scroll
|
||||
title=" "
|
||||
card-style="max-height: 70vh;"
|
||||
:rows="employee_store.employee_list"
|
||||
:columns="employee_list_columns"
|
||||
row-key="name"
|
||||
row-key="email"
|
||||
:rows-per-page-options="[0]"
|
||||
:pagination="{ sortBy: 'last_work_day', descending: true, }"
|
||||
:filter="filters"
|
||||
:filter-method="filterEmployeeRows"
|
||||
class="bg-transparent no-shadow sticky-header-table"
|
||||
|
|
@ -87,11 +79,11 @@
|
|||
table-header-class="text-accent text-uppercase"
|
||||
card-container-class="justify-center"
|
||||
:grid="ui_store.user_preferences.is_employee_list_grid"
|
||||
:loading="is_loading_list"
|
||||
:loading="employee_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')"
|
||||
:visible-columns="['first_name', 'email', 'company', 'supervisor_full_name', 'company_name', 'job_title']"
|
||||
:visible-columns="visible_columns"
|
||||
>
|
||||
<template #top>
|
||||
<div class="row full-width q-mb-sm">
|
||||
|
|
@ -127,6 +119,7 @@
|
|||
color="accent"
|
||||
bg-color="white"
|
||||
label-color="accent"
|
||||
debounce="300"
|
||||
:label="$t('shared.label.search')"
|
||||
>
|
||||
<template v-slot:append>
|
||||
|
|
@ -148,16 +141,13 @@
|
|||
</template>
|
||||
|
||||
<template #header="props">
|
||||
<q-tr
|
||||
:props="props"
|
||||
class="bg-primary"
|
||||
>
|
||||
<q-tr :props="props">
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
>
|
||||
<span class="text-uppercase text-weight-bolder text-white text-h6">
|
||||
<span class="text-uppercase text-weight-bolder text-white" style="font-size: 1.2em;">
|
||||
{{ $t(col.label) }}
|
||||
</span>
|
||||
</q-th>
|
||||
|
|
@ -187,12 +177,32 @@
|
|||
:key="scope.rowIndex + (timesheet_store.pay_period?.pay_period_no ?? 0)"
|
||||
class="rounded-5 cursor-pointer"
|
||||
style="font-size: 1.2em;"
|
||||
:style="`animation-delay: ${scope.rowIndex / 30}s;`"
|
||||
:style="`animation-delay: ${scope.rowIndex / 30}s; ` + (scope.row.last_work_day === null ? '' : 'opacity: 0.5;')"
|
||||
>
|
||||
<div v-if="scope.col.name === 'first_name'">
|
||||
<span class="text-h5 text-uppercase text-accent q-mr-xs">{{ scope.value }}</span>
|
||||
<span
|
||||
class="text-h5 text-uppercase q-mr-xs"
|
||||
:class="scope.row.last_work_day === null ? 'text-accent' : 'text-negative'"
|
||||
>{{ scope.value }}</span>
|
||||
<span class="text-uppercase text-weight-light">{{ scope.row.last_name }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="scope.col.name === 'last_work_day'">
|
||||
<q-badge
|
||||
:color="scope.row.last_work_day === null ? 'accent' : 'negative'"
|
||||
class="row rounded-50 q-px-sm self-center"
|
||||
>
|
||||
<span class="text-bold text-uppercase q-mr-sm">
|
||||
{{ scope.row.last_work_day === null ? $t('employee_list.table.active') :
|
||||
$t('employee_list.table.inactive') }}
|
||||
</span>
|
||||
<q-icon
|
||||
:name="scope.row.last_work_day === null ? 'check' : 'clear'"
|
||||
size="xs"
|
||||
/>
|
||||
</q-badge>
|
||||
</div>
|
||||
|
||||
<span v-else>{{ scope.value }}</span>
|
||||
</div>
|
||||
</transition>
|
||||
|
|
@ -216,6 +226,15 @@
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.q-table__card .q-table__sort-icon) {
|
||||
fill: white !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
:deep(.q-table--dense .q-table__sort-icon) {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.sticky-header-table thead tr:first-child th {
|
||||
background-color: var(--q-primary);
|
||||
margin-top: none;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
|||
field: 'last_work_day',
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
sort: (a: string | null, b: string | null) => {
|
||||
if (a === null && b === null) return 0;
|
||||
else if (a === null && b !== null) return 1;
|
||||
else return -1;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@
|
|||
dense
|
||||
:icon="props.value ? 'lock' : 'lock_open'"
|
||||
:color="props.value ? 'white' : 'grey-5'"
|
||||
class="rounded-5 z-top"
|
||||
class="rounded-5 "
|
||||
:class="props.value ? 'bg-accent' : ''"
|
||||
@click.stop="props.row.is_approved = !props.row.is_approved"
|
||||
/>
|
||||
|
|
|
|||
1
src/stores/store-flag.d.ts
vendored
1
src/stores/store-flag.d.ts
vendored
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable */
|
||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||
import 'quasar/dist/types/feature-flag';
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user