refactor(presets): set up work for schedule presets, minor cleanup of other modules
This commit is contained in:
parent
8852f5990b
commit
2affa8470b
|
|
@ -13,6 +13,10 @@ export default {
|
|||
},
|
||||
|
||||
employee_management: {
|
||||
add_employee: "Add employee",
|
||||
modify_employee: "Modify employee",
|
||||
access_label: "access",
|
||||
details_label: "details",
|
||||
module_access: {
|
||||
dashboard: "Dashboard",
|
||||
employee_list: "employee list",
|
||||
|
|
@ -31,10 +35,10 @@ export default {
|
|||
none_description: "Uncheck all modules",
|
||||
usage_description: "You can use roles to enable preset modules, add or remove modules individually, or both",
|
||||
},
|
||||
add_employee: "Add employee",
|
||||
modify_employee: "Modify employee",
|
||||
access_label: "access",
|
||||
details_label: "details",
|
||||
filter: {
|
||||
show_terminated: "Show inactive employees",
|
||||
sort_by_tags: "sort by tags",
|
||||
},
|
||||
},
|
||||
|
||||
login: {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ export default {
|
|||
},
|
||||
|
||||
employee_management: {
|
||||
add_employee: "Ajouter employé",
|
||||
modify_employee: "Modifier employé",
|
||||
access_label: "accès",
|
||||
details_label: "détails",
|
||||
module_access: {
|
||||
dashboard: "Accueil",
|
||||
employee_list: "Répertoire du personnel",
|
||||
|
|
@ -31,10 +35,10 @@ export default {
|
|||
none_description: "Enlever tous les accès",
|
||||
usage_description: "Vous pouvez utiliser les rôles pour sélectionner des modules prédéfinis, enlever ou ajouter des modules individuellement, ou les deux",
|
||||
},
|
||||
add_employee: "Ajouter employé",
|
||||
modify_employee: "Modifier employé",
|
||||
access_label: "accès",
|
||||
details_label: "détails",
|
||||
filter: {
|
||||
show_terminated: "Afficher les employés inactifs",
|
||||
sort_by_tags: "filtrer par identifiants",
|
||||
},
|
||||
},
|
||||
|
||||
login: {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@
|
|||
import { onMounted, ref } from 'vue';
|
||||
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 } 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 is_loading_list = ref<boolean>(true);
|
||||
|
||||
|
|
@ -48,34 +50,10 @@
|
|||
: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']"
|
||||
@row-click="() => console.log('click!')"
|
||||
>
|
||||
<template #header="props">
|
||||
<q-tr
|
||||
:props="props"
|
||||
class="bg-accent"
|
||||
>
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
>
|
||||
<span class="text-uppercase text-weight-bolder text-white">
|
||||
{{ $t(col.label) }}
|
||||
</span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
|
||||
<template v-slot:item="props">
|
||||
<EmployeeListTableItem
|
||||
:row="props.row"
|
||||
:index="props.rowIndex"
|
||||
@on-profile-click="employee_store.openAddModifyDialog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:top>
|
||||
<template #top>
|
||||
<div class="row full-width q-mb-sm">
|
||||
<q-btn
|
||||
push
|
||||
|
|
@ -121,17 +99,57 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template #body-cell="scope">
|
||||
<q-td
|
||||
:props="scope"
|
||||
class="text-weight-medium"
|
||||
<template #header="props">
|
||||
<q-tr
|
||||
:props="props"
|
||||
class="bg-primary"
|
||||
>
|
||||
<span >{{ scope.value }}</span>
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
>
|
||||
<span class="text-uppercase text-weight-bolder text-white text-h6">
|
||||
{{ $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">
|
||||
<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"
|
||||
style="font-size: 1.2em;"
|
||||
:style="`animation-delay: ${scope.rowIndex / 30}s;`"
|
||||
>
|
||||
<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-uppercase text-weight-light">{{ scope.row.last_name }}</span>
|
||||
</div>
|
||||
<span v-else>{{ scope.value }}</span>
|
||||
</div>
|
||||
</transition>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<!-- Template for custome failed-to-load state -->
|
||||
<template v-slot:no-data="{ message, filter }">
|
||||
<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 }}
|
||||
|
|
@ -146,21 +164,26 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="sass">
|
||||
.sticky-header-table
|
||||
thead tr:first-child th
|
||||
background-color: var(--q-accent)
|
||||
margin-top: none
|
||||
<style scoped>
|
||||
.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
|
||||
thead tr th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.q-table--loading thead tr:last-child th
|
||||
top: 48px
|
||||
thead tr:first-child th {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
tbody
|
||||
scroll-margin-top: 48px
|
||||
&.q-table--loading thead tr:last-child th {
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
tbody {
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,13 +5,15 @@
|
|||
import AddModifyDialogForm from 'src/modules/employee-list/components/employee/add-modify-dialog-form.vue';
|
||||
import AddModifyDialogAccess from 'src/modules/employee-list/components/employee/add-modify-dialog-access.vue';
|
||||
|
||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||
import { ref } from 'vue';
|
||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||
import { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||
|
||||
const employee_store = useEmployeeStore();
|
||||
const current_step = ref<'form' | 'access'>('form');
|
||||
const transition_in_animation = ref('fadeInRight');
|
||||
const transition_out_animation = ref('fadeOutLeft');
|
||||
const initial_employee_profile = ref(new EmployeeProfile)
|
||||
|
||||
const getNextMenu = (animation_in: string, animation_out: string, next_step: 'form' | 'access') => {
|
||||
transition_in_animation.value = animation_in;
|
||||
|
|
@ -25,6 +27,7 @@
|
|||
v-model="employee_store.is_add_modify_dialog_open"
|
||||
full-width
|
||||
@beforeShow="current_step = 'form'"
|
||||
@show="Object.assign(initial_employee_profile, employee_store.employee)"
|
||||
>
|
||||
<div
|
||||
class="column bg-secondary rounded-10"
|
||||
|
|
@ -119,13 +122,13 @@
|
|||
/>
|
||||
</div> -->
|
||||
|
||||
<q-btn
|
||||
square
|
||||
color="accent"
|
||||
:label="employee_store.management_mode === 'add_employee' ? $t('shared.label.save') : $t('shared.label.update')"
|
||||
class="col-auto q-py-sm shadow-up-5"
|
||||
@click="employee_store.createOrUpdateEmployee(employee_store.employee)"
|
||||
/>
|
||||
<q-btn
|
||||
square
|
||||
color="accent"
|
||||
:label="employee_store.management_mode === 'add_employee' ? $t('shared.label.save') : $t('shared.label.update')"
|
||||
class="col-auto q-py-sm shadow-up-5"
|
||||
@click="employee_store.createOrUpdateEmployee(employee_store.employee)"
|
||||
/>
|
||||
<q-inner-loading :showing="employee_store.is_loading" />
|
||||
</div>
|
||||
</q-dialog>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class EmployeeProfile {
|
|||
export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
||||
{
|
||||
name: 'first_name',
|
||||
label: 'employee_list.table.first_name',
|
||||
label: 'timesheet_approvals.table.full_name',
|
||||
field: 'first_name',
|
||||
align: 'left'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -39,4 +39,9 @@
|
|||
:deep(.q-field__control-container) {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
:deep(.q-field__control::before) {
|
||||
border: 1px solid var(--q-accent) !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
export interface shiftColor {
|
||||
type_label: string;
|
||||
background_color: string;
|
||||
font_color: string;
|
||||
}
|
||||
|
||||
export const shift_type_legend: shiftColor[] = [
|
||||
{
|
||||
type_label: 'shared.shift_type.regular',
|
||||
background_color: 'blue-grey-4',
|
||||
font_color: 'blue-grey-8',
|
||||
},
|
||||
{
|
||||
type_label: 'shared.shift_type.evening',
|
||||
background_color: 'warning',
|
||||
font_color: 'blue-grey-2',
|
||||
},
|
||||
{
|
||||
type_label: 'shared.shift_type.emergency',
|
||||
background_color: 'amber-10',
|
||||
font_color: 'blue-grey-2',
|
||||
},
|
||||
{
|
||||
type_label: 'shared.shift_type.overtime',
|
||||
background_color: 'negative',
|
||||
font_color: 'blue-grey-2',
|
||||
},
|
||||
{
|
||||
type_label: 'shared.shift_type.vacation',
|
||||
background_color: 'purple-10',
|
||||
font_color: 'blue-grey-2',
|
||||
},
|
||||
{
|
||||
type_label: 'shared.shift_type.holiday',
|
||||
background_color: 'purple-8',
|
||||
font_color: 'blue-grey-2',
|
||||
},
|
||||
{
|
||||
type_label: 'shared.shift_type.sick',
|
||||
background_color: 'grey-8',
|
||||
font_color: 'blue-grey-2',
|
||||
},
|
||||
]
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { ShiftLegendItem } from 'src/modules/timesheets/models/shift.models';
|
||||
|
||||
const { t } = useI18n();
|
||||
const is_showing_legend = ref(false);
|
||||
|
||||
const legend: ShiftLegendItem[] = [
|
||||
{ type: 'REGULAR', color: 'secondary', label_type: 'timesheet.shift.types.REGULAR' },
|
||||
{ type: 'EVENING', color: 'warning', label_type: 'timesheet.shift.types.EVENING' },
|
||||
{ type: 'EMERGENCY', color: 'amber-10', label_type: 'timesheet.shift.types.EMERGENCY' },
|
||||
{ type: 'VACATION', color: 'purple-10', label_type: 'timesheet.shift.types.VACATION' },
|
||||
{ type: 'HOLIDAY', color: 'purple-5', label_type: 'timesheet.shift.types.HOLIDAY' },
|
||||
{ type: 'SICK', color: 'grey-8', label_type: 'timesheet.shift.types.SICK' },
|
||||
]
|
||||
|
||||
const shift_type_legend = computed(() =>
|
||||
legend.map(item => ({ ...item, label: t(item.label_type) }))
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="items-center"
|
||||
:class="$q.screen.lt.md ? 'column' : 'row'"
|
||||
>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
rounded
|
||||
color="primary"
|
||||
class="col-auto q-my-sm"
|
||||
@click="is_showing_legend = !is_showing_legend"
|
||||
>
|
||||
<template #default>
|
||||
<q-icon
|
||||
:name="is_showing_legend ? 'close' : 'info_outline'"
|
||||
size="md"
|
||||
class="col-auto"
|
||||
/>
|
||||
</template>
|
||||
</q-btn>
|
||||
|
||||
<transition
|
||||
appear
|
||||
enter-active-class="animated fadeIn"
|
||||
leave-active-class="animated fadeOut"
|
||||
class="col-auto"
|
||||
>
|
||||
<div
|
||||
v-if="is_showing_legend"
|
||||
class="q-py-xs bg-white rounded-5 shadow-2 text-center q-my-xs"
|
||||
>
|
||||
<q-badge
|
||||
v-for="shift_type in shift_type_legend"
|
||||
:key="shift_type.type"
|
||||
:color="shift_type.color"
|
||||
:label="shift_type.label"
|
||||
:text-color="shift_type.text_color || 'white'"
|
||||
class="q-pa-xs q-mx-xs q-my-none text-uppercase text-weight-bolder justify-center"
|
||||
style="font-size: 0.8em;"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -27,26 +27,8 @@
|
|||
class="row items-center full-width bg-dark shadow-2 rounded-5 q-my-xs"
|
||||
style="border: 2px solid var(--q-negative)"
|
||||
>
|
||||
<q-item-section class="col-auto">
|
||||
<q-badge
|
||||
outline
|
||||
color="negative"
|
||||
class="bg-dark text-weight-bolder"
|
||||
>{{ error.conflicts.date }}</q-badge>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section class="col-auto">
|
||||
<q-badge
|
||||
outline
|
||||
color="negative"
|
||||
class="bg-dark text-weight-bolder"
|
||||
>
|
||||
{{ error.conflicts.start_time }} - {{ error.conflicts.end_time }}
|
||||
</q-badge>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-label class="text-weight-medium text-caption q-ml-md">
|
||||
{{ $t('timesheet.shift.errors.' + error.error_code) }}
|
||||
{{ $t('timesheet.shift.errors.' + error) }}
|
||||
</q-item-label>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
|
|
|||
|
|
@ -11,13 +11,6 @@ export type ShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'HOLIDAY' | 'VACAT
|
|||
|
||||
export type ShiftErrorCode = 'SHIFT_OVERLAP' | 'MISSING_START_TIME' | 'MISSING_END_TIME' | 'COMMENT_LENGTH_EXCEEDED' | 'APPROVAL_LOCK' | 'INVALID_DATE' | 'INVALID TYPE' | 'INVALID_TIMESHEET';
|
||||
|
||||
export type ShiftLegendItem = {
|
||||
type: ShiftType;
|
||||
color: string;
|
||||
label_type: string;
|
||||
text_color?: string;
|
||||
};
|
||||
|
||||
export class Shift {
|
||||
id: number;
|
||||
timesheet_id: number;
|
||||
|
|
@ -49,23 +42,4 @@ export interface ShiftOption {
|
|||
value: ShiftType;
|
||||
icon: string;
|
||||
icon_color: string;
|
||||
}
|
||||
|
||||
export interface ShiftAPIResponse {
|
||||
ok: boolean;
|
||||
data?: {
|
||||
shift: Shift;
|
||||
overtime: unknown;
|
||||
}
|
||||
error?: ShiftAPIError;
|
||||
}
|
||||
|
||||
export interface ShiftAPIError {
|
||||
error_code: ShiftErrorCode;
|
||||
conflicts:
|
||||
{
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { api } from "src/boot/axios";
|
||||
import type { Shift, ShiftAPIResponse } from "src/modules/timesheets/models/shift.models";
|
||||
import type { BackendResponse } from "src/modules/shared/models/backend-response.models";
|
||||
import type { Shift } from "src/modules/timesheets/models/shift.models";
|
||||
|
||||
export const ShiftService = {
|
||||
deleteShiftById: async (shift_id: number) => {
|
||||
|
|
@ -7,14 +8,14 @@ export const ShiftService = {
|
|||
return response.data;
|
||||
},
|
||||
|
||||
createNewShifts: async (new_shifts: Shift[]):Promise<ShiftAPIResponse[]> => {
|
||||
createNewShifts: async (new_shifts: Shift[]):Promise<BackendResponse<Shift>> => {
|
||||
const response = await api.post(`/shift/create`, new_shifts);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateShifts: async (existing_shifts: Shift[]) => {
|
||||
updateShifts: async (existing_shifts: Shift[]):Promise<BackendResponse<Shift>> => {
|
||||
console.log('sent shifts: ', existing_shifts)
|
||||
const response = await api.patch(`/shift/update`, existing_shifts);
|
||||
return response;
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
|
@ -27,11 +27,4 @@ import { onMounted } from 'vue';
|
|||
class="col-sm-12 col-md-10 col-lg-7 col-xl-5"
|
||||
/>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.q-field--outlined.q-field--readonly .q-field__control:before) {
|
||||
border: 1px solid var(--q-accent);
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
|
@ -4,11 +4,10 @@ import { Notify } from "quasar";
|
|||
import { defineStore } from "pinia";
|
||||
import { ShiftService } from "src/modules/timesheets/services/shift-service";
|
||||
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||
import type { ShiftAPIError } from "src/modules/timesheets/models/shift.models";
|
||||
|
||||
export const useShiftStore = defineStore('shift_store', () => {
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const shift_errors = ref<ShiftAPIError[]>([]);
|
||||
const shift_errors = ref<string[]>([]);
|
||||
|
||||
const deleteShiftById = async (shift_id: number): Promise<boolean> => {
|
||||
try {
|
||||
|
|
@ -22,7 +21,6 @@ export const useShiftStore = defineStore('shift_store', () => {
|
|||
|
||||
const createNewShifts = async (): Promise<boolean> => {
|
||||
if (timesheet_store.timesheets === undefined) return false;
|
||||
const has_errors = false;
|
||||
|
||||
try {
|
||||
const days = timesheet_store.timesheets.flatMap(week => week.days);
|
||||
|
|
@ -30,14 +28,10 @@ export const useShiftStore = defineStore('shift_store', () => {
|
|||
|
||||
if (new_shifts?.length > 0) {
|
||||
const response = await ShiftService.createNewShifts(new_shifts);
|
||||
if (response.every(res => res.ok)) {
|
||||
if (response.success) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
response.forEach(res => {
|
||||
shift_errors.value.push(res.error!);
|
||||
});
|
||||
}
|
||||
else { shift_errors.value.push(response.error!) }
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
|
|
@ -55,7 +49,7 @@ export const useShiftStore = defineStore('shift_store', () => {
|
|||
if (existing_shifts?.length > 0) {
|
||||
const response = await ShiftService.updateShifts(existing_shifts);
|
||||
|
||||
if (response.status < 400) {
|
||||
if (response.success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user