salix-front/src/pages/Worker/Card/WorkerTimeControl.vue

643 lines
21 KiB
Vue

<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { onMounted, ref, computed, onBeforeMount, nextTick, reactive } from 'vue';
import FetchData from 'components/FetchData.vue';
import WorkerTimeHourChip from 'pages/Worker/Card/WorkerTimeHourChip.vue';
import WorkerTimeForm from 'pages/Worker/Card/WorkerTimeForm.vue';
import WorkerTimeReasonForm from 'pages/Worker/Card/WorkerTimeReasonForm.vue';
import WorkerDateLabel from './WorkerDateLabel.vue';
import WorkerTimeControlCalendar from 'pages/Worker/Card/WorkerTimeControlCalendar.vue';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import { useRole } from 'src/composables/useRole';
import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import { dashIfEmpty } from 'src/filters';
import { useVnConfirm } from 'composables/useVnConfirm';
import { useArrayData } from 'composables/useArrayData';
import { toTimeFormat, secondsToHoursMinutes } from 'filters/date.js';
import toDateString from 'filters/toDateString.js';
import { date } from 'quasar';
const route = useRoute();
const { t, locale } = useI18n();
const { notify } = useNotify();
const { hasAny } = useRole();
const _state = useState();
const user = _state.getUser();
const stateStore = useStateStore();
const weekdayStore = useWeekdayStore();
const weekDays = ref([]);
const { openConfirmationModal } = useVnConfirm();
const { getWeekOfYear } = date;
const workerTimeFormDialogRef = ref(null);
const workerTimeReasonFormDialogRef = ref(null);
const workerHoursRef = ref(null);
const selectedDate = ref(null);
const startOfWeek = ref(null);
const endOfWeek = ref(null);
const selectedWeekNumber = ref(null);
const state = ref(null);
const reason = ref(null);
const canResend = ref(null);
const weekTotalHours = ref(null);
const workerTimeControlMails = ref([]);
const workerTimeFormProps = reactive({
dated: null,
entryId: null,
entryCode: null,
});
// Array utilizado por QCalendar para seleccionar un rango de fechas
const selectedCalendarDates = ref([]);
// Date formateada para bindear al componente QDate
const selectedDateFormatted = ref(toDateString(Date.vnNew()));
const arrayData = useArrayData('workerData');
const worker = computed(() => arrayData.store?.data);
const isHr = computed(() => hasAny(['hr']));
const isHimSelf = computed(() => user.value.id === Number(route.params.id));
const columns = computed(() => {
return weekdayStore.getLocales?.map((day, index) => {
const obj = {
label: day.locale,
formattedDate: getHeaderFormattedDate(weekDays.value[index]?.dated),
name: day.name,
align: 'center',
colIndex: index,
dayData: weekDays.value[index],
};
return obj;
});
});
const getHeaderFormattedDate = (date) => {
const newDate = new Date(date);
const day = String(newDate.getDate()).padStart(2, '0');
const monthName = newDate.toLocaleString(locale.value, { month: 'long' });
return `${day} ${monthName}`;
};
const formattedWeekTotalHours = computed(() =>
secondsToHoursMinutes(weekTotalHours.value)
);
const onInputChange = async (date) => {
if (!date) return;
const { year, month, day } = date.scope.timestamp;
const _date = new Date(year, month - 1, day);
setDate(_date);
};
const setDate = async (date) => {
if (!date) return;
selectedDate.value = date;
selectedDate.value.setHours(0, 0, 0, 0);
const newStartOfWeek = getStartOfWeek(selectedDate.value); // Obtener el día de inicio de la semana a partir de la fecha seleccionada
const newEndOfWeek = getEndOfWeek(newStartOfWeek); // Obtener el fin de la semana a partir de la fecha de inicio de la semana
startOfWeek.value = newStartOfWeek;
endOfWeek.value = newEndOfWeek;
selectedWeekNumber.value = getWeekOfYear(newStartOfWeek); // Asignar el número de la semana del año
getWeekDates(newStartOfWeek, newEndOfWeek);
await nextTick(); // Esperar actualización del DOM y luego fetchear data necesaria
await fetchHours();
await fetchWeekData();
};
// Función para obtener el inicio de la semana a partir de una fecha
const getStartOfWeek = (selectedDate) => {
const dayOfWeek = selectedDate.getDay(); // Obtener el día de la semana de la fecha seleccionada
const startOfWeek = new Date(selectedDate);
startOfWeek.setDate(selectedDate.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1)); // Calcular el inicio de la semana
return startOfWeek;
};
// Función para obtener el fin de la semana a partir del inicio de la semana
const getEndOfWeek = (startOfWeek) => {
const endOfWeek = new Date(startOfWeek);
endOfWeek.setHours(23, 59, 59, 59);
endOfWeek.setDate(startOfWeek.getDate() + 6); // Calcular el fin de la semana sumando 6 días al inicio de la semana
return endOfWeek;
};
// Función para obtener las fechas de la semana seleccionada
const getWeekDates = (startOfWeek, endOfWeek) => {
selectedCalendarDates.value = [];
weekDays.value = []; // Limpiar la información de las fechas seleccionadas previamente
let currentDate = new Date(startOfWeek);
while (currentDate <= endOfWeek) {
// Iterar sobre los días de la semana
selectedCalendarDates.value.push(toDateString(currentDate)); // Agregar fecha formateada para el array de fechas bindeado al componente QCalendar
weekDays.value.push({ dated: new Date(currentDate.getTime()) }); // Agregar el día de la semana al array información de días de la semana
currentDate = new Date(currentDate.setDate(currentDate.getDate() + 1)); // Avanzar al siguiente día
}
};
const workerHoursFilter = computed(() => ({
where: {
and: [{ timed: { gte: startOfWeek.value } }, { timed: { lte: endOfWeek.value } }],
},
}));
const getWorkedHours = async (from, to) => {
weekTotalHours.value = null;
let _weekTotalHours = 0;
let params = {
from: from,
id: route.params.id,
to: to,
};
const { data } = await axios.get(`Workers/${route.params.id}/getWorkedHours`, {
params,
});
const workDays = data;
const map = new Map();
for (const workDay of workDays) {
workDay.dated = new Date(workDay.dated);
map.set(workDay.dated, workDay);
_weekTotalHours += workDay.workedHours;
}
for (const weekDay of weekDays.value) {
const workDay = workDays.find((day) => {
let from = new Date(day.dated);
from.setHours(0, 0, 0, 0);
let to = new Date(day.dated);
to.setHours(23, 59, 59, 59);
return weekDay.dated >= from && weekDay.dated <= to;
});
if (workDay) {
weekDay.expectedHours = workDay.expectedHours;
weekDay.workedHours = workDay.workedHours;
}
}
weekTotalHours.value = _weekTotalHours;
};
const getAbsences = async () => {
const params = {
workerFk: route.params.id,
businessFk: null,
year: startOfWeek.value.getFullYear(),
};
const { data } = await axios.get('Calendars/absences', { params });
if (data) addEvents(data);
};
const addEvents = (data) => {
const events = {};
const addEvent = (day, event) => {
events[new Date(day).getTime()] = event;
};
if (data.holidays) {
data.holidays.forEach((holiday) => {
const holidayDetail = holiday.detail && holiday.detail.description;
const holidayType = holiday.type && holiday.type.name;
const holidayName = holidayDetail || holidayType;
addEvent(holiday.dated, {
name: holidayName,
color: '#ff0',
});
});
}
if (data.absences) {
data.absences.forEach((absence) => {
const type = absence.absenceType;
addEvent(absence.dated, {
name: type.name,
color: type.rgb,
});
});
}
weekDays.value.forEach((day) => {
const timestamp = day.dated.getTime();
if (events[timestamp]) day.event = events[timestamp];
});
};
const fetchHours = async () => {
try {
await workerHoursRef.value.fetch();
await getWorkedHours(startOfWeek.value, endOfWeek.value);
await getAbsences();
} catch (err) {
console.error('Error fetching worker hours');
}
};
const fetchWorkerTimeControlMails = async (filter) => {
try {
const { data } = await axios.get('WorkerTimeControlMails', {
params: { filter: JSON.stringify(filter) },
});
return data;
} catch (err) {
console.error('Error fetching worker time control mails');
}
};
const fetchWeekData = async () => {
try {
const filter = {
where: {
workerFk: route.params.id,
year: selectedDate.value ? selectedDate.value?.getFullYear() : null,
week: selectedWeekNumber.value,
},
};
const data = await fetchWorkerTimeControlMails(filter);
if (!data.length) {
state.value = null;
} else {
const [mail] = data;
state.value = mail.state;
reason.value = mail.reason;
}
await canBeResend();
} catch (err) {
console.error('Error fetching week data');
}
};
const canBeResend = async () => {
canResend.value = false;
const filter = {
where: {
year: selectedDate.value.getFullYear(),
week: selectedWeekNumber.value,
},
limit: 1,
};
const data = await fetchWorkerTimeControlMails(filter);
if (data.length) canResend.value = true;
};
const setHours = (data) => {
for (const weekDay of weekDays.value) {
if (data) {
let day = weekDay.dated.getDay();
weekDay.hours = data
.filter((hour) => new Date(hour.timed).getDay() == day)
.sort((a, b) => new Date(a.timed) - new Date(b.timed));
} else weekDay.hours = null;
}
};
const getFinishTime = () => {
if (!weekDays.value || weekDays.value.length === 0) return;
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let todayInWeek = weekDays.value.find(
(day) => day.dated.getTime() === today.getTime()
);
if (todayInWeek && todayInWeek.hours && todayInWeek.hours.length) {
const remainingTime = todayInWeek.workedHours
? (todayInWeek.expectedHours - todayInWeek.workedHours) * 1000
: null;
const lastKnownEntry = todayInWeek.hours[todayInWeek.hours.length - 1];
const lastKnownTime = new Date(lastKnownEntry.timed).getTime();
const finishTimeStamp =
lastKnownTime && remainingTime ? lastKnownTime + remainingTime : null;
if (finishTimeStamp) return toTimeFormat(finishTimeStamp) + ' h.';
}
};
const updateData = async () => {
await fetchHours();
await getMailStates(selectedDate.value);
};
const getMailStates = async (date) => {
const params = {
month: date.getMonth() + 1,
year: date.getFullYear(),
};
const { data } = await axios.get(
`WorkerTimeControls/${route.params.id}/getMailStates`,
{ params }
);
workerTimeControlMails.value = data;
};
const showWorkerTimeForm = (propValue, formType) => {
const isEditForm = formType === 'edit';
workerTimeFormProps.entryId = isEditForm ? propValue.id : null;
workerTimeFormProps.entryCode = isEditForm ? propValue.entryCode : null;
workerTimeFormProps.dated = isEditForm ? null : propValue;
workerTimeFormDialogRef.value.show();
};
const showReasonForm = () => {
workerTimeReasonFormDialogRef.value.show();
};
const updateWorkerTimeControlMail = async (state, reason) => {
try {
const params = {
workerId: Number(route.params.id),
year: selectedDate.value.getFullYear(),
week: selectedWeekNumber.value,
state,
};
if (reason) params.reason = reason;
await axios.post('WorkerTimeControls/updateWorkerTimeControlMail', params);
await getMailStates(selectedDate.value);
await fetchWeekData();
notify(t('globals.dataSaved'), 'positive');
} catch (err) {
console.error('Error updating worker time control mail');
}
};
const isSatisfied = async () => {
await updateWorkerTimeControlMail('CONFIRMED');
};
const isUnsatisfied = async (reason) => {
if (!reason) {
notify(t('You must indicate a reason', 'negative'));
return;
}
updateWorkerTimeControlMail('REVISE', reason);
};
const resendEmail = async () => {
try {
const params = {
recipient: worker.value?.user?.email,
week: selectedWeekNumber.value,
year: selectedDate.value.getFullYear(),
workerId: Number(route.params.id),
state: 'SENDED',
};
await axios.post('WorkerTimeControls/weekly-hour-hecord-email', params);
await getMailStates(selectedDate.value);
notify(t('Email sended'), 'positive');
} catch (err) {
console.error('Error sending email');
}
};
onBeforeMount(() => {
weekdayStore.initStore();
});
onMounted(async () => {
await setDate(Date.vnNew());
await getMailStates(selectedDate.value);
stateStore.rightDrawer = true;
});
</script>
<template>
<FetchData
ref="workerHoursRef"
url="WorkerTimeControls/filter"
:filter="workerHoursFilter"
:params="{
workerFk: route.params.id,
}"
@on-fetch="(data) => setHours(data)"
/>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<div>
<QBtnGroup push class="q-gutter-x-sm" flat>
<QBtn
v-if="isHimSelf && state"
:label="t('Satisfied')"
color="primary"
type="submit"
:disabled="state == 'CONFIRMED'"
@click="isSatisfied()"
/>
<QBtn
v-if="isHimSelf && state"
:label="t('Not satisfied')"
color="primary"
type="submit"
:disabled="state == 'REVISE'"
@click="showReasonForm()"
style="margin-left: 1px"
/>
</QBtnGroup>
<QBtnGroup push class="q-gutter-x-sm" flat style="margin-left: 0px">
<QBtn
v-if="reason && state && (isHimSelf || isHr)"
:label="t('Reason')"
color="primary"
type="submit"
@click="showReasonForm()"
/>
<QBtn
v-if="isHr && state !== 'CONFIRMED' && canResend"
:label="state ? t('Resend') : t('globals.send')"
color="primary"
type="submit"
@click="
openConfirmationModal(
t('Send time control email'),
t('Are you sure you want to send it?'),
resendEmail
)
"
>
<QTooltip>
{{ state ? t('Resend') : t('globals.send') }}
{{ t('email of this week to the user') }}
</QTooltip>
</QBtn>
</QBtnGroup>
</div>
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="260" class="q-pa-md">
<div class="q-pa-md q-mb-md" style="border: 2px solid #222">
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('Hours') }}
</span>
</QCardSection>
<QCardSection class="column items-center" horizontal>
<div>
<span class="details-label">{{ t('Total semana') }} </span>
<span>: {{ formattedWeekTotalHours }}</span>
</div>
<div>
<span class="details-label">{{ t('Termina a las') }}: </span>
<span>{{ dashIfEmpty(getFinishTime()) }}</span>
</div>
</QCardSection>
</div>
<WorkerTimeControlCalendar
v-model:model-value="selectedDateFormatted"
:selected-dates="selectedCalendarDates"
:active-date="false"
:worker-time-control-mails="workerTimeControlMails"
@click-date="onInputChange"
@on-moved="getMailStates"
/>
</QDrawer>
<QPage class="column items-center">
<QTable :columns="columns" :rows="['']" hide-bottom class="full-width">
<template #header="props">
<QTr :props="props" no-hover>
<QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
auto-width
style="vertical-align: top"
>
<div class="column-title-container">
<span class="text-primary">{{ t(col.label) }}</span>
<span class="q-mb-xs">{{ col.formattedDate }}</span>
<WorkerDateLabel
v-if="col.dayData?.event"
:color="col.dayData.event.color"
>
<span>
{{ col.dayData.event.name }}
</span>
</WorkerDateLabel>
</div>
</QTh>
</QTr>
</template>
<template #body="props">
<QTr no-hover>
<QTd
v-for="(day, index) in props.cols"
:key="index"
style="padding: 20px 16px !important"
>
<div class="full-height full-width column items-center">
<WorkerTimeHourChip
v-for="(hour, ind) in day.dayData?.hours"
:key="ind"
:hour="hour.timed"
:manual="hour.manual"
:direction="hour.direction"
:id="hour.id"
@on-hour-entry-deleted="updateData()"
@show-worker-time-form="
showWorkerTimeForm(
{ id: hour.id, entryCode: hour.direction },
'edit'
)
"
class="hour-chip"
/>
</div>
</QTd>
</QTr>
<QTr no-hover>
<QTd v-for="(day, index) in props.cols" :key="index">
<div class="column items-center justify-center">
<span class="q-mb-md text-sm text-body1">
{{ secondsToHoursMinutes(day.dayData?.workedHours) }}
</span>
<QIcon
name="add_circle"
color="primary"
class="fill-icon cursor-pointer"
size="sm"
@click="showWorkerTimeForm(day.dayData?.dated, 'create')"
>
<QTooltip>{{ t('Add time') }}</QTooltip>
</QIcon>
</div>
</QTd>
</QTr>
</template>
</QTable>
<QDialog ref="workerTimeFormDialogRef">
<WorkerTimeForm v-bind="workerTimeFormProps" @on-data-saved="updateData()" />
</QDialog>
<QDialog ref="workerTimeReasonFormDialogRef">
<WorkerTimeReasonForm
@on-submit="isUnsatisfied($event)"
:reason="reason"
:is-him-self="isHimSelf"
/>
</QDialog>
</QPage>
</template>
<style scoped lang="scss">
.column-title-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 14px;
}
.details-label {
color: var(--vn-label);
}
.hour-chip {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0px;
}
}
</style>
<i18n>
es:
Hours: Horas
Total semana: Total semana
Termina a las: Termina a las
Add time: Añadir hora
Reason: Motivo
Not satisfied: No conforme
Satisfied: Conforme
Resend: Reenviar
email of this week to the user: email de esta semana al usuario
Email sended: Email enviado
Send time control email: Enviar email control horario
Are you sure you want to send it?: ¿Seguro que quieres enviarlo?
You must indicate a reason: Debes indicar un motivo
</i18n>