From 6dc1804918cd1f56737368200d6c68926fb1dcf3 Mon Sep 17 00:00:00 2001 From: Nic D Date: Tue, 3 Feb 2026 13:09:46 -0500 Subject: [PATCH] refactor(employee-list): rework schedule preset selector, fix other ui issues, see notes Add warning dialog when changes in an employee profile are unsaved. Move schedule preset buttons from side of selector to side of each option in select menu. optimize behavior of selector: will now switch to empty when deleting currently assigned preset, and will assign automatically any new or copied preset to current employee. --- src/i18n/en-ca/index.ts | 5 +- src/i18n/fr-ca/index.ts | 3 + .../components/add-modify-dialog-schedule.vue | 174 +++++++++++------- .../components/add-modify-dialog.vue | 85 ++++++++- .../components/employee-list-table.vue | 5 +- .../schedule-presets-dialog-delete.vue | 26 ++- .../components/schedule-presets-dialog.vue | 49 +++-- .../composables/use-employee-api.ts | 107 ++++++----- .../employee-list/employee-list-utils.ts | 5 +- .../services/schedule-presets-service.ts | 2 +- src/stores/employee-store.ts | 24 ++- src/stores/schedule-presets.store.ts | 45 +++-- 12 files changed, 365 insertions(+), 165 deletions(-) diff --git a/src/i18n/en-ca/index.ts b/src/i18n/en-ca/index.ts index aae1f5b..db4d786 100644 --- a/src/i18n/en-ca/index.ts +++ b/src/i18n/en-ca/index.ts @@ -109,10 +109,11 @@ export default { banked_hours: "available banked hours", sick_hours: "available PTO hours", vacation_hours: "available vacation hours", + save_changes_notification: "save changes to employee profile?", schedule_presets: { preset_list_placeholder: "Select a schedule", 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_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,11 +238,13 @@ export default { cancel: "cancel", update: "update", modify: "modify", + copy: "copy", close: "close", download: "download", open: "open", day: "day", empty: "empty", + name: "name", }, misc: { or: "or", diff --git a/src/i18n/fr-ca/index.ts b/src/i18n/fr-ca/index.ts index dbe6c6e..935cdb4 100644 --- a/src/i18n/fr-ca/index.ts +++ b/src/i18n/fr-ca/index.ts @@ -109,6 +109,7 @@ export default { banked_hours: "heures en banque disponibles", sick_hours: "heures d'absence payées disponibles", vacation_hours: "heures de vacances disponibles", + save_changes_notification: "Sauvegarder les modifications du profil?", schedule_presets: { preset_list_placeholder: "Sélectionner un horaire", preset_name_placeholder: "nom de l'horaire", @@ -237,11 +238,13 @@ export default { cancel: "annuler", update: "mettre à jour", modify: "modifier", + copy: "copier", close: "fermer", download: "télécharger", open: "ouvrir", day: "jour", empty: "vide", + name: "nom", }, misc: { or: "ou", diff --git a/src/modules/employee-list/components/add-modify-dialog-schedule.vue b/src/modules/employee-list/components/add-modify-dialog-schedule.vue index 4f73c10..c82edcc 100644 --- a/src/modules/employee-list/components/add-modify-dialog-schedule.vue +++ b/src/modules/employee-list/components/add-modify-dialog-schedule.vue @@ -2,43 +2,58 @@ setup 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 AddModifyDialogSchedulePreview from './add-modify-dialog-schedule-preview.vue'; + import { useI18n } from 'vue-i18n'; import { onMounted, ref } from 'vue'; import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store'; import { useEmployeeStore } from 'src/stores/employee-store'; import { useEmployeeListApi } from '../composables/use-employee-api'; 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 employee_store = useEmployeeStore(); const employee_list_api = useEmployeeListApi(); - const preset_options = ref<{ label: string, value: number }[]>([]); - const current_preset = ref<{ label: string | undefined, value: number }>({ label: undefined, value: -1 }); + const preset_options = ref[]>([]); + const selected_preset = ref>({ label: '', value: -1 }); - // ====================== methods ======================== + // ========== methods ======================================== + + const getPresetOptions = (): QSelectOption[] => { + const options: QSelectOption[] = [{ 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; }; const onClickSchedulePresetManager = (mode: PresetManagerMode, preset_id?: number) => { 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 = () => { preset_options.value = getPresetOptions(); - const current_option = preset_options.value.find(option => option.value === employee_store.employee.preset_id); - current_preset.value = current_option ?? { label: undefined, value: -1 }; - schedule_preset_store.setCurrentSchedulePreset(current_preset.value.value); + const employee = employee_store.employee; + + 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(() => { @@ -48,76 +63,111 @@ \ No newline at end of file diff --git a/src/modules/employee-list/components/add-modify-dialog.vue b/src/modules/employee-list/components/add-modify-dialog.vue index df83a8d..307f4c0 100644 --- a/src/modules/employee-list/components/add-modify-dialog.vue +++ b/src/modules/employee-list/components/add-modify-dialog.vue @@ -8,21 +8,52 @@ import { ref } from 'vue'; import { useEmployeeStore } from 'src/stores/employee-store'; - import { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models'; + + // ========== state ======================================== const employee_store = useEmployeeStore(); 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; + } \ No newline at end of file diff --git a/src/modules/employee-list/components/employee-list-table.vue b/src/modules/employee-list/components/employee-list-table.vue index 0b06c64..b41d05d 100644 --- a/src/modules/employee-list/components/employee-list-table.vue +++ b/src/modules/employee-list/components/employee-list-table.vue @@ -147,7 +147,6 @@ color="accent" bg-color="white" label-color="accent" - class="text-primary" debounce="300" :label="$t('shared.label.search')" > @@ -298,4 +297,8 @@ tbody { :deep(.q-table__grid-content) { overflow: auto } + +:deep(.q-field__native) { + color: var(--q-primary); +} \ No newline at end of file diff --git a/src/modules/employee-list/components/schedule-presets-dialog-delete.vue b/src/modules/employee-list/components/schedule-presets-dialog-delete.vue index 23e82b7..f005bb4 100644 --- a/src/modules/employee-list/components/schedule-presets-dialog-delete.vue +++ b/src/modules/employee-list/components/schedule-presets-dialog-delete.vue @@ -6,17 +6,37 @@ import { useEmployeeStore } from 'src/stores/employee-store'; import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api'; - const employee_store = useEmployeeStore(); - const employee_list_api = useEmployeeListApi(); + // ========== state =================================== const { presetId } = defineProps<{ presetId: number; }>(); + const emit = defineEmits<{ + 'onConfirmDelete': [void]; + }>(); + + const employee_store = useEmployeeStore(); + const employee_list_api = useEmployeeListApi(); + const employee_amount_using_preset = ref(0); const delete_input_string = ref(''); + + // ========== computed ================================== + 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(() => { const employees_with_preset = employee_store.employee_list.filter(employee => employee.preset_id === presetId); employee_amount_using_preset.value = employees_with_preset.length; @@ -68,7 +88,7 @@ :color="is_approve_deletion ? 'negative' : 'grey-6'" :label="$t('shared.label.remove')" class="q-px-md" - @click="employee_list_api.deleteSchedulePreset(presetId)" + @click="onClickDeleteConfirm" /> diff --git a/src/modules/employee-list/components/schedule-presets-dialog.vue b/src/modules/employee-list/components/schedule-presets-dialog.vue index d4f1585..8eac059 100644 --- a/src/modules/employee-list/components/schedule-presets-dialog.vue +++ b/src/modules/employee-list/components/schedule-presets-dialog.vue @@ -10,18 +10,35 @@ import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store'; 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 onClickSaveSchedulePreset = async () => { + const success = await employee_list_api.saveSchedulePreset(); + + if (success) + closePresetManager(); + } + + const closePresetManager = () => { + emit('onClose'); + schedulePresetStore.isManagerOpen = false; + }