0
0
Fork 0
This commit is contained in:
William Buezas 2024-04-03 08:08:54 -03:00
parent fc87bb8293
commit bfee37b631
9 changed files with 496 additions and 59 deletions

View File

@ -1,7 +1,6 @@
{ {
"@quasar/testing-unit-vitest": { "@quasar/testing-unit-vitest": {
"options": [ "options": ["scripts"]
"scripts" },
] "@quasar/qcalendar": {}
} }
}

View File

@ -0,0 +1,23 @@
<template>
<div :class="['main-container-background', $q.dark.isActive ? '--dark' : '--light']">
<div class="nav-container row"><slot name="header" /></div>
<slot name="calendar" />
</div>
</template>
<style lang="scss">
.main-container-background {
&.--dark {
background-color: var(--calendar-background-dark);
}
&.--light {
background-color: var(--calendar-background);
}
}
.nav-container {
display: flex;
align-items: center;
}
</style>

View File

@ -1,5 +1,6 @@
// app global css in SCSS form // app global css in SCSS form
@import './icons.scss'; @import './icons.scss';
@import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass';
body.body--light { body.body--light {
--fount-color: black; --fount-color: black;
@ -120,3 +121,14 @@ input::-webkit-inner-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
-moz-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;
}

View File

@ -1225,4 +1225,27 @@ export default {
}, },
iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789', 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',
},
}; };

View File

@ -1225,4 +1225,27 @@ export default {
}, },
iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789', 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',
},
}; };

View File

