diff --git a/quasar.extensions.json b/quasar.extensions.json index e5c5cbfaa..867769090 100644 --- a/quasar.extensions.json +++ b/quasar.extensions.json @@ -1,7 +1,6 @@ { - "@quasar/testing-unit-vitest": { - "options": [ - "scripts" - ] - } -} \ No newline at end of file + "@quasar/testing-unit-vitest": { + "options": ["scripts"] + }, + "@quasar/qcalendar": {} +} diff --git a/src/components/ui/QCalendarMonthWrapper.vue b/src/components/ui/QCalendarMonthWrapper.vue new file mode 100644 index 000000000..49acd0e7d --- /dev/null +++ b/src/components/ui/QCalendarMonthWrapper.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/css/app.scss b/src/css/app.scss index 8e2853ab2..0e9ae66e2 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -1,5 +1,6 @@ // app global css in SCSS form @import './icons.scss'; +@import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass'; body.body--light { --fount-color: black; @@ -120,3 +121,14 @@ input::-webkit-inner-spin-button { -webkit-appearance: none; -moz-appearance: none; } + +// Clases para modificar el color de fecha seleccionada en componente QCalendarMonth +.q-dark div .q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button { + background-color: $primary !important; + color: white !important; +} + +.q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button { + background-color: $primary !important; + color: white !important; +} diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index 34ce16aa7..5ca92df0a 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -1225,4 +1225,27 @@ export default { }, iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789', }, + weekdays: { + sun: 'Sunday', + mon: 'Monday', + tue: 'Tuesday', + wed: 'Wednesday', + thu: 'Thursday', + fri: 'Friday', + sat: 'Saturday', + }, + months: { + jan: 'January', + feb: 'February', + mar: 'March', + apr: 'April', + may: 'May', + jun: 'June', + jul: 'July', + aug: 'August', + sep: 'September', + oct: 'October', + nov: 'November', + dec: 'December', + }, }; diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index fee08322c..0b63f3de5 100644 --- a/src/i18n/es/index.js +++ b/src/i18n/es/index.js @@ -1225,4 +1225,27 @@ export default { }, iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789', }, + weekdays: { + sun: 'Domingo', + mon: 'Lunes', + tue: 'Martes', + wed: 'Miércoles', + thu: 'Jueves', + fri: 'Viernes', + sat: 'Sábado', + }, + months: { + jan: 'Enero', + feb: 'Febrero', + mar: 'Marzo', + apr: 'Abril', + may: 'Mayo', + jun: 'Junio', + jul: 'Julio', + aug: 'Agosto', + sep: 'Septiembre', + oct: 'Octubre', + nov: 'Noviembre', + dec: 'Diciembre', + }, }; diff --git a/src/pages/Worker/Card/WorkerCalendar.vue b/src/pages/Worker/Card/WorkerCalendar.vue index 73111cafa..859dedb57 100644 --- a/src/pages/Worker/Card/WorkerCalendar.vue +++ b/src/pages/Worker/Card/WorkerCalendar.vue @@ -1,26 +1,35 @@ + es: Search worker: Buscar trabajador You can search by worker id or name: Puedes buscar por id o nombre del trabajador + To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia diff --git a/src/pages/Worker/Card/WorkerCalendarFilter.vue b/src/pages/Worker/Card/WorkerCalendarFilter.vue index d73305085..f8c1b2167 100644 --- a/src/pages/Worker/Card/WorkerCalendarFilter.vue +++ b/src/pages/Worker/Card/WorkerCalendarFilter.vue @@ -4,7 +4,7 @@ import FetchData from 'components/FetchData.vue'; import { useI18n } from 'vue-i18n'; import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import { useRoute } from 'vue-router'; -import { computed, ref, watch } from 'vue'; +import { computed, ref, watch, reactive } from 'vue'; import { toDateFormat } from '../../../filters/date'; import axios from 'axios'; @@ -26,7 +26,12 @@ const props = defineProps({ }, }); -const emit = defineEmits(['update:businessFk', 'update:year', 'update:absenceType']); +const emit = defineEmits([ + 'update:businessFk', + 'update:year', + 'update:absenceType', + 'updateEvents', +]); const selectedBusinessFk = computed({ get: () => props.businessFk, @@ -35,13 +40,18 @@ const selectedBusinessFk = computed({ emit('update:businessFk', value); }, }); + const selectedYear = computed({ get: () => props.year, set: (value) => emit('update:year', value), }); + const selectedAbsenceType = computed({ get: () => props.absenceType, - set: (value) => emit('update:absenceType', value), + set: (value) => { + if (value === props.absenceType) value = null; + emit('update:absenceType', value); + }, }); const generateYears = () => { @@ -57,11 +67,19 @@ const yearList = ref(generateYears()); const contractHolidays = ref(null); const yearHolidays = ref(null); +const events = reactive({}); +const calendar = ref(null); const getHolidays = async (params) => { - return axios - .get(`Workers/${route.params.id}/holidays`, { params }) - .then((res) => res.data); + try { + const { data } = await axios.get(`Workers/${route.params.id}/holidays`, { + params, + }); + return data; + } catch (error) { + console.error('Error fetching holidays:', error); + return null; + } }; const updateContractHolidays = async () => { @@ -75,14 +93,80 @@ const updateYearHolidays = async () => { yearHolidays.value = await getHolidays({ year: selectedYear.value }); }; -watch(selectedYear, () => { +const getAbsences = async () => { + try { + const params = { + workerFk: route.params.id, + businessFk: props.businessFk, + year: props.year, + }; + const { data } = await axios.get('Calendars/absences', { params }); + if (data) onAbsencesFetched(data); + return data; + } catch (error) { + console.error('Error fetching absences:', error); + return null; + } +}; + +const refreshData = () => { updateYearHolidays(); updateContractHolidays(); + getAbsences(); +}; + +const onAbsencesFetched = (data) => { + calendar.value = data.calendar; + + let addEvent = (day, newEvent) => { + const timestamp = new Date(day).getTime(); + let event = events[timestamp]; + + if (event) { + const oldName = event.name; + Object.assign(event, newEvent); + event.name = `${oldName}, ${event.name}`; + } else events[timestamp] = newEvent; + }; + + if (data.holidays) { + data.holidays.forEach((holiday) => { + const holidayDetail = holiday.detail && holiday.detail.name; + const holidayType = holiday.type && holiday.type.name; + const holidayName = holidayDetail || holidayType; + + addEvent(holiday.dated, { + name: holidayName, + className: 'festive', + }); + }); + } + if (data.absences) { + data.absences.forEach((absence) => { + let type = absence.absenceType; + addEvent(absence.dated, { + name: type.name, + color: type.rgb, + type: type.code, + absenceId: absence.id, + }); + }); + } + + emit('updateEvents', events); + console.log('events:: ', events); +}; + +watch(selectedYear, () => { + refreshData(); }); watch(selectedBusinessFk, () => { - updateYearHolidays(); - updateContractHolidays(); + refreshData(); +}); + +defineExpose({ + refreshData, }); diff --git a/src/pages/Worker/Card/WorkerCalendarItem.vue b/src/pages/Worker/Card/WorkerCalendarItem.vue index b06d8f028..79c47063c 100644 --- a/src/pages/Worker/Card/WorkerCalendarItem.vue +++ b/src/pages/Worker/Card/WorkerCalendarItem.vue @@ -1,11 +1,17 @@ + + + + + +es: + Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha + diff --git a/src/stores/useWeekdayStore.js b/src/stores/useWeekdayStore.js new file mode 100644 index 000000000..ad898c9a7 --- /dev/null +++ b/src/stores/useWeekdayStore.js @@ -0,0 +1,95 @@ +import { reactive, ref, computed } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { defineStore } from 'pinia'; + +export const useWeekdayStore = defineStore('weekdayStore', () => { + const { t } = useI18n(); + + const weekdays = [ + { code: 'sun', name: 'Sunday' }, + { code: 'mon', name: 'Monday' }, + { code: 'tue', name: 'Tuesday' }, + { code: 'wed', name: 'Wednesday' }, + { code: 'thu', name: 'Thursday' }, + { code: 'fri', name: 'Friday' }, + { code: 'sat', name: 'Saturday' }, + ]; + + const monthCodes = [ + 'jan', + 'feb', + 'mar', + 'apr', + 'may', + 'jun', + 'jul', + 'aug', + 'sep', + 'oct', + 'nov', + 'dec', + ]; + + const localeOrder = { + es: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'], + en: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'], + }; + + const weekdaysMap = reactive({}); + const localeWeekdays = ref([]); + + const initStore = () => { + getWeekdaysMap(); + }; + + const getWeekdaysMap = () => { + if (Object.keys(weekdaysMap).length > 0) return weekdaysMap; + + weekdays.forEach((day, i) => { + const obj = { + ...day, + index: i, + char: day.name.substr(0, 1), + abr: day.name.substr(0, 3), + }; + weekdaysMap[day.code] = obj; + }); + }; + + const getLocales = computed(() => { + // El día de mañana esto permitirá ordenar los weekdays en base a el locale si se lo desea reemplazando localeOrder.es por localeOrder[locale] + const locales = []; + for (let code of localeOrder.es) { + const obj = { + ...weekdaysMap[code], + locale: t(`weekdays.${weekdaysMap[code].code}`), + localeChar: t(`weekdays.${weekdaysMap[code].code}`).substr(0, 1), + localeAbr: t(`weekdays.${weekdaysMap[code].code}`).substr(0, 3), + }; + locales.push(obj); + } + return locales; + }); + + const getLocaleMonths = computed(() => { + const locales = []; + for (let code of monthCodes) { + const obj = { + code: code, + locale: t(`months.${code}`), + }; + locales.push(obj); + } + return locales; + }); + + return { + initStore, + weekdaysMap, + localeWeekdays, + getLocales, + weekdays, + monthCodes, + getLocaleMonths, + }; +});