@@ -128,6 +137,11 @@
min-height: 25px;
}
+ .weekday-field :deep(.q-field__marginal) {
+ height: 25px;
+ min-height: 25px;
+ }
+
:deep(.q-field--auto-height.q-field--dense .q-field__native) {
min-height: 25px;
}
diff --git a/src/modules/employee-list/components/schedule_presets_dialog.vue b/src/modules/employee-list/components/schedule-presets-dialog.vue
similarity index 81%
rename from src/modules/employee-list/components/schedule_presets_dialog.vue
rename to src/modules/employee-list/components/schedule-presets-dialog.vue
index 0d33281..d4f1585 100644
--- a/src/modules/employee-list/components/schedule_presets_dialog.vue
+++ b/src/modules/employee-list/components/schedule-presets-dialog.vue
@@ -3,10 +3,12 @@
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';
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
+ import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
const schedule_preset_store = useSchedulePresetsStore();
const employee_list_api = useEmployeeListApi();
@@ -17,8 +19,14 @@
v-model="schedule_preset_store.is_manager_open"
full-width
>
+
+
{
const employee_store = useEmployeeStore();
@@ -15,30 +16,67 @@ export const useEmployeeListApi = () => {
employee_store.is_loading = false;
};
- const getEmployeeDetails = async(email: string): Promise => {
+ const getEmployeeDetails = async (email: string): Promise => {
const success = await employee_store.getEmployeeDetails(email);
if (success && employee_store.employee.preset_id !== null) {
schedule_preset_store.setCurrentSchedulePreset(employee_store.employee.preset_id ?? -1);
}
}
-
+
const setSchedulePreset = (preset_id: number) => {
schedule_preset_store.setCurrentSchedulePreset(preset_id);
employee_store.employee.preset_id = preset_id < 0 ? null : preset_id;
}
- const saveSchedulePreset = async() => {
+ const saveSchedulePreset = async () => {
+ // Get the currently edited schedule preset from the store (frontend model)
const preset = schedule_preset_store.current_schedule_preset;
+
+ // Check if there's any overlap between shifts. If there is, is_error property
+ // will be toggled to true and save process will stop
+ for (const weekday of preset.weekdays) {
+ weekday.is_error = isShiftOverlap(weekday.shifts);
+ }
+
+ console.log('current preset: ', preset);
+
+ if (preset.weekdays.some(weekday => weekday.is_error)) {
+ console.log('overlap!');
+ return;
+ }
+
+ // Flatten all weekday shifts into a single array
const preset_shifts = preset.weekdays.flatMap(weekday => weekday.shifts);
- const backend_preset = new SchedulePreset(preset.id, preset.name, preset.is_default, preset_shifts);
+
+ // Build a backend-compatible SchedulePreset instance
+ const backend_preset = new SchedulePreset(
+ preset.id,
+ preset.name,
+ preset.is_default,
+ preset_shifts
+ );
+
+ // Track whether the create/update operation succeeds
let success = false;
- if (preset.id === -1) success = await schedule_preset_store.createSchedulePreset(backend_preset);
- else success = await schedule_preset_store.updateSchedulePreset(backend_preset);
-
+ // Create a new preset if it has no backend ID, otherwise update the existing one
+ if (preset.id === -1)
+ success = await schedule_preset_store.createSchedulePreset(backend_preset);
+ else
+ success = await schedule_preset_store.updateSchedulePreset(backend_preset);
+
+ // On success, refresh the preset list and close the preset manager UI
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 +85,6 @@ export const useEmployeeListApi = () => {
getEmployeeDetails,
setSchedulePreset,
saveSchedulePreset,
+ deleteSchedulePreset,
};
};
\ No newline at end of file
diff --git a/src/modules/employee-list/models/employee-profile.models.ts b/src/modules/employee-list/models/employee-profile.models.ts
index ad7520e..b53dc9a 100644
--- a/src/modules/employee-list/models/employee-profile.models.ts
+++ b/src/modules/employee-list/models/employee-profile.models.ts
@@ -34,7 +34,7 @@ export class EmployeeProfile {
this.residence = '';
this.birth_date = '';
this.is_supervisor = false;
- this.external_payroll_id = -1;
+ this.external_payroll_id = 999;
this.user_module_access = ['dashboard',];
}
}
@@ -85,7 +85,6 @@ export const employee_list_columns: QTableColumn[] = [
label: 'employee_list.table.role',
field: 'job_title',
align: 'left',
- sortable: true,
},
{
name: 'last_work_day',
diff --git a/src/modules/employee-list/models/schedule-presets.models.ts b/src/modules/employee-list/models/schedule-presets.models.ts
index 58696fe..20c6185 100644
--- a/src/modules/employee-list/models/schedule-presets.models.ts
+++ b/src/modules/employee-list/models/schedule-presets.models.ts
@@ -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;
@@ -48,6 +50,7 @@ export class SchedulePresetFrontend {
this.is_default = schedule_preset?.is_default ?? false;
this.weekdays = WEEKDAYS.map(day => ({
day,
+ is_error: false,
shifts: schedule_preset !== undefined ? schedule_preset?.shifts.filter(shift => shift.week_day === day) : [],
}))
}
@@ -55,5 +58,6 @@ export class SchedulePresetFrontend {
export interface WeekdayPresetShifts {
day: Weekday;
+ is_error: boolean;
shifts: SchedulePresetShift[];
}
\ No newline at end of file
diff --git a/src/modules/employee-list/services/employee-list-service.ts b/src/modules/employee-list/services/employee-list-service.ts
index 0c546e1..d2c0c7d 100644
--- a/src/modules/employee-list/services/employee-list-service.ts
+++ b/src/modules/employee-list/services/employee-list-service.ts
@@ -19,7 +19,7 @@ export const EmployeeListService = {
return response.data;
},
- createNewEmployee: async (profile: Omit): Promise> => {
+ createNewEmployee: async (profile: Omit): Promise> => {
const response = await api.post('employees/create', profile);
return response.data;
},
diff --git a/src/modules/employee-list/services/schedule-presets-service.ts b/src/modules/employee-list/services/schedule-presets-service.ts
index bc000d9..b288363 100644
--- a/src/modules/employee-list/services/schedule-presets-service.ts
+++ b/src/modules/employee-list/services/schedule-presets-service.ts
@@ -13,7 +13,7 @@ export const SchedulePresetsService = {
return response.data;
},
- deleteSchedulePresets: async (preset_id: number) => {
+ deleteSchedulePresets: async (preset_id: number): Promise> => {
const response = await api.delete(`/schedule-presets/delete/${preset_id}`);
return response.data;
},
diff --git a/src/modules/profile/components/shared/menu-panel-preferences.vue b/src/modules/profile/components/shared/menu-panel-preferences.vue
index e6c9614..7087a15 100644
--- a/src/modules/profile/components/shared/menu-panel-preferences.vue
+++ b/src/modules/profile/components/shared/menu-panel-preferences.vue
@@ -22,7 +22,8 @@
-
+
{
+export const isShiftOverlap = (shifts: Shift[] | SchedulePresetShift[]): boolean => {
if (shifts.length < 2) return false;
const parsed_shifts = shifts.map(shift => ({
- start: date.extractDate(`${shift.date} ${shift.start_time}`, 'YYYY-MM-DD HH:mm').getTime(),
- end: date.extractDate(`${shift.date} ${shift.end_time}`, 'YYYY-MM-DD HH:mm').getTime(),
+ start: date.extractDate(`2000-01-01 ${shift.start_time}`, 'YYYY-MM-DD HH:mm').getTime(),
+ end: date.extractDate(`2000-01-01 ${shift.end_time}`, 'YYYY-MM-DD HH:mm').getTime(),
}));
+
+ console.log('parsed_shifts: ', parsed_shifts);
for (let i = 0; i < parsed_shifts.length; i++) {
for (let j = i + 1; j < parsed_shifts.length; j++) {
@@ -15,6 +18,8 @@ export const isShiftOverlap = (shifts: Shift[]): boolean => {
const parsed_shift_b = parsed_shifts[j];
if (parsed_shift_a === undefined || parsed_shift_b === undefined) continue;
+ console.log('times(a start, b start, a end, b end): ', parsed_shift_a.start, parsed_shift_b.start, parsed_shift_a.end, parsed_shift_b.end);
+ console.log('result: ', Math.max(parsed_shift_a.start, parsed_shift_b.start) < Math.min(parsed_shift_a.end, parsed_shift_b.end))
if (Math.max(parsed_shift_a.start, parsed_shift_b.start) < Math.min(parsed_shift_a.end, parsed_shift_b.end)) {
return true; // overlap found
diff --git a/src/pages/dashboard-page.vue b/src/pages/dashboard-page.vue
index 0c502fe..6071010 100644
--- a/src/pages/dashboard-page.vue
+++ b/src/pages/dashboard-page.vue
@@ -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('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;
}
diff --git a/src/pages/profile-page.vue b/src/pages/profile-page.vue
index 8597b81..e010de1 100644
--- a/src/pages/profile-page.vue
+++ b/src/pages/profile-page.vue
@@ -24,7 +24,7 @@ import { onMounted } from 'vue';
\ No newline at end of file
diff --git a/src/stores/employee-store.ts b/src/stores/employee-store.ts
index 0fbf103..7a243f9 100644
--- a/src/stores/employee-store.ts
+++ b/src/stores/employee-store.ts
@@ -13,18 +13,19 @@ export const useEmployeeStore = defineStore('employee', () => {
const is_loading = ref(false);
const openAddModifyDialog = async (employee_email?: string) => {
- is_add_modify_dialog_open.value = true;
-
+
if (employee_email === undefined) {
management_mode.value = 'add_employee'
employee.value = new EmployeeProfile();
+ is_add_modify_dialog_open.value = true;
return;
}
-
+
is_loading.value = true;
management_mode.value = 'modify_employee';
await getEmployeeDetails(employee_email);
is_loading.value = false;
+ is_add_modify_dialog_open.value = true;
}
const closeAddModifyDialog = () => {
@@ -68,7 +69,7 @@ export const useEmployeeStore = defineStore('employee', () => {
let response;
if (management_mode.value === 'add_employee') {
- const { birth_date, external_payroll_id, last_work_day, ...create_payload} = profile;
+ const { birth_date, last_work_day, ...create_payload} = profile;
response = await EmployeeListService.createNewEmployee(create_payload);
} else {
diff --git a/src/stores/schedule-presets.store.ts b/src/stores/schedule-presets.store.ts
index 8dc72a4..d924f16 100644
--- a/src/stores/schedule-presets.store.ts
+++ b/src/stores/schedule-presets.store.ts
@@ -1,31 +1,37 @@
-/* eslint-disable */
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([new SchedulePreset]);
const current_schedule_preset = ref(new SchedulePresetFrontend);
+ const schedule_preset_dialog_mode = ref('create');
const is_manager_open = ref(false);
+ const openSchedulePresetManager = (preset_id: number) => {
+ if (preset_id === -1)
+ current_schedule_preset.value = new SchedulePresetFrontend;
+ else if (schedule_preset_dialog_mode.value === 'copy') {
+ const preset = schedule_presets.value.find(preset => preset.id === preset_id)!;
+ const copied_preset = new SchedulePresetFrontend(preset);
+ copied_preset.id = -1;
+ copied_preset.name = "";
+ current_schedule_preset.value = copied_preset;
+ }
+ else
+ setCurrentSchedulePreset(preset_id);
+
+ is_manager_open.value = true;
+ };
+
const setCurrentSchedulePreset = (preset_id: number) => {
if (preset_id === -1) {
current_schedule_preset.value = new SchedulePresetFrontend;
return;
}
- current_schedule_preset.value = new SchedulePresetFrontend(schedule_presets.value.find(preset => preset.id === preset_id)!)
- };
-
- const openSchedulePresetManager = (preset_id: number) => {
- if (preset_id === -1) {
- current_schedule_preset.value = new SchedulePresetFrontend;
- } else {
- setCurrentSchedulePreset(preset_id);
- }
-
- is_manager_open.value = true;
+ current_schedule_preset.value = new SchedulePresetFrontend(schedule_presets.value.find(preset => preset.id === preset_id))
};
const createSchedulePreset = async (preset: SchedulePreset): Promise => {
@@ -51,11 +57,11 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
const deleteSchedulePreset = async (preset_id: number): Promise => {
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;
}
};
@@ -72,18 +78,10 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
}
};
- const applySchedulePreset = async (): Promise => {
- try {
- return true;
- } catch (error) {
- console.error('DEV ERROR || error while building schedule: ', error);
- return false
- }
- };
-
return {
schedule_presets,
current_schedule_preset,
+ schedule_preset_dialog_mode,
is_manager_open,
setCurrentSchedulePreset,
openSchedulePresetManager,
@@ -91,6 +89,5 @@ export const useSchedulePresetsStore = defineStore('schedule_presets_store', ()
updateSchedulePreset,
deleteSchedulePreset,
findSchedulePresetList,
- applySchedulePreset,
}
})
\ No newline at end of file
diff --git a/src/utils/table-grid-FLIP.ts b/src/utils/table-grid-FLIP.ts
deleted file mode 100644
index a2a7f7f..0000000
--- a/src/utils/table-grid-FLIP.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { type Ref, nextTick } from 'vue';
-
-export const animateFlip = (container: Ref) => {
- const el = container.value;
- if (!el) return;
-
- const children = Array.from(el.children) as HTMLElement[];
-
- // FIRST: record initial positions
- const firstRects = children.map(c => c.getBoundingClientRect());
-
- // Do LAST → INVERT → PLAY after DOM update
- void nextTick(() => {
- const lastRects = children.map(c => c.getBoundingClientRect());
-
- children.forEach((child, i) => {
- const dx = firstRects[i]!.left - lastRects[i]!.left;
- const dy = firstRects[i]!.top - lastRects[i]!.top;
-
- if (!dx && !dy) return;
-
- child.style.transition = 'none';
- child.style.transform = `translate(${dx}px, ${dy}px)`;
-
- requestAnimationFrame(() => {
- child.style.transition = 'transform 250ms ease';
- child.style.transform = '';
- });
- });
- });
-}