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
|
// add your custom rules here
|
||||||
rules: {
|
rules: {
|
||||||
'prefer-promise-reject-errors': 'off',
|
'prefer-promise-reject-errors': 'off',
|
||||||
|
|
||||||
// warn about unused but underscored variables
|
// warn about unused but underscored variables
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
'no-unused-vars': [
|
'no-unused-vars': [
|
||||||
'warn',
|
'warn',
|
||||||
{ argsIgnorePattern: '^_' }
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||||
],
|
],
|
||||||
|
|
||||||
// allow debugger during development only
|
// allow debugger during development only
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
import { defineBoot } from '#q-app/wrappers';
|
import { defineBoot } from '#q-app/wrappers';
|
||||||
import axios, { type AxiosInstance } from 'axios';
|
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 {
|
declare namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
NODE_ENV: string;
|
NODE_ENV: string;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ export default {
|
||||||
supervisor: "Supervisor",
|
supervisor: "Supervisor",
|
||||||
company: "Company",
|
company: "Company",
|
||||||
is_supervisor: "is a supervisor",
|
is_supervisor: "is a supervisor",
|
||||||
|
active: "active",
|
||||||
|
inactive: "inactive",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ export default {
|
||||||
supervisor: "superviseur",
|
supervisor: "superviseur",
|
||||||
company: "Compagnie",
|
company: "Compagnie",
|
||||||
is_supervisor: "est un superviseur",
|
is_supervisor: "est un superviseur",
|
||||||
|
active: "actif",
|
||||||
|
inactive: "inactif",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,21 @@
|
||||||
<template>
|
<template>
|
||||||
<transition
|
<transition
|
||||||
appear
|
appear
|
||||||
enter-active-class="animated fadeInUp slow"
|
enter-active-class="animated fadeInUp fast"
|
||||||
|
leave-active-class="animated fadeOutDown fast"
|
||||||
mode="out-in"
|
mode="out-in"
|
||||||
>
|
>
|
||||||
<q-card
|
<q-card
|
||||||
|
|
||||||
v-ripple
|
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"
|
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="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)"
|
@click="emit('onProfileClick', row.email)"
|
||||||
>
|
>
|
||||||
<q-card-section class="col-6 text-center">
|
<q-card-section class="col-6 text-center">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:color="row.last_work_day === undefined ? 'accent' : 'negative'"
|
:color="row.last_work_day === null ? 'accent' : 'negative'"
|
||||||
size="8em"
|
size="8em"
|
||||||
class="shadow-3 q-mb-md"
|
class="shadow-3 q-mb-md"
|
||||||
>
|
>
|
||||||
|
|
@ -51,12 +53,12 @@
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ellipsis"
|
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 }}
|
{{ row.first_name }} {{ row.last_name }}
|
||||||
</div>
|
</div>
|
||||||
<q-separator
|
<q-separator
|
||||||
color="accent"
|
:color="row.last_work_day === null ? 'accent' : 'negative'"
|
||||||
class="q-mx-sm q-mt-xs"
|
class="q-mx-sm q-mt-xs"
|
||||||
/>
|
/>
|
||||||
<div class=" ellipsis-2-lines text-caption">{{ row.job_title }}</div>
|
<div class=" ellipsis-2-lines text-caption">{{ row.job_title }}</div>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
|
/* eslint-disable */
|
||||||
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
@ -16,68 +17,59 @@
|
||||||
const employee_store = useEmployeeStore();
|
const employee_store = useEmployeeStore();
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
const ui_store = useUiStore();
|
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>({
|
const filters = ref<EmployeeListFilters>({
|
||||||
search_bar_string: '',
|
search_bar_string: '',
|
||||||
hide_inactive_users: true,
|
hide_inactive_users: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterEmployeeRows = (
|
const filterEmployeeRows = (rows: readonly EmployeeProfile[], terms: EmployeeListFilters): EmployeeProfile[] => {
|
||||||
rows: readonly EmployeeProfile[],
|
let result = [...rows];
|
||||||
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);
|
|
||||||
|
|
||||||
if (terms.hide_inactive_users === true) {
|
if (terms.hide_inactive_users) {
|
||||||
active_rows = active_rows.filter(row =>
|
const now = new Date();
|
||||||
row.last_work_day === null
|
result = result.filter(row => {
|
||||||
// {
|
if (!row.last_work_day) return true;
|
||||||
// if (row.last_work_day === null) return true;
|
const inactiveDate = date.extractDate(row.last_work_day, 'YYYY-MM-DD');
|
||||||
|
const limit = new Date(inactiveDate);
|
||||||
// const inactive_date = date.extractDate(row.last_work_day!, 'YYYY-MM-DD');
|
limit.setDate(limit.getDate() + 14);
|
||||||
// const inactive_date_limit = new Date();
|
return limit >= now;
|
||||||
// inactive_date_limit.setDate(inactive_date.getDate() + 14)
|
});
|
||||||
|
|
||||||
// return inactive_date_limit > inactive_date
|
|
||||||
// }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
result = result.filter(row => {
|
||||||
console.log('more filtering!!')
|
const rowValues = Object.values(row).map(v => String(v ?? '').toLowerCase());
|
||||||
const search_terms = terms.search_bar_string.split(',');
|
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 result;
|
||||||
};
|
};
|
||||||
|
|
||||||
return filtered_rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
is_loading_list.value = true;
|
|
||||||
await employee_list_api.getEmployeeList();
|
await employee_list_api.getEmployeeList();
|
||||||
is_loading_list.value = false;
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="q-pa-lg">
|
<div class="q-pa-lg">
|
||||||
<q-table
|
<q-table
|
||||||
|
:key="filters.hide_inactive_users ? '1' : '0'"
|
||||||
dense
|
dense
|
||||||
hide-pagination
|
hide-pagination
|
||||||
virtual-scroll
|
|
||||||
title=" "
|
title=" "
|
||||||
card-style="max-height: 70vh;"
|
card-style="max-height: 70vh;"
|
||||||
:rows="employee_store.employee_list"
|
:rows="employee_store.employee_list"
|
||||||
:columns="employee_list_columns"
|
:columns="employee_list_columns"
|
||||||
row-key="name"
|
row-key="email"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
|
:pagination="{ sortBy: 'last_work_day', descending: true, }"
|
||||||
:filter="filters"
|
:filter="filters"
|
||||||
:filter-method="filterEmployeeRows"
|
:filter-method="filterEmployeeRows"
|
||||||
class="bg-transparent no-shadow sticky-header-table"
|
class="bg-transparent no-shadow sticky-header-table"
|
||||||
|
|
@ -87,11 +79,11 @@
|
||||||
table-header-class="text-accent text-uppercase"
|
table-header-class="text-accent text-uppercase"
|
||||||
card-container-class="justify-center"
|
card-container-class="justify-center"
|
||||||
:grid="ui_store.user_preferences.is_employee_list_grid"
|
: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-data-label="$t('shared.error.no_data_found')"
|
||||||
: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')"
|
||||||
:visible-columns="['first_name', 'email', 'company', 'supervisor_full_name', 'company_name', 'job_title']"
|
:visible-columns="visible_columns"
|
||||||
>
|
>
|
||||||
<template #top>
|
<template #top>
|
||||||
<div class="row full-width q-mb-sm">
|
<div class="row full-width q-mb-sm">
|
||||||
|
|
@ -127,6 +119,7 @@
|
||||||
color="accent"
|
color="accent"
|
||||||
bg-color="white"
|
bg-color="white"
|
||||||
label-color="accent"
|
label-color="accent"
|
||||||
|
debounce="300"
|
||||||
:label="$t('shared.label.search')"
|
:label="$t('shared.label.search')"
|
||||||
>
|
>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
|
|
@ -148,16 +141,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #header="props">
|
<template #header="props">
|
||||||
<q-tr
|
<q-tr :props="props">
|
||||||
:props="props"
|
|
||||||
class="bg-primary"
|
|
||||||
>
|
|
||||||
<q-th
|
<q-th
|
||||||
v-for="col in props.cols"
|
v-for="col in props.cols"
|
||||||
:key="col.name"
|
:key="col.name"
|
||||||
:props="props"
|
: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) }}
|
{{ $t(col.label) }}
|
||||||
</span>
|
</span>
|
||||||
</q-th>
|
</q-th>
|
||||||
|
|
@ -187,12 +177,32 @@
|
||||||
:key="scope.rowIndex + (timesheet_store.pay_period?.pay_period_no ?? 0)"
|
:key="scope.rowIndex + (timesheet_store.pay_period?.pay_period_no ?? 0)"
|
||||||
class="rounded-5 cursor-pointer"
|
class="rounded-5 cursor-pointer"
|
||||||
style="font-size: 1.2em;"
|
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'">
|
<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>
|
<span class="text-uppercase text-weight-light">{{ scope.row.last_name }}</span>
|
||||||
</div>
|
</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>
|
<span v-else>{{ scope.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
@ -216,6 +226,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.sticky-header-table thead tr:first-child th {
|
||||||
background-color: var(--q-primary);
|
background-color: var(--q-primary);
|
||||||
margin-top: none;
|
margin-top: none;
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,11 @@ export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
||||||
field: 'last_work_day',
|
field: 'last_work_day',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
sortable: true,
|
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
|
dense
|
||||||
:icon="props.value ? 'lock' : 'lock_open'"
|
:icon="props.value ? 'lock' : 'lock_open'"
|
||||||
:color="props.value ? 'white' : 'grey-5'"
|
:color="props.value ? 'white' : 'grey-5'"
|
||||||
class="rounded-5 z-top"
|
class="rounded-5 "
|
||||||
:class="props.value ? 'bg-accent' : ''"
|
:class="props.value ? 'bg-accent' : ''"
|
||||||
@click.stop="props.row.is_approved = !props.row.is_approved"
|
@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,
|
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||||
import 'quasar/dist/types/feature-flag';
|
import 'quasar/dist/types/feature-flag';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user