258 lines
10 KiB
Vue
258 lines
10 KiB
Vue
<script
|
|
setup
|
|
lang="ts"
|
|
>
|
|
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
|
|
|
import { onMounted, ref } from 'vue';
|
|
import { date, type QTableColumn } from 'quasar';
|
|
import { useUiStore } from 'src/stores/ui-store';
|
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
|
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
|
import { employee_list_columns, type EmployeeProfile, type EmployeeListFilters } from 'src/modules/employee-list/models/employee-profile.models';
|
|
|
|
const employee_list_api = useEmployeeListApi();
|
|
const employee_store = useEmployeeStore();
|
|
const timesheet_store = useTimesheetStore();
|
|
const ui_store = useUiStore();
|
|
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>[]): EmployeeProfile[] => {
|
|
let result = [...rows];
|
|
|
|
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;
|
|
});
|
|
}
|
|
|
|
if (terms.search_bar_string.trim().length > 0) {
|
|
const searchTerms = terms.search_bar_string.split(' ').map(s => s.trim().toLowerCase());
|
|
|
|
result = result.filter(row => {
|
|
const rowValues = Object.values(row).map(v => String(v ?? '').toLowerCase());
|
|
return searchTerms.every(term =>
|
|
rowValues.some(value => value.includes(term))
|
|
);
|
|
});
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await employee_list_api.getEmployeeList();
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="q-pa-lg">
|
|
<q-table
|
|
:key="filters.hide_inactive_users ? '1' : '0'"
|
|
dense
|
|
hide-pagination
|
|
title=" "
|
|
card-style="max-height: 70vh;"
|
|
:rows="employee_store.employee_list"
|
|
:columns="employee_list_columns"
|
|
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"
|
|
:style="$q.screen.lt.md ? '' : 'width: 80vw;'"
|
|
:table-class="$q.dark.isActive ? 'q-py-none q-mx-md rounded-10 bg-dark shadow-10' : 'q-py-none q-mx-md rounded-10 bg-white shadow-10'"
|
|
color="accent"
|
|
table-header-class="text-accent text-uppercase"
|
|
card-container-class="justify-center"
|
|
:grid="ui_store.user_preferences.is_employee_list_grid"
|
|
: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="visible_columns"
|
|
>
|
|
<template #top>
|
|
<div class="row full-width q-mb-sm">
|
|
<q-btn
|
|
push
|
|
color="accent"
|
|
icon="person_add"
|
|
:label="$t('shared.label.add')"
|
|
class="text-uppercase"
|
|
@click.stop="_evt => employee_store.openAddModifyDialog()"
|
|
/>
|
|
|
|
<q-space />
|
|
|
|
<q-btn-toggle
|
|
v-model="ui_store.user_preferences.is_employee_list_grid"
|
|
push
|
|
rounded
|
|
color="white"
|
|
text-color="accent"
|
|
toggle-color="accent"
|
|
class="q-mr-md"
|
|
:options="[
|
|
{ icon: 'grid_view', value: true },
|
|
{ icon: 'view_list', value: false },
|
|
]"
|
|
/>
|
|
<q-input
|
|
v-model="filters.search_bar_string"
|
|
outlined
|
|
dense
|
|
rounded
|
|
color="accent"
|
|
bg-color="white"
|
|
label-color="accent"
|
|
debounce="300"
|
|
:label="$t('shared.label.search')"
|
|
>
|
|
<template v-slot:append>
|
|
<q-icon
|
|
name="search"
|
|
color="accent"
|
|
/>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
<div class="row">
|
|
<q-space />
|
|
<q-checkbox
|
|
v-model="filters.hide_inactive_users"
|
|
color="accent"
|
|
:label="$t('employee_management.filter.hide_terminated')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<template #header="props">
|
|
<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" style="font-size: 1.2em;">
|
|
{{ $t(col.label) }}
|
|
</span>
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
|
|
<template #item="props">
|
|
<EmployeeListTableItem
|
|
:row="props.row"
|
|
:index="props.rowIndex"
|
|
@on-profile-click="employee_store.openAddModifyDialog"
|
|
/>
|
|
</template>
|
|
|
|
<template #body-cell="scope">
|
|
<q-td
|
|
:props="scope"
|
|
@click="employee_store.openAddModifyDialog(scope.row.email)"
|
|
>
|
|
<transition
|
|
appear
|
|
enter-active-class="animated fadeInUp slow"
|
|
leave-active-class="animated fadeOutDown"
|
|
mode="out-in"
|
|
>
|
|
<div
|
|
: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; ` + (scope.row.last_work_day === null ? '' : 'opacity: 0.5;')"
|
|
>
|
|
<div v-if="scope.col.name === 'first_name'">
|
|
<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>
|
|
</q-td>
|
|
</template>
|
|
|
|
<!-- Template for custome failed-to-load state -->
|
|
<template #no-data="{ message, filter }">
|
|
<div class="full-width column items-center text-accent q-gutter-sm">
|
|
<span class="text-h6 q-mt-xl">
|
|
{{ message }}
|
|
</span>
|
|
<q-icon
|
|
size="4em"
|
|
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</q-table>
|
|
</div>
|
|
</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;
|
|
}
|
|
|
|
thead tr th {
|
|
position: sticky;
|
|
z-index: 1;
|
|
}
|
|
|
|
thead tr:first-child th {
|
|
top: 0px;
|
|
}
|
|
|
|
&.q-table--loading thead tr:last-child th {
|
|
top: 48px;
|
|
}
|
|
|
|
tbody {
|
|
scroll-margin-top: 48px;
|
|
}
|
|
</style> |