Merge pull request 'release/nicolas/v1.1' (#74) from release/nicolas/v1.1 into main
Reviewed-on: Targo/targo_frontend#74
This commit is contained in:
commit
36cf7d2fcf
|
|
@ -109,10 +109,11 @@ export default {
|
||||||
banked_hours: "available banked hours",
|
banked_hours: "available banked hours",
|
||||||
sick_hours: "available PTO hours",
|
sick_hours: "available PTO hours",
|
||||||
vacation_hours: "available vacation hours",
|
vacation_hours: "available vacation hours",
|
||||||
|
save_changes_notification: "save changes to employee profile?",
|
||||||
schedule_presets: {
|
schedule_presets: {
|
||||||
preset_list_placeholder: "Select a schedule",
|
preset_list_placeholder: "Select a schedule",
|
||||||
preset_name_placeholder: "schedule preset name",
|
preset_name_placeholder: "schedule preset name",
|
||||||
delete_warning: "",
|
delete_warning: "Are you certain you wish to delete this schedule?",
|
||||||
delete_warning_employee_1: "This schedule is used by",
|
delete_warning_employee_1: "This schedule is used by",
|
||||||
delete_warning_employee_2: "Deleting this preset will not affect previous timesheets, but they will no longer be able to apply this preset to their timesheets going forward.",
|
delete_warning_employee_2: "Deleting this preset will not affect previous timesheets, but they will no longer be able to apply this preset to their timesheets going forward.",
|
||||||
},
|
},
|
||||||
|
|
@ -237,10 +238,13 @@ export default {
|
||||||
cancel: "cancel",
|
cancel: "cancel",
|
||||||
update: "update",
|
update: "update",
|
||||||
modify: "modify",
|
modify: "modify",
|
||||||
|
copy: "copy",
|
||||||
close: "close",
|
close: "close",
|
||||||
download: "download",
|
download: "download",
|
||||||
open: "open",
|
open: "open",
|
||||||
day: "day",
|
day: "day",
|
||||||
|
empty: "empty",
|
||||||
|
name: "name",
|
||||||
},
|
},
|
||||||
misc: {
|
misc: {
|
||||||
or: "or",
|
or: "or",
|
||||||
|
|
@ -327,6 +331,9 @@ export default {
|
||||||
empty_list: 'No registered expenses',
|
empty_list: 'No registered expenses',
|
||||||
employee_comment: 'Comment',
|
employee_comment: 'Comment',
|
||||||
supervisor_comment: 'Supervisor note',
|
supervisor_comment: 'Supervisor note',
|
||||||
|
actions: {
|
||||||
|
delete_confirm: "Delete this expense?",
|
||||||
|
},
|
||||||
hints: {
|
hints: {
|
||||||
amount_or_mileage: "Either amount or mileage, not both",
|
amount_or_mileage: "Either amount or mileage, not both",
|
||||||
comment_required: "A comment required",
|
comment_required: "A comment required",
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ export default {
|
||||||
banked_hours: "heures en banque disponibles",
|
banked_hours: "heures en banque disponibles",
|
||||||
sick_hours: "heures d'absence payées disponibles",
|
sick_hours: "heures d'absence payées disponibles",
|
||||||
vacation_hours: "heures de vacances disponibles",
|
vacation_hours: "heures de vacances disponibles",
|
||||||
|
save_changes_notification: "Sauvegarder les modifications du profil?",
|
||||||
schedule_presets: {
|
schedule_presets: {
|
||||||
preset_list_placeholder: "Sélectionner un horaire",
|
preset_list_placeholder: "Sélectionner un horaire",
|
||||||
preset_name_placeholder: "nom de l'horaire",
|
preset_name_placeholder: "nom de l'horaire",
|
||||||
|
|
@ -237,10 +238,13 @@ export default {
|
||||||
cancel: "annuler",
|
cancel: "annuler",
|
||||||
update: "mettre à jour",
|
update: "mettre à jour",
|
||||||
modify: "modifier",
|
modify: "modifier",
|
||||||
|
copy: "copier",
|
||||||
close: "fermer",
|
close: "fermer",
|
||||||
download: "télécharger",
|
download: "télécharger",
|
||||||
open: "ouvrir",
|
open: "ouvrir",
|
||||||
day: "jour",
|
day: "jour",
|
||||||
|
empty: "vide",
|
||||||
|
name: "nom",
|
||||||
},
|
},
|
||||||
misc: {
|
misc: {
|
||||||
or: "ou",
|
or: "ou",
|
||||||
|
|
@ -327,6 +331,9 @@ export default {
|
||||||
empty_list: 'Aucun dépense enregistrée',
|
empty_list: 'Aucun dépense enregistrée',
|
||||||
employee_comment: 'Commentaire',
|
employee_comment: 'Commentaire',
|
||||||
supervisor_comment: 'Note du Superviseur',
|
supervisor_comment: 'Note du Superviseur',
|
||||||
|
actions: {
|
||||||
|
delete_confirm: "Supprimer cette dépense?",
|
||||||
|
},
|
||||||
hints: {
|
hints: {
|
||||||
amount_or_mileage: "Soit dépense ou kilométrage, pas les deux",
|
amount_or_mileage: "Soit dépense ou kilométrage, pas les deux",
|
||||||
comment_required: "un commentaire est requis",
|
comment_required: "un commentaire est requis",
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
<div
|
<div
|
||||||
v-if="chatbot_store.is_showing_chatbot"
|
v-if="chatbot_store.is_showing_chatbot"
|
||||||
class="col column"
|
class="col column"
|
||||||
style="background: rgba(0, 0, 0, 0.7); overflow: hidden;"
|
style="background: rgba(0, 0, 0, 0.7); overflow: hidden; pointer-events: auto;"
|
||||||
>
|
>
|
||||||
<q-btn
|
<q-btn
|
||||||
dense
|
dense
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
dark
|
dark
|
||||||
label-color="white"
|
label-color="white"
|
||||||
class="col q-px-md"
|
class="col q-px-md"
|
||||||
style="background: rgba(0, 0, 0, 0.3);"
|
style="background: rgba(0, 0, 0, 0.3); pointer-events: all;"
|
||||||
@keydown.enter="handleSend"
|
@keydown.enter="handleSend"
|
||||||
/>
|
/>
|
||||||
</q-form>
|
</q-form>
|
||||||
|
|
@ -92,6 +92,7 @@
|
||||||
color="accent"
|
color="accent"
|
||||||
size="2em"
|
size="2em"
|
||||||
class="shadow-5"
|
class="shadow-5"
|
||||||
|
style="pointer-events: auto;"
|
||||||
@click="chatbot_store.is_showing_chatbot = true"
|
@click="chatbot_store.is_showing_chatbot = true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -103,5 +104,6 @@
|
||||||
:deep(.q-drawer) {
|
:deep(.q-drawer) {
|
||||||
background: rgba(0, 0, 0, 0);
|
background: rgba(0, 0, 0, 0);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -2,43 +2,58 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import HorizontalSlideTransition from 'src/modules/shared/components/horizontal-slide-transition.vue';
|
|
||||||
import SchedulePresetsDialog from 'src/modules/employee-list/components/schedule-presets-dialog.vue';
|
import SchedulePresetsDialog from 'src/modules/employee-list/components/schedule-presets-dialog.vue';
|
||||||
import AddModifyDialogSchedulePreview from './add-modify-dialog-schedule-preview.vue';
|
import AddModifyDialogSchedulePreview from './add-modify-dialog-schedule-preview.vue';
|
||||||
|
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
|
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
import { useEmployeeListApi } from '../composables/use-employee-api';
|
import { useEmployeeListApi } from '../composables/use-employee-api';
|
||||||
import type { PresetManagerMode } from 'src/modules/employee-list/models/schedule-presets.models';
|
import type { PresetManagerMode } from 'src/modules/employee-list/models/schedule-presets.models';
|
||||||
|
import type { QSelectOption } from 'quasar';
|
||||||
|
|
||||||
// ================= state ======================
|
// ========== state ========================================
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const schedule_preset_store = useSchedulePresetsStore();
|
const schedule_preset_store = useSchedulePresetsStore();
|
||||||
const employee_store = useEmployeeStore();
|
const employee_store = useEmployeeStore();
|
||||||
const employee_list_api = useEmployeeListApi();
|
const employee_list_api = useEmployeeListApi();
|
||||||
|
|
||||||
const preset_options = ref<{ label: string, value: number }[]>([]);
|
const preset_options = ref<QSelectOption<number>[]>([]);
|
||||||
const current_preset = ref<{ label: string | undefined, value: number }>({ label: undefined, value: -1 });
|
const selected_preset = ref<QSelectOption<number>>({ label: '', value: -1 });
|
||||||
|
|
||||||
// ====================== methods ========================
|
// ========== methods ========================================
|
||||||
|
|
||||||
|
const getPresetOptions = (): QSelectOption<number>[] => {
|
||||||
|
const options: QSelectOption<number>[] = [{ label: t('shared.label.empty'), value: -1 }];
|
||||||
|
schedule_preset_store.schedule_presets.forEach(preset => {
|
||||||
|
options.push({ label: preset.name, value: preset.id })
|
||||||
|
});
|
||||||
|
options.push({ label: '', value: 0 });
|
||||||
|
|
||||||
const getPresetOptions = (): { label: string, value: number }[] => {
|
|
||||||
const options = schedule_preset_store.schedule_presets.map(preset => { return { label: preset.name, value: preset.id } });
|
|
||||||
options.push({ label: 'Aucun', value: -1 });
|
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickSchedulePresetManager = (mode: PresetManagerMode, preset_id?: number) => {
|
const onClickSchedulePresetManager = (mode: PresetManagerMode, preset_id?: number) => {
|
||||||
schedule_preset_store.schedule_preset_dialog_mode = mode;
|
schedule_preset_store.schedule_preset_dialog_mode = mode;
|
||||||
schedule_preset_store.openSchedulePresetManager(preset_id ?? current_preset.value.value);
|
schedule_preset_store.openSchedulePresetManager(preset_id ?? selected_preset.value.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadSelectedPresetOption = () => {
|
const loadSelectedPresetOption = () => {
|
||||||
preset_options.value = getPresetOptions();
|
preset_options.value = getPresetOptions();
|
||||||
const current_option = preset_options.value.find(option => option.value === employee_store.employee.preset_id);
|
const employee = employee_store.employee;
|
||||||
current_preset.value = current_option ?? { label: undefined, value: -1 };
|
|
||||||
schedule_preset_store.setCurrentSchedulePreset(current_preset.value.value);
|
if (!employee.preset_id)
|
||||||
|
selected_preset.value = preset_options.value[0]!;
|
||||||
|
|
||||||
|
else
|
||||||
|
selected_preset.value = preset_options.value.find(opt =>
|
||||||
|
opt.value === employee.preset_id
|
||||||
|
)!;
|
||||||
|
|
||||||
|
schedule_preset_store.setCurrentSchedulePreset(selected_preset.value.value);
|
||||||
|
schedule_preset_store.schedule_preset_dialog_mode = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
@ -48,76 +63,111 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:key="schedule_preset_store.is_manager_open === false ? '0' : '1'"
|
:key="schedule_preset_store.isManagerOpen === false ? '0' : '1'"
|
||||||
class="column full-width flex-center items-start"
|
class="column full-width flex-center items-start"
|
||||||
>
|
>
|
||||||
<SchedulePresetsDialog @before-hide="loadSelectedPresetOption"/>
|
<SchedulePresetsDialog @on-close="loadSelectedPresetOption" />
|
||||||
|
|
||||||
<div class="col row justify-center full-width no-wrap">
|
<div class="col row flex-center full-width no-wrap">
|
||||||
<q-select
|
<q-select
|
||||||
v-model="current_preset"
|
v-model="selected_preset"
|
||||||
standout="bg-accent"
|
:options="getPresetOptions()"
|
||||||
|
standout="bg-primary"
|
||||||
dense
|
dense
|
||||||
options-dense
|
options-dense
|
||||||
rounded
|
rounded
|
||||||
color="accent"
|
color="accent"
|
||||||
:options="getPresetOptions()"
|
|
||||||
class="col-xs-10 col-md-7"
|
|
||||||
popup-content-class="text-uppercase text-weight-medium rounded-20"
|
|
||||||
popup-content-style="border: 2px solid var(--q-accent)"
|
|
||||||
menu-anchor="bottom middle"
|
menu-anchor="bottom middle"
|
||||||
menu-self="top middle"
|
menu-self="top middle"
|
||||||
:menu-offset="[0, 10]"
|
:menu-offset="[0, 5]"
|
||||||
|
class="col-xs-10 col-md-7"
|
||||||
|
popup-content-class="text-uppercase text-weight-medium rounded-20 shadow-24"
|
||||||
|
popup-content-style="border: 2px solid var(--q-primary)"
|
||||||
|
style="font-size: 1.4em;"
|
||||||
@update:modelValue="option => employee_list_api.setSchedulePreset(option.value)"
|
@update:modelValue="option => employee_list_api.setSchedulePreset(option.value)"
|
||||||
>
|
>
|
||||||
<template #selected>
|
<template #selected>
|
||||||
<span
|
<span
|
||||||
class="text-uppercase text-center text-weight-bold full-width"
|
class="text-uppercase text-center text-weight-bold full-width"
|
||||||
:style="current_preset.label === undefined ? 'opacity: 0.5;' : ''"
|
:style="selected_preset.label === undefined ? 'opacity: 0.5;' : ''"
|
||||||
>
|
>
|
||||||
{{ current_preset.label === undefined ?
|
{{ selected_preset.label === undefined ?
|
||||||
$t('employee_management.schedule_presets.preset_list_placeholder') :
|
$t('employee_management.schedule_presets.preset_list_placeholder') :
|
||||||
current_preset.label }}
|
selected_preset.label }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item
|
||||||
|
v-if="scope.opt.value !== 0"
|
||||||
|
v-bind="scope.itemProps"
|
||||||
|
class="row flex-center"
|
||||||
|
style="font-size: 1.2em;"
|
||||||
|
>
|
||||||
|
<div class="col">
|
||||||
|
<span>{{ scope.label }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="scope.opt.value > 0"
|
||||||
|
class="row items-center no-wrap"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- edit currently-selected preset -->
|
||||||
|
<div class="col-auto q-px-sm">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="edit"
|
||||||
|
color="accent"
|
||||||
|
@click.stop="onClickSchedulePresetManager('update', scope.opt.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- copy currently-selected preset -->
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="content_copy"
|
||||||
|
color="accent"
|
||||||
|
@click.stop="onClickSchedulePresetManager('copy', scope.opt.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- delete currently-selected preset -->
|
||||||
|
<div class="col-auto q-px-xs">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
color="negative"
|
||||||
|
icon="las la-trash"
|
||||||
|
class="q-py-none"
|
||||||
|
@click.stop="onClickSchedulePresetManager('delete', scope.opt.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-else
|
||||||
|
class="q-pa-none"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
square
|
||||||
|
icon="add"
|
||||||
|
size="md"
|
||||||
|
color="accent"
|
||||||
|
class="full-width q-py-none"
|
||||||
|
:label="$t('shared.label.add')"
|
||||||
|
style="font-size: 1.2em;"
|
||||||
|
@click.stop="onClickSchedulePresetManager('create', -1)"
|
||||||
|
/>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
</q-select>
|
</q-select>
|
||||||
|
|
||||||
<q-btn
|
|
||||||
icon="add"
|
|
||||||
color="accent"
|
|
||||||
class="col-auto q-px-sm q-ml-sm rounded-50"
|
|
||||||
@click="onClickSchedulePresetManager('create', -1)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HorizontalSlideTransition :show="current_preset !== undefined && current_preset?.value !== -1">
|
|
||||||
<div class="col-auto row no-wrap full-height">
|
|
||||||
<q-btn
|
|
||||||
icon="edit"
|
|
||||||
color="accent"
|
|
||||||
class="col-auto q-px-sm q-ml-sm rounded-50"
|
|
||||||
@click="onClickSchedulePresetManager('update')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
icon="content_copy"
|
|
||||||
color="accent"
|
|
||||||
class="col-auto q-px-sm q-mx-sm rounded-50"
|
|
||||||
@click="onClickSchedulePresetManager('copy')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
rounded
|
|
||||||
icon="clear"
|
|
||||||
color="negative"
|
|
||||||
class="col-auto q-px-sm full-height"
|
|
||||||
@click="onClickSchedulePresetManager('delete')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HorizontalSlideTransition>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<AddModifyDialogSchedulePreview :current-preset-id="current_preset.value" />
|
|
||||||
|
<AddModifyDialogSchedulePreview :current-preset-id="selected_preset.value" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -8,21 +8,52 @@
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
import { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
|
||||||
|
// ========== state ========================================
|
||||||
|
|
||||||
const employee_store = useEmployeeStore();
|
const employee_store = useEmployeeStore();
|
||||||
const current_step = ref<'form' | 'access' | 'schedule'>('form');
|
const current_step = ref<'form' | 'access' | 'schedule'>('form');
|
||||||
const initial_employee_profile = ref(new EmployeeProfile)
|
const initial_employee_details = ref('');
|
||||||
|
const is_showing_close_confirm = ref(false);
|
||||||
|
|
||||||
|
// ========== methods ========================================
|
||||||
|
|
||||||
|
const onBeforeHide = () => {
|
||||||
|
const current_employee_details = JSON.stringify(employee_store.employee);
|
||||||
|
|
||||||
|
if (initial_employee_details.value !== current_employee_details)
|
||||||
|
is_showing_close_confirm.value = true;
|
||||||
|
else
|
||||||
|
employee_store.is_add_modify_dialog_open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBeforeShow = () => {
|
||||||
|
current_step.value = 'form';
|
||||||
|
initial_employee_details.value = JSON.stringify(employee_store.employee);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickSaveChanges = async () => {
|
||||||
|
const success = await employee_store.createOrUpdateEmployee(employee_store.employee);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
closeAllDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeAllDialogs = () => {
|
||||||
|
is_showing_close_confirm.value = false;
|
||||||
|
employee_store.is_add_modify_dialog_open = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-dialog
|
<q-dialog
|
||||||
v-model="employee_store.is_add_modify_dialog_open"
|
:model-value="employee_store.is_add_modify_dialog_open"
|
||||||
full-width
|
full-width
|
||||||
full-height
|
full-height
|
||||||
@beforeShow="current_step = 'form'"
|
backdrop-filter="blur(4px)"
|
||||||
@show="Object.assign(initial_employee_profile, employee_store.employee)"
|
|
||||||
class="shadow-24"
|
class="shadow-24"
|
||||||
|
@beforeShow="onBeforeShow"
|
||||||
|
@update:model-value="onBeforeHide"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="column bg-secondary rounded-10 no-wrap"
|
class="column bg-secondary rounded-10 no-wrap"
|
||||||
|
|
@ -112,8 +143,50 @@
|
||||||
color="accent"
|
color="accent"
|
||||||
:label="employee_store.management_mode === 'add_employee' ? $t('shared.label.save') : $t('shared.label.update')"
|
:label="employee_store.management_mode === 'add_employee' ? $t('shared.label.save') : $t('shared.label.update')"
|
||||||
class="col-auto q-py-sm shadow-up-5"
|
class="col-auto q-py-sm shadow-up-5"
|
||||||
@click="employee_store.createOrUpdateEmployee(employee_store.employee)"
|
@click="onClickSaveChanges"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- dialog to confirm if you want to save changes -->
|
||||||
|
<q-dialog
|
||||||
|
v-model="is_showing_close_confirm"
|
||||||
|
persistent
|
||||||
|
backdrop-filter="blur(4px)"
|
||||||
|
>
|
||||||
|
<q-card class="q-pa-md shadow-24">
|
||||||
|
<span class="text-uppercase text-weight-light text-h6">{{ $t('employee_management.save_changes_notification') }}</span>
|
||||||
|
|
||||||
|
<div class="row full-width">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="lg"
|
||||||
|
:label="$t('shared.label.cancel')"
|
||||||
|
class="col"
|
||||||
|
@click="is_showing_close_confirm = false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="lg"
|
||||||
|
color="negative"
|
||||||
|
:label="$t('shared.misc.no')"
|
||||||
|
class="col"
|
||||||
|
@click="closeAllDialogs"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="lg"
|
||||||
|
color="accent"
|
||||||
|
:label="$t('shared.misc.yes')"
|
||||||
|
class="col"
|
||||||
|
@click="onClickSaveChanges"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -147,7 +147,6 @@
|
||||||
color="accent"
|
color="accent"
|
||||||
bg-color="white"
|
bg-color="white"
|
||||||
label-color="accent"
|
label-color="accent"
|
||||||
class="text-primary"
|
|
||||||
debounce="300"
|
debounce="300"
|
||||||
:label="$t('shared.label.search')"
|
:label="$t('shared.label.search')"
|
||||||
>
|
>
|
||||||
|
|
@ -298,4 +297,8 @@ tbody {
|
||||||
:deep(.q-table__grid-content) {
|
:deep(.q-table__grid-content) {
|
||||||
overflow: auto
|
overflow: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.q-field__native) {
|
||||||
|
color: var(--q-primary);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -6,17 +6,37 @@
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
||||||
|
|
||||||
const employee_store = useEmployeeStore();
|
// ========== state ===================================
|
||||||
const employee_list_api = useEmployeeListApi();
|
|
||||||
|
|
||||||
const { presetId } = defineProps<{
|
const { presetId } = defineProps<{
|
||||||
presetId: number;
|
presetId: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'onConfirmDelete': [void];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
const employee_list_api = useEmployeeListApi();
|
||||||
|
|
||||||
const employee_amount_using_preset = ref(0);
|
const employee_amount_using_preset = ref(0);
|
||||||
const delete_input_string = ref('');
|
const delete_input_string = ref('');
|
||||||
|
|
||||||
|
// ========== computed ==================================
|
||||||
|
|
||||||
const is_approve_deletion = computed(() => ['SUPPRIMER', 'DELETE'].includes(delete_input_string.value));
|
const is_approve_deletion = computed(() => ['SUPPRIMER', 'DELETE'].includes(delete_input_string.value));
|
||||||
|
|
||||||
|
// ========== methods ===================================
|
||||||
|
|
||||||
|
const onClickDeleteConfirm = async () => {
|
||||||
|
const success = await employee_list_api.deleteSchedulePreset(presetId);
|
||||||
|
|
||||||
|
if (success && employee_store.employee.preset_id === presetId)
|
||||||
|
employee_store.employee.preset_id = null;
|
||||||
|
|
||||||
|
emit('onConfirmDelete');
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const employees_with_preset = employee_store.employee_list.filter(employee => employee.preset_id === presetId);
|
const employees_with_preset = employee_store.employee_list.filter(employee => employee.preset_id === presetId);
|
||||||
employee_amount_using_preset.value = employees_with_preset.length;
|
employee_amount_using_preset.value = employees_with_preset.length;
|
||||||
|
|
@ -68,7 +88,7 @@
|
||||||
:color="is_approve_deletion ? 'negative' : 'grey-6'"
|
:color="is_approve_deletion ? 'negative' : 'grey-6'"
|
||||||
:label="$t('shared.label.remove')"
|
:label="$t('shared.label.remove')"
|
||||||
class="q-px-md"
|
class="q-px-md"
|
||||||
@click="employee_list_api.deleteSchedulePreset(presetId)"
|
@click="onClickDeleteConfirm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,18 +10,35 @@
|
||||||
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
|
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
|
||||||
import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
||||||
|
|
||||||
const schedule_preset_store = useSchedulePresetsStore();
|
const emit = defineEmits<{
|
||||||
|
'onClose': [void];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const schedulePresetStore = useSchedulePresetsStore();
|
||||||
const employee_list_api = useEmployeeListApi();
|
const employee_list_api = useEmployeeListApi();
|
||||||
|
|
||||||
|
const onClickSaveSchedulePreset = async () => {
|
||||||
|
const success = await employee_list_api.saveSchedulePreset();
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
closePresetManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
const closePresetManager = () => {
|
||||||
|
emit('onClose');
|
||||||
|
schedulePresetStore.isManagerOpen = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-dialog
|
<q-dialog
|
||||||
v-model="schedule_preset_store.is_manager_open"
|
v-model="schedulePresetStore.isManagerOpen"
|
||||||
full-width
|
full-width
|
||||||
>
|
>
|
||||||
<SchedulePresetsDialogDelete
|
<SchedulePresetsDialogDelete
|
||||||
v-if="schedule_preset_store.schedule_preset_dialog_mode === 'delete'"
|
v-if="schedulePresetStore.schedule_preset_dialog_mode === 'delete'"
|
||||||
:preset-id="schedule_preset_store.current_schedule_preset.id"
|
:preset-id="schedulePresetStore.current_schedule_preset.id"
|
||||||
|
@on-confirm-delete="closePresetManager"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -34,24 +51,23 @@
|
||||||
style="border-radius: 8px 8px 0 0;"
|
style="border-radius: 8px 8px 0 0;"
|
||||||
>
|
>
|
||||||
<span class="row col-auto text-uppercase text-weight-bold text-white q-py-sm">{{
|
<span class="row col-auto text-uppercase text-weight-bold text-white q-py-sm">{{
|
||||||
schedule_preset_store.current_schedule_preset.id === -1 ?
|
schedulePresetStore.current_schedule_preset.id === -1 ?
|
||||||
$t('shared.label.add') :
|
$t('shared.label.add') :
|
||||||
$t('shared.label.modify') }}
|
$t('shared.label.modify') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row col-auto q-px-sm flex-center full-width q-py-sm">
|
<div class="row col-auto q-px-sm flex-center full-width q-pt-md">
|
||||||
<div class="col-8 bg-dark rounded-10 ellipsis">
|
<div class="col-8 bg-dark rounded-10 shadow-2 ellipsis">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="schedule_preset_store.current_schedule_preset.name"
|
v-model="schedulePresetStore.current_schedule_preset.name"
|
||||||
standout
|
standout
|
||||||
dense
|
dense
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
:placeholder="$t('employee_management.schedule_presets.preset_name_placeholder')"
|
:placeholder="$t('employee_management.schedule_presets.preset_name_placeholder')"
|
||||||
class="text-uppercase"
|
|
||||||
input-class="text-weight-bold text-center"
|
input-class="text-weight-bold text-center"
|
||||||
>
|
>
|
||||||
<template #before>
|
<template #prepend>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="edit"
|
name="edit"
|
||||||
color="accent"
|
color="accent"
|
||||||
|
|
@ -62,12 +78,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="column col full-width q-py-sm q-px-lg no-wrap scroll">
|
||||||
v-if="schedule_preset_store.schedule_preset_dialog_mode !== 'copy'"
|
|
||||||
class="column col full-width q-py-sm q-px-lg no-wrap scroll"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-for="weekday of schedule_preset_store.current_schedule_preset.weekdays"
|
v-for="weekday of schedulePresetStore.current_schedule_preset.weekdays"
|
||||||
:key="weekday.day"
|
:key="weekday.day"
|
||||||
class="row col-auto items-center q-my-xs shadow-2 bg-dark rounded-10 ellipsis"
|
class="row col-auto items-center q-my-xs shadow-2 bg-dark rounded-10 ellipsis"
|
||||||
style="min-height: 50px;"
|
style="min-height: 50px;"
|
||||||
|
|
@ -106,14 +119,14 @@
|
||||||
<div class="col-auto row self-end q-px-lg q-mt-sm full-width">
|
<div class="col-auto row self-end q-px-lg q-mt-sm full-width">
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn
|
<q-btn
|
||||||
:disable="schedule_preset_store.current_schedule_preset.name === ''"
|
:disable="schedulePresetStore.current_schedule_preset.name === ''"
|
||||||
push
|
push
|
||||||
dense
|
dense
|
||||||
:color="schedule_preset_store.current_schedule_preset.name === '' ? 'grey-7' : 'accent'"
|
:color="schedulePresetStore.current_schedule_preset.name === '' ? 'grey-7' : 'accent'"
|
||||||
icon="download"
|
icon="download"
|
||||||
:label="$t('shared.label.save')"
|
:label="$t('shared.label.save')"
|
||||||
class="col-auto q-px-md q-mb-sm"
|
class="col-auto q-px-md q-mb-sm"
|
||||||
@click="employee_list_api.saveSchedulePreset"
|
@click="onClickSaveSchedulePreset"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,76 +4,95 @@ import { SchedulePreset } from "../models/schedule-presets.models";
|
||||||
import { isShiftOverlap } from "src/modules/timesheets/utils/shift.util";
|
import { isShiftOverlap } from "src/modules/timesheets/utils/shift.util";
|
||||||
|
|
||||||
export const useEmployeeListApi = () => {
|
export const useEmployeeListApi = () => {
|
||||||
const employee_store = useEmployeeStore();
|
const employeeStore = useEmployeeStore();
|
||||||
const schedule_preset_store = useSchedulePresetsStore();
|
const schedulePresetStore = useSchedulePresetsStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the employee store with the exhaustive list of all employees past and present
|
||||||
|
* Also populates the schedule preset store with all schedule presets currently available.
|
||||||
|
*/
|
||||||
const getEmployeeList = async (): Promise<void> => {
|
const getEmployeeList = async (): Promise<void> => {
|
||||||
employee_store.is_loading = true;
|
employeeStore.is_loading = true;
|
||||||
|
|
||||||
const success = await employee_store.getEmployeeList();
|
const success = await employeeStore.getEmployeeList();
|
||||||
if (success) await schedule_preset_store.findSchedulePresetList();
|
if (success) await schedulePresetStore.getSchedulePresetList();
|
||||||
|
|
||||||
employee_store.is_loading = false;
|
employeeStore.is_loading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the details of a specific employee to the employee store. If the employee has a
|
||||||
|
* schedule preset assigned, it also assign that preset to the schedule preset store.
|
||||||
|
*
|
||||||
|
* @param email email associated to employee.
|
||||||
|
*/
|
||||||
const getEmployeeDetails = async (email: string): Promise<void> => {
|
const getEmployeeDetails = async (email: string): Promise<void> => {
|
||||||
const success = await employee_store.getEmployeeDetails(email);
|
const success = await employeeStore.getEmployeeDetails(email);
|
||||||
if (success && employee_store.employee.preset_id !== null) {
|
if (success && employeeStore.employee.preset_id !== null) {
|
||||||
schedule_preset_store.setCurrentSchedulePreset(employee_store.employee.preset_id ?? -1);
|
schedulePresetStore.setCurrentSchedulePreset(employeeStore.employee.preset_id ?? -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the specified preset as the current preset in the schedule preset store and also
|
||||||
|
* applies it to the current employee in the employee store. If a negative ID is provided, the
|
||||||
|
* employee's assigned preset is set to null.
|
||||||
|
*
|
||||||
|
* @param preset_id - the preset id currently selected
|
||||||
|
*/
|
||||||
const setSchedulePreset = (preset_id: number) => {
|
const setSchedulePreset = (preset_id: number) => {
|
||||||
schedule_preset_store.setCurrentSchedulePreset(preset_id);
|
schedulePresetStore.setCurrentSchedulePreset(preset_id);
|
||||||
employee_store.employee.preset_id = preset_id < 0 ? null : preset_id;
|
employeeStore.employee.preset_id = preset_id < 0 ? null : preset_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveSchedulePreset = async () => {
|
/**
|
||||||
// Get the currently edited schedule preset from the store (frontend model)
|
* Validates and converts the current schedule preset into a model that the backend
|
||||||
const preset = schedule_preset_store.current_schedule_preset;
|
* can ingest and save, then sends a request to create or update preset.
|
||||||
|
*
|
||||||
// Check if there's any overlap between shifts. If there is, is_error property
|
* @returns `true` if the preset is valid and was successfully saved, `false` otherwise.
|
||||||
// will be toggled to true and save process will stop
|
*/
|
||||||
for (const weekday of preset.weekdays) {
|
const saveSchedulePreset = async (): Promise<boolean> => {
|
||||||
weekday.is_error = isShiftOverlap(weekday.shifts);
|
const preset = schedulePresetStore.current_schedule_preset;
|
||||||
}
|
preset.weekdays.forEach(weekday => weekday.is_error = isShiftOverlap(weekday.shifts));
|
||||||
|
|
||||||
if (preset.weekdays.some(weekday => weekday.is_error)) {
|
if (preset.weekdays.some(weekday => weekday.is_error)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten all weekday shifts into a single array
|
|
||||||
const preset_shifts = preset.weekdays.flatMap(weekday => weekday.shifts);
|
|
||||||
|
|
||||||
// Build a backend-compatible SchedulePreset instance
|
const preset_shifts = preset.weekdays.flatMap(weekday => weekday.shifts);
|
||||||
const backend_preset = new SchedulePreset(
|
const backend_preset = new SchedulePreset(
|
||||||
preset.id,
|
preset.id,
|
||||||
preset.name,
|
preset.name,
|
||||||
preset_shifts
|
preset_shifts
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track whether the create/update operation succeeds
|
|
||||||
let success = false;
|
let success = false;
|
||||||
|
|
||||||
// Create a new preset if it has no backend ID, otherwise update the existing one
|
if (preset.id === -1)
|
||||||
if (preset.id === -1)
|
success = await schedulePresetStore.createSchedulePreset(backend_preset);
|
||||||
success = await schedule_preset_store.createSchedulePreset(backend_preset);
|
else
|
||||||
else
|
success = await schedulePresetStore.updateSchedulePreset(backend_preset);
|
||||||
success = await schedule_preset_store.updateSchedulePreset(backend_preset);
|
|
||||||
|
|
||||||
// On success, refresh the preset list and close the preset manager UI
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await schedule_preset_store.findSchedulePresetList();
|
await schedulePresetStore.getSchedulePresetList();
|
||||||
schedule_preset_store.is_manager_open = false;
|
employeeStore.employee.preset_id = schedulePresetStore.current_schedule_preset.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteSchedulePreset = async (preset_id: number) => {
|
/**
|
||||||
const success = await schedule_preset_store.deleteSchedulePreset(preset_id);
|
* Sends request to delete the preset associated to the provided ID.
|
||||||
if (success) {
|
*
|
||||||
await schedule_preset_store.findSchedulePresetList();
|
* @param preset_id Backend ID of preset to delete
|
||||||
schedule_preset_store.is_manager_open = false;
|
* @return `true` if successfully deleted, `false` otherwise.
|
||||||
}
|
*/
|
||||||
|
const deleteSchedulePreset = async (preset_id: number): Promise<boolean> => {
|
||||||
|
const success = await schedulePresetStore.deleteSchedulePreset(preset_id);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
await schedulePresetStore.getSchedulePresetList();
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,4 @@ export const useEmployeeProfileRules = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const company_options = [
|
export const company_options = ['Targo', 'Solucom'];
|
||||||
{ label: 'Targo', value: 'Targo' },
|
|
||||||
{ label: 'Solucom', value: 'Solucom' },
|
|
||||||
]
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { SchedulePreset } from "src/modules/employee-list/models/schedule-p
|
||||||
import type { BackendResponse } from "src/modules/shared/models/backend-response.models";
|
import type { BackendResponse } from "src/modules/shared/models/backend-response.models";
|
||||||
|
|
||||||
export const SchedulePresetsService = {
|
export const SchedulePresetsService = {
|
||||||
createSchedulePresets: async (preset: SchedulePreset) => {
|
createSchedulePresets: async (preset: SchedulePreset): Promise<BackendResponse<SchedulePreset>> => {
|
||||||
const response = await api.post(`/schedule-presets/create/`, preset);
|
const response = await api.post(`/schedule-presets/create/`, preset);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,39 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
/* eslint-disable */
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
import DetailsDialogChartHoursWorked from 'src/modules/timesheet-approval/components/details-dialog-chart-hours-worked.vue';
|
import DetailsDialogChartHoursWorked from 'src/modules/timesheet-approval/components/details-dialog-chart-hours-worked.vue';
|
||||||
import DetailsDialogChartShiftTypes from 'src/modules/timesheet-approval/components/details-dialog-chart-shift-types.vue';
|
import DetailsDialogChartShiftTypes from 'src/modules/timesheet-approval/components/details-dialog-chart-shift-types.vue';
|
||||||
import DetailsDialogChartExpenses from 'src/modules/timesheet-approval/components/details-dialog-chart-expenses.vue';
|
import DetailsDialogChartExpenses from 'src/modules/timesheet-approval/components/details-dialog-chart-expenses.vue';
|
||||||
import TimesheetWrapper from 'src/modules/timesheets/components/timesheet-wrapper.vue';
|
import TimesheetWrapper from 'src/modules/timesheets/components/timesheet-wrapper.vue';
|
||||||
import ExpenseDialogList from 'src/modules/timesheets/components/expense-dialog-list.vue';
|
import ExpenseDialogList from 'src/modules/timesheets/components/expense-dialog-list.vue';
|
||||||
|
import { useTimesheetApprovalApi } from '../composables/use-timesheet-approval-api';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
|
const timesheetApprovalApi = useTimesheetApprovalApi();
|
||||||
const is_dialog_open = ref(false);
|
const is_dialog_open = ref(false);
|
||||||
|
|
||||||
|
const isApproved = computed(() => timesheet_store.timesheets.every(timesheet => timesheet.is_approved));
|
||||||
|
const approveButtonLabel = computed(() => isApproved.value ?
|
||||||
|
t('timesheet_approvals.table.verified') :
|
||||||
|
t('timesheet_approvals.table.unverified')
|
||||||
|
);
|
||||||
|
const approveButtonIcon = computed(() => isApproved.value ? 'lock' : 'lock_open');
|
||||||
|
|
||||||
|
const onClickApproveAll = async () => {
|
||||||
|
const employeeEmail = timesheet_store.current_pay_period_overview?.email;
|
||||||
|
const isApproved = timesheet_store.timesheets.every(timesheet => timesheet.is_approved);
|
||||||
|
|
||||||
|
if (employeeEmail !== undefined && isApproved !== undefined) {
|
||||||
|
await timesheetApprovalApi.toggleTimesheetsApprovalByEmployeeEmail(
|
||||||
|
employeeEmail,
|
||||||
|
!isApproved
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -28,12 +50,34 @@
|
||||||
@before-hide="timesheet_store.getTimesheetOverviews"
|
@before-hide="timesheet_store.getTimesheetOverviews"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="column bg-secondary hide-scrollbar shadow-12 rounded-15 q-pa-sm no-wrap"
|
class="column bg-secondary hide-scrollbar shadow-12 rounded-15 q-pb-sm no-wrap"
|
||||||
:style="($q.screen.lt.md ? '' : 'width:80vw !important;') + ($q.dark.isActive ? ' border: 2px solid var(--q-accent)' : '')"
|
:style="($q.screen.lt.md ? '' : 'width:80vw !important;') + ($q.dark.isActive ? ' border: 2px solid var(--q-accent)' : '')"
|
||||||
>
|
>
|
||||||
<!-- employee name -->
|
<!-- employee name -->
|
||||||
<div class="col-auto text-h4 text-weight-bolder text-center text-uppercase q-px-none q-py-sm">
|
<div class="col-auto row flex-center q-px-none q-py-sm sticky-top bg-secondary full-width shadow-4">
|
||||||
<span>{{ timesheet_store.selected_employee_name }}</span>
|
<span class="col text-h4 text-weight-bolder text-uppercase q-px-lg">
|
||||||
|
{{ timesheet_store.selected_employee_name }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="col-auto q-px-lg">
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
size="lg"
|
||||||
|
color="accent"
|
||||||
|
:label="approveButtonLabel"
|
||||||
|
class="q-px-xl"
|
||||||
|
@click="onClickApproveAll"
|
||||||
|
>
|
||||||
|
<transition enter-active-class="animated swing" mode="out-in">
|
||||||
|
<q-icon
|
||||||
|
:key="isApproved ? '1' : '2'"
|
||||||
|
:name="approveButtonIcon"
|
||||||
|
class="q-pl-md"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- employee pay period details using chart -->
|
<!-- employee pay period details using chart -->
|
||||||
|
|
@ -61,4 +105,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sticky-top {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 5;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="column bg-primary text-uppercase q-px-sm text-white">
|
<div class="column bg-primary text-uppercase q-px-sm text-white no-wrap">
|
||||||
<div class="col row">
|
<div class="col row no-wrap">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="filters.is_showing_inactive"
|
v-model="filters.is_showing_inactive"
|
||||||
keep-color
|
keep-color
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="col-auto q-px-md q-pt-sm text-h6 text-bold">
|
<span class="col q-px-md q-pt-sm text-h6 text-bold ellipsis">
|
||||||
{{ $t('timesheet_approvals.table.filter_columns') }}
|
{{ $t('timesheet_approvals.table.filter_columns') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,10 @@ export const useTimesheetApprovalApi = () => {
|
||||||
const approval_success = await timesheet_store.toggleTimesheetsApprovalByEmployeeEmail(email, approval_status);
|
const approval_success = await timesheet_store.toggleTimesheetsApprovalByEmployeeEmail(email, approval_status);
|
||||||
const overview = timesheet_store.pay_period_overviews.find(overview => overview.email === email);
|
const overview = timesheet_store.pay_period_overviews.find(overview => overview.email === email);
|
||||||
|
|
||||||
if (overview && approval_success) overview.is_approved = approval_status;
|
if (overview && approval_success) {
|
||||||
|
overview.is_approved = approval_status;
|
||||||
|
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(email);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timesheet_store.is_loading = false;
|
timesheet_store.is_loading = false;
|
||||||
|
|
|
||||||
|
|
@ -264,9 +264,6 @@
|
||||||
<q-file
|
<q-file
|
||||||
v-model="file"
|
v-model="file"
|
||||||
standout="bg-blue-grey-9"
|
standout="bg-blue-grey-9"
|
||||||
dense
|
|
||||||
use-chips
|
|
||||||
multiple
|
|
||||||
stack-label
|
stack-label
|
||||||
:label="$t('timesheet.expense.hints.attach_file')"
|
:label="$t('timesheet.expense.hints.attach_file')"
|
||||||
class="col full-width q-my-xs"
|
class="col full-width q-my-xs"
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,24 @@
|
||||||
import type { Expense } from 'src/modules/timesheets/models/expense.models';
|
import type { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||||
import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue';
|
import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue';
|
||||||
|
|
||||||
|
// =========== state =====================================
|
||||||
|
|
||||||
const expense = defineModel<Expense>({ required: true })
|
const expense = defineModel<Expense>({ required: true })
|
||||||
|
|
||||||
const expenses_store = useExpensesStore();
|
const expenses_store = useExpensesStore();
|
||||||
const expenses_api = useExpensesApi();
|
const expenses_api = useExpensesApi();
|
||||||
|
|
||||||
const approved_class = computed(() => expense.value.is_approved ? ' bg-accent text-white' : '')
|
|
||||||
const is_showing_update_form = ref(false);
|
const is_showing_update_form = ref(false);
|
||||||
|
const is_showing_delete_confirm = ref(false);
|
||||||
|
|
||||||
|
// =========== computed ==================================
|
||||||
|
|
||||||
|
const approved_class = computed(() => expense.value.is_approved ? ' bg-accent text-white' : '')
|
||||||
|
|
||||||
|
// =========== methods ===================================
|
||||||
|
|
||||||
const requestExpenseDeletion = async () => {
|
const requestExpenseDeletion = async () => {
|
||||||
|
showDeleteConfirmation(false);
|
||||||
await expenses_api.deleteExpenseById(expense.value.id);
|
await expenses_api.deleteExpenseById(expense.value.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,84 +40,139 @@
|
||||||
expenses_store.current_expense = expense.value;
|
expenses_store.current_expense = expense.value;
|
||||||
expenses_store.initial_expense = unwrapAndClone(expense.value);
|
expenses_store.initial_expense = unwrapAndClone(expense.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showDeleteConfirmation = (state: boolean) => {
|
||||||
|
is_showing_delete_confirm.value = state;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="column bg-dark shadow-5 rounded-5 q-my-sm full-width">
|
<div class="column bg-dark shadow-5 rounded-5 q-my-sm full-width">
|
||||||
|
<!-- expense deletion confirmation -->
|
||||||
|
<q-dialog
|
||||||
|
v-model="is_showing_delete_confirm"
|
||||||
|
backdrop-filter="blur(4px)"
|
||||||
|
class="z-max"
|
||||||
|
>
|
||||||
|
<div class="column rounded-5 bg-dark">
|
||||||
|
<span class="col text-uppercase text-weight-light text-h6 q-py-sm q-px-md">
|
||||||
|
{{ $t('timesheet.expense.actions.delete_confirm') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="row col">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
square
|
||||||
|
size="lg"
|
||||||
|
color="negative"
|
||||||
|
:label="$t('shared.misc.no')"
|
||||||
|
class="col"
|
||||||
|
@click="showDeleteConfirmation(false)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
square
|
||||||
|
size="lg"
|
||||||
|
color="accent"
|
||||||
|
:label="$t('shared.misc.yes')"
|
||||||
|
class="col"
|
||||||
|
@click="requestExpenseDeletion"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-model="is_showing_update_form"
|
v-model="is_showing_update_form"
|
||||||
hide-expand-icon
|
hide-expand-icon
|
||||||
dense
|
dense
|
||||||
group="expenses"
|
group="expenses"
|
||||||
|
header-class="q-px-none"
|
||||||
|
class="rounded-5"
|
||||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||||
@before-show="onUpdateClicked()"
|
@before-show="onUpdateClicked()"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="column col">
|
<div class="row full-width">
|
||||||
<!-- date label -->
|
<div class="column col q-px-sm q-py-xs">
|
||||||
<div class="col-auto row items-center q-pl-xs">
|
<!-- date label and delete button -->
|
||||||
<q-icon
|
<div class="col-auto row items-center q-pl-xs">
|
||||||
name="calendar_month"
|
<q-icon
|
||||||
size="sm"
|
name="calendar_month"
|
||||||
class="col-auto"
|
size="sm"
|
||||||
/>
|
class="col-auto"
|
||||||
|
/>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="col text-uppercase text-weight-light full-width q-pl-sm text-h6"
|
class="col text-uppercase text-weight-light full-width q-pl-sm text-h6"
|
||||||
:class="approved_class"
|
:class="approved_class"
|
||||||
>
|
>
|
||||||
{{ $d(
|
{{ $d(
|
||||||
date.extractDate(expense.date, 'YYYY-MM-DD'),
|
date.extractDate(expense.date, 'YYYY-MM-DD'),
|
||||||
{ month: 'long', day: 'numeric' }
|
{ month: 'long', day: 'numeric' }
|
||||||
) }}
|
) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col row full-width items-center">
|
<q-btn
|
||||||
<!-- avatar type icon section -->
|
flat
|
||||||
<q-icon
|
dense
|
||||||
:name="getExpenseIcon(expense.type)"
|
icon="las la-trash"
|
||||||
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')"
|
color="primary"
|
||||||
size="lg"
|
size="lg"
|
||||||
class="col-auto"
|
class="col-auto bg-dark q-px-xs"
|
||||||
/>
|
style="border-radius: 0 2px 2px 0;"
|
||||||
|
@click.stop="showDeleteConfirmation(true)"
|
||||||
<!-- amount or mileage section -->
|
/>
|
||||||
<div class="col text-weight-bold text-h6">
|
|
||||||
<q-item-label v-if="expense.type === 'MILEAGE'">
|
|
||||||
{{ expense.mileage?.toFixed(1) }} km
|
|
||||||
</q-item-label>
|
|
||||||
<q-item-label v-else>
|
|
||||||
$ {{ expense.amount.toFixed(2) }}
|
|
||||||
</q-item-label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- attachment file icon -->
|
<div class="col row full-width items-center q-px-xs">
|
||||||
<div class="col-auto q-px-xs">
|
<!-- avatar type icon section -->
|
||||||
|
<q-icon
|
||||||
|
:name="getExpenseIcon(expense.type)"
|
||||||
|
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')"
|
||||||
|
size="lg"
|
||||||
|
class="col-auto q-pr-sm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- amount or mileage section -->
|
||||||
|
<div class="col text-weight-bold text-h6">
|
||||||
|
<q-item-label v-if="expense.type === 'MILEAGE'">
|
||||||
|
{{ expense.mileage?.toFixed(1) }} km
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label v-else>
|
||||||
|
$ {{ expense.amount.toFixed(2) }}
|
||||||
|
</q-item-label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- attachment file -->
|
||||||
|
<div class="col-auto q-pa-xs full-width">
|
||||||
<q-btn
|
<q-btn
|
||||||
push
|
|
||||||
:color="expense.is_approved ? 'white' : 'accent'"
|
:color="expense.is_approved ? 'white' : 'accent'"
|
||||||
:text-color="expense.is_approved ? 'accent' : 'white'"
|
:text-color="expense.is_approved ? 'accent' : 'white'"
|
||||||
class="col-auto q-mx-sm q-px-sm q-pb-sm"
|
icon="las la-paperclip"
|
||||||
icon="attach_file"
|
:label="expense.attachment_name ?? `( ${$t('shared.label.empty')} )`"
|
||||||
/>
|
class="full-width text-lowercase q-mx-sm q-px-sm q-pb-sm inset-shadow"
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-icon
|
|
||||||
v-if="expense.is_approved"
|
|
||||||
name="verified"
|
|
||||||
color="white"
|
|
||||||
size="lg"
|
|
||||||
class="full-height"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<q-icon
|
||||||
|
v-if="expense.is_approved"
|
||||||
|
name="verified"
|
||||||
|
color="white"
|
||||||
|
size="lg"
|
||||||
|
class="full-height q-px-none"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="q-px-sm">
|
<div class="q-px-sm">
|
||||||
<ExpenseDialogFormMobile />
|
<ExpenseDialogFormMobile v-model="expense" />
|
||||||
</div>
|
</div>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,18 @@
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, inject, onMounted, ref } from 'vue';
|
||||||
import { QSelect, QInput, useQuasar, type QSelectProps } from 'quasar';
|
import { QSelect, QInput, useQuasar, type QSelectProps, QPopupProxy } from 'quasar';
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
import { SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
import { SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
||||||
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||||
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
|
||||||
// ================== State ==================
|
// ================== State ==================
|
||||||
|
|
||||||
const COMMENT_LENGTH_MAX = 280;
|
const shift = defineModel<Shift>('shift', { required: true });
|
||||||
|
|
||||||
const { errorMessage = undefined, isTimesheetApproved = false, holiday = false } = defineProps<{
|
const { errorMessage = undefined, isTimesheetApproved = false, holiday = false } = defineProps<{
|
||||||
dense?: boolean;
|
|
||||||
isTimesheetApproved?: boolean;
|
isTimesheetApproved?: boolean;
|
||||||
errorMessage?: string | undefined;
|
errorMessage?: string | undefined;
|
||||||
holiday?: boolean | undefined;
|
holiday?: boolean | undefined;
|
||||||
|
|
@ -25,19 +25,30 @@
|
||||||
'onTimeFieldBlur': [void];
|
'onTimeFieldBlur': [void];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const shift = defineModel<Shift>('shift', { required: true });
|
|
||||||
|
const COMMENT_LENGTH_MAX = 280;
|
||||||
|
|
||||||
const q = useQuasar();
|
const q = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const ui_store = useUiStore();
|
const ui_store = useUiStore();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const mode = inject<'normal' | 'approval'>('mode');
|
||||||
|
|
||||||
const shiftTypeSelected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
const shiftTypeSelected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||||
const selectRef = ref<QSelect | null>(null);
|
const selectRef = ref<QSelect | null>(null);
|
||||||
const shiftErrorMessage = ref<string | undefined>();
|
const shiftErrorMessage = ref<string | undefined>();
|
||||||
const is_showing_delete_confirm = ref(false);
|
const is_showing_delete_confirm = ref(false);
|
||||||
|
const popupProxyRef = ref<QPopupProxy | null>(null);
|
||||||
|
|
||||||
// ================== Computed ==================
|
// ================== Computed ==================
|
||||||
|
|
||||||
|
const rightClickMenuIcon = computed(() => shift.value.is_approved ? 'lock_open' : 'lock');
|
||||||
|
|
||||||
|
const rightClickMenuLabel = computed(() => shift.value.is_approved ?
|
||||||
|
t('timesheet_approvals.tooltip.unapprove') :
|
||||||
|
t('timesheet_approvals.tooltip.approve'));
|
||||||
|
|
||||||
const timeInputProps = computed(() => ({
|
const timeInputProps = computed(() => ({
|
||||||
dense: true,
|
dense: true,
|
||||||
borderless: shift.value.is_approved && isTimesheetApproved,
|
borderless: shift.value.is_approved && isTimesheetApproved,
|
||||||
|
|
@ -109,6 +120,14 @@
|
||||||
is_showing_delete_confirm.value = state;
|
is_showing_delete_confirm.value = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onRightClickApprove = () => {
|
||||||
|
if (authStore.user?.user_module_access.includes('timesheets_approval'))
|
||||||
|
shift.value.is_approved = !shift.value.is_approved;
|
||||||
|
|
||||||
|
if (popupProxyRef.value)
|
||||||
|
popupProxyRef.value.hide();
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (ui_store.focus_next_component) {
|
if (ui_store.focus_next_component) {
|
||||||
selectRef.value?.focus();
|
selectRef.value?.focus();
|
||||||
|
|
@ -121,6 +140,31 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<!-- right-click to approve shift only (if in approval mode) -->
|
||||||
|
<q-popup-proxy
|
||||||
|
v-if="mode === 'approval'"
|
||||||
|
ref="popupProxyRef"
|
||||||
|
context-menu
|
||||||
|
class="rounded-5 q-px-md shadow-24 cursor-pointer"
|
||||||
|
style="border: 3px solid var(--q-primary);"
|
||||||
|
>
|
||||||
|
<q-banner
|
||||||
|
dense
|
||||||
|
class="cursor-pointer q-px-lg"
|
||||||
|
@click="onRightClickApprove"
|
||||||
|
>
|
||||||
|
<template v-slot:avatar>
|
||||||
|
<q-icon
|
||||||
|
:name="rightClickMenuIcon"
|
||||||
|
color="accent"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<span class="text-weight-bold text-accent text-uppercase">
|
||||||
|
{{ rightClickMenuLabel }}
|
||||||
|
</span>
|
||||||
|
</q-banner>
|
||||||
|
</q-popup-proxy>
|
||||||
|
|
||||||
<!-- delete shift confirmation dialog -->
|
<!-- delete shift confirmation dialog -->
|
||||||
<q-dialog
|
<q-dialog
|
||||||
v-model="is_showing_delete_confirm"
|
v-model="is_showing_delete_confirm"
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@
|
||||||
shift.id = 0;
|
shift.id = 0;
|
||||||
emit('deleteUnsavedShift');
|
emit('deleteUnsavedShift');
|
||||||
} else {
|
} else {
|
||||||
console.log('email: ', employeeEmail);
|
|
||||||
await shift_api.deleteShiftById(shift.id, employeeEmail);
|
await shift_api.deleteShiftById(shift.id, employeeEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,9 @@
|
||||||
));
|
));
|
||||||
|
|
||||||
// =================== methods ==========================
|
// =================== methods ==========================
|
||||||
|
|
||||||
provide('employeeEmail', employeeEmail);
|
provide('employeeEmail', employeeEmail);
|
||||||
|
provide('mode', mode);
|
||||||
|
|
||||||
const onClickSaveTimesheets = async () => {
|
const onClickSaveTimesheets = async () => {
|
||||||
if (mode === 'normal') {
|
if (mode === 'normal') {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ export class Expense {
|
||||||
type: ExpenseType;
|
type: ExpenseType;
|
||||||
amount: number;
|
amount: number;
|
||||||
mileage?: number;
|
mileage?: number;
|
||||||
|
attachment_name?: string;
|
||||||
|
attachment_key?: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
supervisor_comment?: string;
|
supervisor_comment?: string;
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@ export const useEmployeeStore = defineStore('employee', () => {
|
||||||
const is_loading = ref(false);
|
const is_loading = ref(false);
|
||||||
|
|
||||||
const openAddModifyDialog = async (employee_email?: string) => {
|
const openAddModifyDialog = async (employee_email?: string) => {
|
||||||
|
|
||||||
if (employee_email === undefined) {
|
if (employee_email === undefined) {
|
||||||
management_mode.value = 'add_employee'
|
management_mode.value = 'add_employee'
|
||||||
employee.value = new EmployeeProfile();
|
employee.value = new EmployeeProfile();
|
||||||
is_add_modify_dialog_open.value = true;
|
is_add_modify_dialog_open.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_loading.value = true;
|
is_loading.value = true;
|
||||||
management_mode.value = 'modify_employee';
|
management_mode.value = 'modify_employee';
|
||||||
await getEmployeeDetails(employee_email);
|
await getEmployeeDetails(employee_email);
|
||||||
|
|
@ -65,25 +65,29 @@ export const useEmployeeStore = defineStore('employee', () => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createOrUpdateEmployee = async (profile: EmployeeProfile) => {
|
const createOrUpdateEmployee = async (profile: EmployeeProfile): Promise<boolean> => {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
if (management_mode.value === 'add_employee') {
|
if (management_mode.value === 'add_employee') {
|
||||||
const { birth_date, last_work_day, ...create_payload} = profile;
|
const { birth_date, last_work_day, ...create_payload } = profile;
|
||||||
response = await EmployeeListService.createNewEmployee(create_payload);
|
response = await EmployeeListService.createNewEmployee(create_payload);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
response = await EmployeeListService.updateEmployee(profile);
|
response = await EmployeeListService.updateEmployee(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAddModifyDialog();
|
closeAddModifyDialog();
|
||||||
|
|
||||||
if (response.success) await getEmployeeList();
|
if (response.success)
|
||||||
else {
|
await getEmployeeList();
|
||||||
|
|
||||||
|
else
|
||||||
Notify.create({
|
Notify.create({
|
||||||
message: 'failed to update or create employee',
|
message: 'failed to update or create employee',
|
||||||
color: 'negative',
|
color: 'negative',
|
||||||
})}
|
});
|
||||||
|
|
||||||
|
return response.success;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,39 +7,49 @@ import { type PresetManagerMode, SchedulePreset, SchedulePresetFrontend } from "
|
||||||
export const useSchedulePresetsStore = defineStore('schedule_presets_store', () => {
|
export const useSchedulePresetsStore = defineStore('schedule_presets_store', () => {
|
||||||
const schedule_presets = ref<SchedulePreset[]>([new SchedulePreset]);
|
const schedule_presets = ref<SchedulePreset[]>([new SchedulePreset]);
|
||||||
const current_schedule_preset = ref<SchedulePresetFrontend>(new SchedulePresetFrontend);
|
const current_schedule_preset = ref<SchedulePresetFrontend>(new SchedulePresetFrontend);
|
||||||
const schedule_preset_dialog_mode = ref<PresetManagerMode>('create');
|
const schedule_preset_dialog_mode = ref<PresetManagerMode | undefined>();
|
||||||
const is_manager_open = ref(false);
|
const isManagerOpen = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the schedule preset manager with the preset associated with provided ID. If mode is
|
||||||
|
* set to `copy`, a clone of the preset will be created with a blank name instead.
|
||||||
|
* @param preset_id
|
||||||
|
*/
|
||||||
const openSchedulePresetManager = (preset_id: number) => {
|
const openSchedulePresetManager = (preset_id: number) => {
|
||||||
if (preset_id === -1)
|
if (preset_id === -1) {
|
||||||
current_schedule_preset.value = new SchedulePresetFrontend;
|
current_schedule_preset.value = new SchedulePresetFrontend;
|
||||||
else if (schedule_preset_dialog_mode.value === 'copy') {
|
} else if (schedule_preset_dialog_mode.value === 'copy') {
|
||||||
const preset = schedule_presets.value.find(preset => preset.id === preset_id)!;
|
const preset = schedule_presets.value.find(preset => preset.id === preset_id)!;
|
||||||
const copied_preset = new SchedulePresetFrontend(preset);
|
const copied_preset = new SchedulePresetFrontend(preset);
|
||||||
copied_preset.id = -1;
|
copied_preset.id = -1;
|
||||||
copied_preset.name = "";
|
copied_preset.name = "";
|
||||||
current_schedule_preset.value = copied_preset;
|
current_schedule_preset.value = copied_preset;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
setCurrentSchedulePreset(preset_id);
|
setCurrentSchedulePreset(preset_id);
|
||||||
|
|
||||||
is_manager_open.value = true;
|
isManagerOpen.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCurrentSchedulePreset = (preset_id: number) => {
|
const setCurrentSchedulePreset = (preset_id: number) => {
|
||||||
if (preset_id === -1) {
|
if (preset_id === -1)
|
||||||
current_schedule_preset.value = new SchedulePresetFrontend;
|
current_schedule_preset.value = new SchedulePresetFrontend;
|
||||||
return;
|
else
|
||||||
}
|
current_schedule_preset.value = new SchedulePresetFrontend(
|
||||||
current_schedule_preset.value = new SchedulePresetFrontend(schedule_presets.value.find(preset => preset.id === preset_id))
|
schedule_presets.value.find(preset => preset.id === preset_id)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createSchedulePreset = async (preset: SchedulePreset): Promise<boolean> => {
|
const createSchedulePreset = async (preset: SchedulePreset): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await SchedulePresetsService.createSchedulePresets(preset);
|
const response = await SchedulePresetsService.createSchedulePresets(preset);
|
||||||
|
|
||||||
|
if (response.success && response.data)
|
||||||
|
current_schedule_preset.value = new SchedulePresetFrontend(response.data);
|
||||||
|
|
||||||
return response.success;
|
return response.success;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('DEV ERROR || error while creating schedule preset: ', error);
|
console.error('DEV ERROR || error while creating schedule preset: ', error);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -47,9 +57,11 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
|
||||||
const updateSchedulePreset = async (preset: SchedulePreset): Promise<boolean> => {
|
const updateSchedulePreset = async (preset: SchedulePreset): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await SchedulePresetsService.updateSchedulePresets(preset);
|
const response = await SchedulePresetsService.updateSchedulePresets(preset);
|
||||||
|
|
||||||
return response.success;
|
return response.success;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('DEV ERROR || error while updating schedule preset: ', error);
|
console.error('DEV ERROR || error while updating schedule preset: ', error);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,17 +70,20 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
|
||||||
const deleteSchedulePreset = async (preset_id: number): Promise<boolean> => {
|
const deleteSchedulePreset = async (preset_id: number): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await SchedulePresetsService.deleteSchedulePresets(preset_id);
|
const response = await SchedulePresetsService.deleteSchedulePresets(preset_id);
|
||||||
|
|
||||||
return response.success;
|
return response.success;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('DEV ERROR || error while deleting schedule preset: ', error);
|
console.error('DEV ERROR || error while deleting schedule preset: ', error);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findSchedulePresetList = async (): Promise<boolean> => {
|
const getSchedulePresetList = async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await SchedulePresetsService.getSchedulePresetsList();
|
const response = await SchedulePresetsService.getSchedulePresetsList();
|
||||||
if (response.success && response.data) schedule_presets.value = response.data;
|
if (response.success && response.data)
|
||||||
|
schedule_presets.value = response.data;
|
||||||
|
|
||||||
return response.success;
|
return response.success;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -82,12 +97,12 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
|
||||||
schedule_presets,
|
schedule_presets,
|
||||||
current_schedule_preset,
|
current_schedule_preset,
|
||||||
schedule_preset_dialog_mode,
|
schedule_preset_dialog_mode,
|
||||||
is_manager_open,
|
isManagerOpen,
|
||||||
setCurrentSchedulePreset,
|
setCurrentSchedulePreset,
|
||||||
openSchedulePresetManager,
|
openSchedulePresetManager,
|
||||||
createSchedulePreset,
|
createSchedulePreset,
|
||||||
updateSchedulePreset,
|
updateSchedulePreset,
|
||||||
deleteSchedulePreset,
|
deleteSchedulePreset,
|
||||||
findSchedulePresetList,
|
getSchedulePresetList,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -98,7 +98,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('There was an error retrieving Employee Pay Period overviews: ', error);
|
console.error('There was an error retrieving Employee Pay Period overviews: ', error);
|
||||||
pay_period_overviews.value = [];
|
pay_period_overviews.value = [];
|
||||||
// TODO: More in-depth error-handling here
|
|
||||||
is_loading.value = false;
|
is_loading.value = false;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -107,7 +106,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
|
|
||||||
const getTimesheetsByOptionalEmployeeEmail = async (employee_email?: string): Promise<boolean> => {
|
const getTimesheetsByOptionalEmployeeEmail = async (employee_email?: string): Promise<boolean> => {
|
||||||
if (pay_period.value === undefined) return false;
|
if (pay_period.value === undefined) return false;
|
||||||
is_loading.value = true;
|
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -128,13 +126,11 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
initial_timesheets.value = [];
|
initial_timesheets.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
is_loading.value = false;
|
|
||||||
return response.success;
|
return response.success;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
||||||
// TODO: More in-depth error-handling here
|
|
||||||
timesheets.value = [];
|
timesheets.value = [];
|
||||||
is_loading.value = false;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user