@ -1,26 +1,35 @@
<script setup> <script setup>
import { ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useRoute } from 'vue-router';
import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue'; import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import { useRoute } from 'vue-router';
import { ref } from 'vue';
import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue'; import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue';
import { useStateStore } from 'stores/useStateStore';
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const workerCalendarFilterRef = ref(null);
const workerCalendarRef = ref(null);
const businessFk = ref(null); const businessFk = ref(null);
const absenceType = ref(null); const absenceType = ref(null);
const hasWorkCenter = ref(false); const hasWorkCenter = ref(false);
const isSubordinate = ref(false); const isSubordinate = ref(false);
const year = ref(Date.vnNew().getFullYear()); const year = ref(Date.vnNew().getFullYear());
const events = ref({});
const onFetchActiveContract = (data) => { const onFetchActiveContract = (data) => {
businessFk.value = data?.businessFk; businessFk.value = data?.businessFk;
hasWorkCenter.value = Boolean(data?.workCenterFk); hasWorkCenter.value = Boolean(data?.workCenterFk);
}; };
const onEventsUpdated = (ev) => (events.value = ev);
const onHandleRefresh = () => workerCalendarFilterRef.value.refreshData();
</script> </script>
<template> <template>
@ -35,7 +44,7 @@ const onFetchActiveContract = (data) => {
auto-load auto-load
/> />
<template v-if="stateStore.isHeaderMounted()"> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append"> <!-- <Teleport to="#actions-append">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
<QBtn <QBtn
flat flat
@ -49,14 +58,16 @@ const onFetchActiveContract = (data) => {
</QTooltip> </QTooltip>
</QBtn> </QBtn>
</div> </div>
</Teleport> </Teleport> -->
</template> </template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<WorkerCalendarFilter <WorkerCalendarFilter
ref="workerCalendarFilterRef"
v-model:business-fk="businessFk" v-model:business-fk="businessFk"
v-model:year="year" v-model:year="year"
v-model:absence-type="absenceType" v-model:absence-type="absenceType"
@update-events="onEventsUpdated"
/> />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
@ -66,15 +77,39 @@ const onFetchActiveContract = (data) => {
{{ t('Autonomous worker') }} {{ t('Autonomous worker') }}
</QCardSection> </QCardSection>
</QCard> </QCard>
<QCard class="full-width"> <QCard v-else class="full-width q-pa-xl">
<QCardSection class="calendar-container"> <QIcon
<div v-for="month in 12" :key="month" class="full-width full-height"> v-if="isSubordinate"
<WorkerCalendarItem class="full-width" :year="year" :month="month" /> name="info"
</div> size="sm"
</QCardSection> class="absolute"
style="top: 16px; right: 16px"
>
<QTooltip max-width="250px">
{{
t(
'To start adding absences, click an absence type from the right menu and then on the day you want to add an absence'
)
}}
</QTooltip>
</QIcon>
<div class="calendar-container">
<WorkerCalendarItem
ref="workerCalendarRef"
v-for="month in 12"
:key="month"
:year="year"
:month="month"
:absence-type="absenceType"
:business-fk="businessFk"
:events="events"
@refresh="onHandleRefresh"
/>
</div>
</QCard> </QCard>
</QPage> </QPage>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.calendar-container { .calendar-container {
display: grid; display: grid;
@ -88,19 +123,16 @@ const onFetchActiveContract = (data) => {
} }
} }
@media (max-width: $breakpoint-sm) { @media (max-width: $breakpoint-xs) {
.calendar-container { .calendar-container {
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(1, minmax(0, 1fr));
} }
} }
.calendar-item {
max-width: 324px;
}
</style> </style>
<i18n> <i18n>
es: es:
Search worker: Buscar trabajador Search worker: Buscar trabajador
You can search by worker id or name: Puedes buscar por id o nombre del 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
</i18n> </i18n>

View File

@ -4,7 +4,7 @@ import FetchData from 'components/FetchData.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch, reactive } from 'vue';
import { toDateFormat } from '../../../filters/date'; import { toDateFormat } from '../../../filters/date';
import axios from 'axios'; 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({ const selectedBusinessFk = computed({
get: () => props.businessFk, get: () => props.businessFk,
@ -35,13 +40,18 @@ const selectedBusinessFk = computed({
emit('update:businessFk', value); emit('update:businessFk', value);
}, },
}); });
const selectedYear = computed({ const selectedYear = computed({
get: () => props.year, get: () => props.year,
set: (value) => emit('update:year', value), set: (value) => emit('update:year', value),
}); });
const selectedAbsenceType = computed({ const selectedAbsenceType = computed({
get: () => props.absenceType, get: () => props.absenceType,
set: (value) => emit('update:absenceType', value), set: (value) => {
if (value === props.absenceType) value = null;
emit('update:absenceType', value);
},
}); });
const generateYears = () => { const generateYears = () => {
@ -57,11 +67,19 @@ const yearList = ref(generateYears());
const contractHolidays = ref(null); const contractHolidays = ref(null);
const yearHolidays = ref(null); const yearHolidays = ref(null);
const events = reactive({});
const calendar = ref(null);
const getHolidays = async (params) => { const getHolidays = async (params) => {
return axios try {
.get(`Workers/${route.params.id}/holidays`, { params }) const { data } = await axios.get(`Workers/${route.params.id}/holidays`, {
.then((res) => res.data); params,
});
return data;
} catch (error) {
console.error('Error fetching holidays:', error);
return null;
}
}; };
const updateContractHolidays = async () => { const updateContractHolidays = async () => {
@ -75,14 +93,80 @@ const updateYearHolidays = async () => {
yearHolidays.value = await getHolidays({ year: selectedYear.value }); 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(); updateYearHolidays();
updateContractHolidays(); 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, () => { watch(selectedBusinessFk, () => {
updateYearHolidays(); refreshData();
updateContractHolidays(); });
defineExpose({
refreshData,
}); });
</script> </script>

View File

@ -1,11 +1,17 @@
<script setup> <script setup>
import { onBeforeMount, ref, watch } from 'vue'; import { onBeforeMount, ref, watch } from 'vue';
import { QCalendarMonth, today } from '@quasar/quasar-ui-qcalendar/src/index.js';
import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.sass';
import '@quasar/quasar-ui-qcalendar/src/QCalendarTransitions.sass';
import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass';
import { date } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { date } from 'quasar';
import { useRoute } from 'vue-router';
import QCalendarMonthWrapper from 'src/components/ui/QCalendarMonthWrapper.vue';
import { QCalendarMonth } from '@quasar/quasar-ui-qcalendar/src/index.js';
import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.sass';
import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass';
import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
const props = defineProps({ const props = defineProps({
year: { year: {
@ -16,41 +22,181 @@ const props = defineProps({
type: Number, type: Number,
required: true, required: true,
}, },
absenceType: {
type: Number,
default: null,
},
businessFk: {
type: Number,
default: null,
},
events: {
type: Object,
default: null,
},
}); });
const emit = defineEmits(['refresh']);
const route = useRoute();
const { t } = useI18n();
const { notify } = useNotify();
const { locale } = useI18n(); const { locale } = useI18n();
const calendarRef = ref(null); const calendarRef = ref(null);
const selectedDate = ref(today()); const weekdayStore = useWeekdayStore();
const selectedDate = ref(null);
const calendarEventDates = [];
const updateSelectedDate = (year) => { const updateSelectedDate = (year) => {
const dateObject = Date.vnNew(); const _date = new Date(year, props.month - 1, 1);
dateObject.setFullYear(year); selectedDate.value = date.formatDate(_date, 'YYYY-MM-DD');
dateObject.setMonth(props.month - 1); };
selectedDate.value = date.formatDate(dateObject, 'YYYY-MM-DD');
console.log(date.formatDate(dateObject, 'YYYY-MM-DD')); const createEvent = async (date) => {
try {
const params = {
dated: date,
absenceTypeId: props.absenceType,
businessFk: props.businessFk,
};
const { data } = await axios.post(
`Workers/${route.params.id}/createAbsence`,
params
);
console.log('data:: ', data);
// TODO: Agregar notify success
emit('refresh');
} catch (error) {
console.log('error creating event:: ', error);
}
};
const editEvent = async (event) => {
console.log('editEvent');
};
const deleteEvent = async (date, event) => {
console.log('deleteEvent');
};
const handleDateSelected = (date) => {
if (!props.absenceType) {
notify(t('Choose an absence type from the right menu'), 'warning');
return;
}
const { year, month, day } = date.scope.timestamp;
const _date = new Date(year, month - 1, day);
const stamp = _date.getTime();
const event = props.events[stamp];
if (event && event.absenceId) {
if (event.type == props.absenceType.code) deleteEvent(_date, event);
else editEvent(event);
} else createEvent(_date);
};
const getEventByTimestamp = ({ year, month, day }) => {
const stamp = date.formatDate(new Date(year, month - 1, day), 'x');
return props.events[stamp] || null;
};
const getEventAttrs = (timestamp) => {
const event = getEventByTimestamp(timestamp);
if (!event) return '';
const { name, color } = event;
return {
title: name,
style: `background-color: ${color};`,
};
}; };
onBeforeMount(() => { onBeforeMount(() => {
updateSelectedDate(props.year); updateSelectedDate(props.year);
}); });
watch( watch(props.year, (newValue) => {
() => props.year, updateSelectedDate(newValue);
(newValue) => { });
updateSelectedDate(newValue);
}
);
</script> </script>
<template> <template>
<QCalendarMonth <QCalendarMonthWrapper style="height: 200px">
ref="calendarRef" <template #header>
v-model="selectedDate" <span class="full-width text-center text-body1 q-py-sm">{{
show-work-weeks weekdayStore.getLocaleMonths[$props.month - 1].locale
no-outside-days }}</span>
:selected-dates="[]" </template>
:disabled-weekdays="[0, 6]" <template #calendar>
:locale="locale" <QCalendarMonth
animated ref="calendarRef"
mini-mode v-model="selectedDate"
/> @click-date="handleDateSelected"
show-work-weeks
no-outside-days
:selected-dates="calendarEventDates"
no-active-date
:weekdays="[1, 2, 3, 4, 5, 6, 0]"
:disabled-weekdays="[0, 6]"
:locale="locale"
mini-mode
>
<template #day="{ scope: { timestamp } }">
<QButton
v-if="getEventByTimestamp(timestamp)"
v-bind="{ ...getEventAttrs(timestamp) }"
class="calendar-event"
>
{{ timestamp.day }}
</QButton>
</template>
</QCalendarMonth>
</template>
</QCalendarMonthWrapper>
</template> </template>
<style lang="scss">
.q-calendar__button {
&:hover {
background-color: var(--vn-accent-color);
cursor: pointer;
}
}
.q-calendar-month__day--content {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
}
.q-outside .calendar-event {
display: none;
}
.calendar-event {
display: flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
font-size: 0.75em;
line-height: 1.715em;
border-radius: 50%;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
</style>
<i18n>
es:
Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha
</i18n>

View File

@ -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,
};
});