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": {
"options": [
"scripts"
]
}
}
"@quasar/testing-unit-vitest": {
"options": ["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
@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;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,17 @@
<script setup>
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 { 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({
year: {
@ -16,41 +22,181 @@ const props = defineProps({
type: Number,
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 calendarRef = ref(null);
const selectedDate = ref(today());
const weekdayStore = useWeekdayStore();
const selectedDate = ref(null);
const calendarEventDates = [];
const updateSelectedDate = (year) => {
const dateObject = Date.vnNew();
dateObject.setFullYear(year);
dateObject.setMonth(props.month - 1);
selectedDate.value = date.formatDate(dateObject, 'YYYY-MM-DD');
console.log(date.formatDate(dateObject, 'YYYY-MM-DD'));
const _date = new Date(year, props.month - 1, 1);
selectedDate.value = date.formatDate(_date, '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(() => {
updateSelectedDate(props.year);
});
watch(
() => props.year,
(newValue) => {
updateSelectedDate(newValue);
}
);
watch(props.year, (newValue) => {
updateSelectedDate(newValue);
});
</script>
<template>
<QCalendarMonth
ref="calendarRef"
v-model="selectedDate"
show-work-weeks
no-outside-days
:selected-dates="[]"
:disabled-weekdays="[0, 6]"
:locale="locale"
animated
mini-mode
/>
<QCalendarMonthWrapper style="height: 200px">
<template #header>
<span class="full-width text-center text-body1 q-py-sm">{{
weekdayStore.getLocaleMonths[$props.month - 1].locale
}}</span>
</template>
<template #calendar>
<QCalendarMonth
ref="calendarRef"
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>
<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,
};
});