fix(presets): fix issue with shifts in preset editing getting sorted reactively, due to shifts being unsorted from backend.

Backend now sorts shifts before sending to front.
This commit is contained in:
Nicolas Drolet 2025-12-12 14:54:25 -05:00
parent a2103a306b
commit 34f1ce5762
11 changed files with 168 additions and 27 deletions

View File

@ -21,9 +21,13 @@ export default {
access_label: "access",
details_label: "details",
schedule_label: "schedule",
enter_delete_input: "type 'DELETE' to remove",
schedule_presets: {
preset_list_placeholder: "Select a schedule",
preset_name_placeholder: "schedule preset name",
delete_warning: "",
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.",
},
module_access: {
dashboard: "Dashboard",

View File

@ -21,9 +21,13 @@ export default {
access_label: "accès",
details_label: "détails",
schedule_label: "horaire",
enter_delete_input: "tappez 'SUPPRIMER' pour confirmer",
schedule_presets: {
preset_list_placeholder: "Sélectionner un horaire",
preset_name_placeholder: "nom de l'horaire",
delete_warning: "Êtes-vous certain de vouloir supprimer cet horaire?",
delete_warning_employee_1: "Cet horaire est présentement utilisé par",
delete_warning_employee_2: "La suppression n'affectera pas leurs feuilles de temps antérieures, mais ils ne pourront plus appliquer cet horaire à leurs feuilles de temps à partir de maintenant.",
},
module_access: {
dashboard: "Accueil",

View File

@ -2,7 +2,7 @@
setup
lang="ts"
>
import { date } from 'quasar';
// import { date } from 'quasar';
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
const schedule_preset_store = useSchedulePresetsStore();
@ -27,7 +27,7 @@ import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
<span class="col-2 text-weight-bolder text-accent text-uppercase text-overline" style="font-size: 1.3em;">{{
$t(`shared.weekday.${weekday.day.toLowerCase()}`) }}</span>
<div
v-for="shift, index in weekday.shifts.sort((a, b) => date.extractDate(a.start_time, 'HH:mm').getTime() - date.extractDate(b.start_time, 'HH:mm').getTime())"
v-for="shift, index in weekday.shifts"
:key="index"
class="col q-px-md q-py-xs"
>

View File

@ -3,13 +3,14 @@
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 { onMounted, ref } from 'vue';
import { onMounted, ref, watch } 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 { useEmployeeListApi } from '../composables/use-employee-api';
import type { PresetManagerMode } from 'src/modules/employee-list/models/schedule-presets.models';
const schedule_preset_store = useSchedulePresetsStore();
const employee_store = useEmployeeStore();
@ -17,6 +18,7 @@ import { useEmployeeListApi } from '../composables/use-employee-api';
const preset_options = ref<{ label: string, value: number }[]>([]);
const current_preset = ref<{ label: string | undefined, value: number }>({ label: undefined, value: -1 });
const manager_watcher = ref(schedule_preset_store.is_manager_open);
const getPresetOptions = (): { label: string, value: number }[] => {
const options = schedule_preset_store.schedule_presets.map(preset => { return { label: preset.name, value: preset.id } });
@ -24,16 +26,31 @@ import { useEmployeeListApi } from '../composables/use-employee-api';
return options;
};
onMounted(() => {
const onClickSchedulePresetManager = (mode: PresetManagerMode, preset_id?: number) => {
schedule_preset_store.schedule_preset_dialog_mode = mode;
console.log('preset id: ', preset_id);
schedule_preset_store.openSchedulePresetManager(preset_id ?? current_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);
};
onMounted(() => {
loadSelectedPresetOption();
});
watch(manager_watcher, loadSelectedPresetOption)
</script>
<template>
<div :key="schedule_preset_store.is_manager_open === false ? '0' : '1'" class="column full-width flex-center items-start">
<div
:key="schedule_preset_store.is_manager_open === false ? '0' : '1'"
class="column full-width flex-center items-start"
>
<SchedulePresetsDialog />
<div class="col row justify-center full-width no-wrap">
@ -72,26 +89,31 @@ import { useEmployeeListApi } from '../composables/use-employee-api';
icon="add"
color="accent"
class="col-auto q-px-sm q-ml-sm"
@click="schedule_preset_store.openSchedulePresetManager(-1)"
@click="onClickSchedulePresetManager('create', -1)"
/>
<HorizontalSlideTransition :show="current_preset !== undefined && current_preset?.value !== -1">
<transition
enter-active-class="animated zoomIn"
leave-active-class="animated zoomOut"
mode="out-in"
>
<div class="col-auto row no-wrap full-height">
<q-btn
v-if="current_preset !== undefined && current_preset?.value !== -1"
push
dense
rounded
icon="edit"
color="accent"
class="col-auto q-px-sm q-ml-sm full-height"
@click="schedule_preset_store.openSchedulePresetManager(current_preset.value)"
class="col-auto q-px-sm q-mx-sm full-height"
@click="onClickSchedulePresetManager('update')"
/>
</transition>
<q-btn
flat
dense
rounded
icon="clear"
color="negative"
class="col-auto q-px-sm full-height"
@click="onClickSchedulePresetManager('delete')"
/>
</div>
</HorizontalSlideTransition>
</div>

View File

@ -0,0 +1,75 @@
<script
setup
lang="ts"
>
import { computed, onMounted, ref } from 'vue';
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();
const { presetId } = defineProps<{
presetId: number;
}>();
const employee_amount_using_preset = ref(0);
const delete_input_string = ref('');
const is_approve_deletion = computed(() => ['SUPPRIMER', 'DELETE'].includes(delete_input_string.value));
onMounted(() => {
const employees_with_preset = employee_store.employee_list.filter(employee => employee.preset_id === presetId);
employee_amount_using_preset.value = employees_with_preset.length;
})
</script>
<template>
<div
class="column flex-center bg-secondary q-pa-md rounded-10 shadow-24"
style="border: 2px solid var(--q-negative); width: 40vw !important;"
>
<span class="col-auto text-weight-bold text-uppercase text-center text-negative text-h5 q-pb-lg">
{{ $t('shared.label.remove') }}
</span>
<div
v-if="employee_amount_using_preset > 0"
class="col row flex-center text-weight-medium text-center q-mb-lg"
>
<span class="col-auto">{{ $t('employee_management.schedule_presets.delete_warning_employee_1') }}</span>
<span class="col-auto q-px-sm text-h6 text-weight-bolder text-negative">{{ employee_amount_using_preset
}}</span>
<span class="col-auto">{{ $t('employee_management.module_access.preset_employee') +
(employee_amount_using_preset > 1 ? 's' : '') }}</span>
<span>{{ $t('employee_management.schedule_presets.delete_warning_employee_2') }}</span>
</div>
<div class="col">
<span class="text-weight-bold text-uppercase">{{ $t('employee_management.schedule_presets.delete_warning')
}}</span>
<q-input
v-model="delete_input_string"
standout
dense
rounded
:placeholder="$t('employee_management.enter_delete_input')"
input-class="text-center"
:input-style="delete_input_string.length > 0 ? '' : 'opacity: 0.6;'"
class="q-my-sm"
/>
</div>
<div class="col-auto row">
<q-space />
<q-btn
push
dense
:disable="!is_approve_deletion"
:color="is_approve_deletion ? 'negative' : 'grey-6'"
:label="$t('shared.label.remove')"
class="q-px-md"
@click="employee_list_api.deleteSchedulePreset(presetId)"
/>
</div>
</div>
</template>

View File

@ -3,6 +3,7 @@
lang="ts"
>
import SchedulePresetsDialogRow from './schedule-presets-dialog-row.vue';
import SchedulePresetsDialogDelete from 'src/modules/employee-list/components/schedule-presets-dialog-delete.vue';
import { useEmployeeListApi } from '../composables/use-employee-api';
import { SchedulePresetShift } from '../models/schedule-presets.models';
@ -17,7 +18,13 @@
v-model="schedule_preset_store.is_manager_open"
full-width
>
<SchedulePresetsDialogDelete
v-if="schedule_preset_store.schedule_preset_dialog_mode === 'delete'"
:preset-id="schedule_preset_store.current_schedule_preset.id"
/>
<div
v-else
class="column flex-center bg-secondary rounded-10 shadow-24"
style="border: 2px solid var(--q-accent); width: 50vw !important;"
>

View File

@ -37,8 +37,16 @@ export const useEmployeeListApi = () => {
else success = await schedule_preset_store.updateSchedulePreset(backend_preset);
if (success) {
schedule_preset_store.is_manager_open = false;
await schedule_preset_store.findSchedulePresetList();
schedule_preset_store.is_manager_open = false;
}
}
const deleteSchedulePreset = async(preset_id: number) => {
const success = await schedule_preset_store.deleteSchedulePreset(preset_id);
if (success) {
await schedule_preset_store.findSchedulePresetList();
schedule_preset_store.is_manager_open = false;
}
}
@ -47,5 +55,6 @@ export const useEmployeeListApi = () => {
getEmployeeDetails,
setSchedulePreset,
saveSchedulePreset,
deleteSchedulePreset,
};
};

View File

@ -2,7 +2,9 @@ import type { ShiftType } from "src/modules/timesheets/models/shift.models";
export type Weekday = 'SUN' | 'MON' | 'TUE' | 'WED' | 'THU' | 'FRI' | 'SAT';
export const WEEKDAYS: Weekday[] = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']
export const WEEKDAYS: Weekday[] = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
export type PresetManagerMode = 'create' | 'update' | 'copy' | 'delete';
export class SchedulePreset {
id: number;

View File

@ -13,7 +13,7 @@ export const SchedulePresetsService = {
return response.data;
},
deleteSchedulePresets: async (preset_id: number) => {
deleteSchedulePresets: async (preset_id: number): Promise<BackendResponse<boolean>> => {
const response = await api.delete(`/schedule-presets/delete/${preset_id}`);
return response.data;
},

View File

@ -5,6 +5,10 @@
import { ref } from 'vue';
import { Notify } from 'quasar';
const click_number = ref(1);
const icon = ref('las la-hand-peace');
const color = ref('accent');
const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et \
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip \
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \
@ -14,10 +18,22 @@
const slide = ref<string>('welcome');
const clickNotify = () => {
if (click_number.value % 7 === 0) {
icon.value = 'las la-hand-middle-finger';
color.value = 'negative';
}
else {
icon.value = 'las la-hand-peace';
color.value = 'accent';
}
Notify.create({
message: 'You clicked the little click button!',
color: 'info'
})
color: color.value,
icon: icon.value,
iconSize: '5em',
iconColor: 'white',
});
click_number.value += 1;
}
</script>

View File

@ -2,12 +2,13 @@
import { ref } from "vue";
import { defineStore } from "pinia";
import { SchedulePresetsService } from "src/modules/employee-list/services/schedule-presets-service";
import { SchedulePreset, SchedulePresetFrontend } from "src/modules/employee-list/models/schedule-presets.models";
import { type PresetManagerMode, SchedulePreset, SchedulePresetFrontend } from "src/modules/employee-list/models/schedule-presets.models";
export const useSchedulePresetsStore = defineStore('schedule_presets_store', () => {
const schedule_presets = ref<SchedulePreset[]>([new SchedulePreset]);
const current_schedule_preset = ref<SchedulePresetFrontend>(new SchedulePresetFrontend);
const schedule_preset_dialog_mode = ref<PresetManagerMode>('create');
const is_manager_open = ref(false);
const openSchedulePresetManager = (preset_id: number) => {
@ -51,11 +52,11 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
const deleteSchedulePreset = async (preset_id: number): Promise<boolean> => {
try {
await SchedulePresetsService.deleteSchedulePresets(preset_id);
return true;
const response = await SchedulePresetsService.deleteSchedulePresets(preset_id);
return response.success;
} catch (error) {
console.error('DEV ERROR || error while deleting schedule preset: ', error);
return false
return false;
}
};
@ -84,6 +85,7 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
return {
schedule_presets,
current_schedule_preset,
schedule_preset_dialog_mode,
is_manager_open,
setCurrentSchedulePreset,
openSchedulePresetManager,