|
@@ -0,0 +1,615 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
|
|
|
|
|
+ <div class="bg-page full-height column no-shadow">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
|
|
|
|
|
+ <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
|
|
|
|
|
+ <q-space />
|
|
|
|
|
+ <span class="text-subtitle1 text-weight-bold text-primary">{{ $t('profile.availability.title') }}</span>
|
|
|
|
|
+ <q-space />
|
|
|
|
|
+ <div style="width: 32px"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="col overflow-auto">
|
|
|
|
|
+ <div class="q-px-md q-pt-lg">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="text-h6 text-weight-bold gradient-diarista q-mb-xs">
|
|
|
|
|
+ {{ $t('profile.availability.working_days_title') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-caption text-text q-mb-md">
|
|
|
|
|
+ {{ $t('profile.availability.working_days_subtitle') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ <div class="bg-surface q-pa-md q-mb-sm shadow-card" style="border-radius: 12px;">
|
|
|
|
|
+ <div flat class="q-mb-lg availability-notice-card">
|
|
|
|
|
+ <q-card-section class="q-pa-sm row items-start no-wrap q-gutter-x-sm">
|
|
|
|
|
+ <q-icon name="mdi-lock-outline" color="grey-5" size="20px" class="q-mt-xs" />
|
|
|
|
|
+ <div class="text-caption text-text">{{ $t('profile.availability.working_days_hint') }}</div>
|
|
|
|
|
+ </q-card-section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="working-days-grid">
|
|
|
|
|
+ <div v-for="day in daysOfWeek" :key="day" class="day-column">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="day-label text-center text-weight-bold cursor-pointer q-mb-xs gradient-diarista-bg"
|
|
|
|
|
+ @click="toggleDay(day)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ $t(`profile.availability.days_short.${day}`) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="period-chip text-center cursor-pointer q-mb-xs"
|
|
|
|
|
+ :class="isSelected(day, 'morning') ? 'chip-active' : 'chip-inactive'"
|
|
|
|
|
+ @click.stop="togglePeriod(day, 'morning')"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-spinner v-if="loadingToggle[`${day}_morning`]" size="10px" color="white" />
|
|
|
|
|
+ <span v-else>{{ $t('profile.availability.morning') }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="period-chip text-center cursor-pointer"
|
|
|
|
|
+ :class="isSelected(day, 'afternoon') ? 'chip-active' : 'chip-inactive'"
|
|
|
|
|
+ @click.stop="togglePeriod(day, 'afternoon')"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-spinner v-if="loadingToggle[`${day}_afternoon`]" size="10px" color="white" />
|
|
|
|
|
+ <span v-else>{{ $t('profile.availability.afternoon') }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-caption text-text text-center q-px-sm q-pt-md">
|
|
|
|
|
+ {{ $t('profile.availability.working_days_instructions') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ <div class="text-h6 text-weight-bold gradient-diarista q-mb-xs">
|
|
|
|
|
+ {{ $t('profile.availability.agenda_title') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-caption text-text q-mb-md">
|
|
|
|
|
+ {{ $t('profile.availability.agenda_subtitle') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="shadow-card q-mb-xl " style="border-radius: 20px; overflow: hidden;">
|
|
|
|
|
+ <q-date
|
|
|
|
|
+ ref="calendar"
|
|
|
|
|
+ v-model="calendarDate"
|
|
|
|
|
+ square
|
|
|
|
|
+ class="full-width calendar-custom text-text"
|
|
|
|
|
+ :first-day-of-week="0"
|
|
|
|
|
+ :events="blockedDatesForCalendar"
|
|
|
|
|
+ event-color="negative"
|
|
|
|
|
+ minimal
|
|
|
|
|
+ @update:model-value="onDateClick"
|
|
|
|
|
+ @navigation="onNavigation"
|
|
|
|
|
+ >
|
|
|
|
|
+ </q-date>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="q-pb-xl"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <transition name="fade-sheet">
|
|
|
|
|
+ <div v-if="showActionSheet" class="action-sheet-overlay" @click.self="closeActionSheet">
|
|
|
|
|
+ <div class="action-sheet-panel bg-white q-pa-lg">
|
|
|
|
|
+
|
|
|
|
|
+ <template v-if="currentDateBlockedDays.length > 0">
|
|
|
|
|
+ <div class="text-subtitle2 text-weight-bold text-center text-text text-capitalize q-mb-md">
|
|
|
|
|
+ {{ $t('profile.availability.already_blocked_title') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="bd in currentDateBlockedDays"
|
|
|
|
|
+ :key="bd.id"
|
|
|
|
|
+ class="row items-center q-py-sm no-wrap"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-icon name="mdi-calendar-remove-outline" color="negative" size="20px" class="q-mr-sm" />
|
|
|
|
|
+ <div class="col text-text">
|
|
|
|
|
+ <div class="text-weight-bold">
|
|
|
|
|
+ {{ $t(`profile.availability.period_labels.${bd.period}`) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-caption text-grey-6">{{ `${bd.init_hour} - ${bd.end_hour}` }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ rounded
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ color="negative"
|
|
|
|
|
+ :label="$t('profile.availability.unblock_btn')"
|
|
|
|
|
+ :loading="loadingUnblock[bd.id]"
|
|
|
|
|
+ class="q-ml-sm"
|
|
|
|
|
+ style="font-size: 11px;"
|
|
|
|
|
+ @click="unblockDay(bd)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <div class="row items-center q-py-sm no-wrap">
|
|
|
|
|
+ <q-icon name="mdi-information-outline" color="grey-5" size="20px" class="q-mr-sm" />
|
|
|
|
|
+ <div class="col">
|
|
|
|
|
+ <div class="text-weight-bold text-text">{{ $t('profile.availability.block_day_title') }}</div>
|
|
|
|
|
+ <div class="text-caption text-grey-6">{{ $t('profile.availability.block_day_description') }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ rounded
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ color="secondary"
|
|
|
|
|
+ :label="$t('profile.availability.close_agenda_btn')"
|
|
|
|
|
+ class="q-ml-sm"
|
|
|
|
|
+ style="font-size: 11px;"
|
|
|
|
|
+ @click="openBlockDayConfirm"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <q-separator class="q-my-xs" />
|
|
|
|
|
+ <div class="row items-center q-py-sm no-wrap">
|
|
|
|
|
+ <q-icon name="mdi-information-outline" color="grey-5" size="20px" class="q-mr-sm" />
|
|
|
|
|
+ <div class="col">
|
|
|
|
|
+ <div class="text-weight-bold text-text">{{ $t('profile.availability.block_period_title') }}</div>
|
|
|
|
|
+ <div class="text-caption text-grey-6">{{ $t('profile.availability.block_period_description') }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ rounded
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ color="secondary"
|
|
|
|
|
+ :label="$t('profile.availability.close_agenda_btn')"
|
|
|
|
|
+ class="q-ml-sm"
|
|
|
|
|
+ style="font-size: 11px;"
|
|
|
|
|
+ @click="openBlockPeriodSelect"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </transition>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </q-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, computed, onMounted } from 'vue';
|
|
|
|
|
+import { useDialogPluginComponent, useQuasar, /*date*/ } from 'quasar';
|
|
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
|
|
+import {
|
|
|
|
|
+ getProviderWorkingDays,
|
|
|
|
|
+ createProviderWorkingDay,
|
|
|
|
|
+ deleteProviderWorkingDay,
|
|
|
|
|
+} from 'src/api/providerWorkingDay';
|
|
|
|
|
+import {
|
|
|
|
|
+ getProviderBlockedDays,
|
|
|
|
|
+ createProviderBlockedDay,
|
|
|
|
|
+ deleteProviderBlockedDay,
|
|
|
|
|
+} from 'src/api/providerBlockedDay';
|
|
|
|
|
+import { userStore } from 'src/stores/user';
|
|
|
|
|
+import ProfileBlockDayDialog from './ProfileBlockDayDialog.vue';
|
|
|
|
|
+
|
|
|
|
|
+defineEmits([...useDialogPluginComponent.emits]);
|
|
|
|
|
+
|
|
|
|
|
+const { dialogRef } = useDialogPluginComponent();
|
|
|
|
|
+const $q = useQuasar();
|
|
|
|
|
+const { t } = useI18n();
|
|
|
|
|
+const user = userStore();
|
|
|
|
|
+
|
|
|
|
|
+const daysOfWeek = [0, 1, 2, 3, 4, 5, 6];
|
|
|
|
|
+
|
|
|
|
|
+const workingDays = ref([]);
|
|
|
|
|
+const loadingToggle = ref({});
|
|
|
|
|
+
|
|
|
|
|
+const blockedDays = ref([]);
|
|
|
|
|
+const loadingBlock = ref(false);
|
|
|
|
|
+const loadingUnblock = ref({});
|
|
|
|
|
+
|
|
|
|
|
+const calendar = ref(null);
|
|
|
|
|
+const calendarDate = ref(null);
|
|
|
|
|
+const currentViewDate = ref(new Date());
|
|
|
|
|
+
|
|
|
|
|
+const showActionSheet = ref(false);
|
|
|
|
|
+const currentActionDate = ref(null);
|
|
|
|
|
+
|
|
|
|
|
+const selectedBlockPeriod = ref('morning');
|
|
|
|
|
+
|
|
|
|
|
+const isSelected = (day, period) =>
|
|
|
|
|
+ workingDays.value.some((wd) => wd.day === day && wd.period === period);
|
|
|
|
|
+
|
|
|
|
|
+const isDayFullySelected = (day) =>
|
|
|
|
|
+ isSelected(day, 'morning') && isSelected(day, 'afternoon');
|
|
|
|
|
+
|
|
|
|
|
+const blockedDatesForCalendar = computed(() =>
|
|
|
|
|
+ blockedDays.value.map((bd) => bd.date.replace(/-/g, '/'))
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+const currentDateBlockedDays = computed(() => {
|
|
|
|
|
+ if (!currentActionDate.value) return [];
|
|
|
|
|
+ const dateStr = currentActionDate.value.replace(/\//g, '-');
|
|
|
|
|
+ return blockedDays.value.filter((bd) => bd.date === dateStr);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// const currentMonthYearLabel = computed(() => {
|
|
|
|
|
+// const date = currentViewDate.value;
|
|
|
|
|
+// return date.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' });
|
|
|
|
|
+// });
|
|
|
|
|
+
|
|
|
|
|
+const formattedBlockDate = computed(() => {
|
|
|
|
|
+ if (!currentActionDate.value) return '';
|
|
|
|
|
+ const [year, month, day] = currentActionDate.value.split('/');
|
|
|
|
|
+ const date = new Date(Number(year), Number(month) - 1, Number(day));
|
|
|
|
|
+ const dayNames = [
|
|
|
|
|
+ 'Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira',
|
|
|
|
|
+ 'Quinta-feira', 'Sexta-feira', 'Sábado',
|
|
|
|
|
+ ];
|
|
|
|
|
+ return `${dayNames[date.getDay()]}, ${day}/${month}/${year}`;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const loadWorkingDays = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await getProviderWorkingDays(user.user.provider_id);
|
|
|
|
|
+ workingDays.value = response || [];
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const loadBlockedDays = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await getProviderBlockedDays(user.user.provider_id);
|
|
|
|
|
+ blockedDays.value = response || [];
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const togglePeriod = async (day, period) => {
|
|
|
|
|
+ const key = `${day}_${period}`;
|
|
|
|
|
+ if (loadingToggle.value[key]) return;
|
|
|
|
|
+
|
|
|
|
|
+ loadingToggle.value = { ...loadingToggle.value, [key]: true };
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (isSelected(day, period)) {
|
|
|
|
|
+ await deleteProviderWorkingDay(day, period, user.user.provider_id);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await createProviderWorkingDay({ provider_id: user.user.provider_id, day, period });
|
|
|
|
|
+ }
|
|
|
|
|
+ await loadWorkingDays();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ $q.notify({ type: 'negative', message: t('http.errors.failed') });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ const updated = { ...loadingToggle.value };
|
|
|
|
|
+ delete updated[key];
|
|
|
|
|
+ loadingToggle.value = updated;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const toggleDay = async (day) => {
|
|
|
|
|
+ const key = `${day}_all`;
|
|
|
|
|
+ if (loadingToggle.value[key]) return;
|
|
|
|
|
+
|
|
|
|
|
+ loadingToggle.value = { ...loadingToggle.value, [key]: true };
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (isDayFullySelected(day)) {
|
|
|
|
|
+ await deleteProviderWorkingDay(day, 'morning', user.user.provider_id);
|
|
|
|
|
+ await deleteProviderWorkingDay(day, 'afternoon', user.user.provider_id);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (!isSelected(day, 'morning')) {
|
|
|
|
|
+ await createProviderWorkingDay({ provider_id: user.user.provider_id, day, period: 'morning' });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isSelected(day, 'afternoon')) {
|
|
|
|
|
+ await createProviderWorkingDay({ provider_id: user.user.provider_id, day, period: 'afternoon' });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ await loadWorkingDays();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ $q.notify({ type: 'negative', message: t('http.errors.failed') });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ const updated = { ...loadingToggle.value };
|
|
|
|
|
+ delete updated[key];
|
|
|
|
|
+ loadingToggle.value = updated;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const onDateClick = (date) => {
|
|
|
|
|
+ calendarDate.value = null;
|
|
|
|
|
+ currentActionDate.value = date;
|
|
|
|
|
+ showActionSheet.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// const nextMonth = () => {
|
|
|
|
|
+// const current = currentViewDate.value || new Date();
|
|
|
|
|
+// calendarDate.value = date.formatDate(date.addToDate(current, { months: 1 }), 'YYYY/MM/DD');
|
|
|
|
|
+// };
|
|
|
|
|
+
|
|
|
|
|
+// const prevMonth = () => {
|
|
|
|
|
+// const current = currentViewDate.value || new Date();
|
|
|
|
|
+// calendarDate.value = date.formatDate(date.subtractFromDate(current, { months: 1 }), 'YYYY/MM/DD');
|
|
|
|
|
+// };
|
|
|
|
|
+
|
|
|
|
|
+const onNavigation = (view) => {
|
|
|
|
|
+ currentViewDate.value = new Date(view.year, view.month - 1, 1);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const closeActionSheet = () => {
|
|
|
|
|
+ showActionSheet.value = false;
|
|
|
|
|
+ currentActionDate.value = null;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const openBlockDayConfirm = () => {
|
|
|
|
|
+ showActionSheet.value = false;
|
|
|
|
|
+ $q.dialog({
|
|
|
|
|
+ component: ProfileBlockDayDialog,
|
|
|
|
|
+ componentProps: {
|
|
|
|
|
+ title: t('profile.availability.block_day_confirm_title'),
|
|
|
|
|
+ subtitle: t('profile.availability.block_day_confirm_subtitle'),
|
|
|
|
|
+ dateLabel: formattedBlockDate.value,
|
|
|
|
|
+ isPeriod: false,
|
|
|
|
|
+ loading: loadingBlock.value
|
|
|
|
|
+ }
|
|
|
|
|
+ }).onOk(async () => {
|
|
|
|
|
+ await confirmBlockDay();
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const openBlockPeriodSelect = () => {
|
|
|
|
|
+ showActionSheet.value = false;
|
|
|
|
|
+ $q.dialog({
|
|
|
|
|
+ component: ProfileBlockDayDialog,
|
|
|
|
|
+ componentProps: {
|
|
|
|
|
+ title: t('profile.availability.block_period_confirm_title'),
|
|
|
|
|
+ subtitle: t('profile.availability.block_period_confirm_subtitle'),
|
|
|
|
|
+ dateLabel: formattedBlockDate.value,
|
|
|
|
|
+ isPeriod: true,
|
|
|
|
|
+ loading: loadingBlock.value
|
|
|
|
|
+ }
|
|
|
|
|
+ }).onOk(async (period) => {
|
|
|
|
|
+ selectedBlockPeriod.value = period;
|
|
|
|
|
+ await confirmBlockPeriod();
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const confirmBlockDay = async () => {
|
|
|
|
|
+ loadingBlock.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const [year, month, day] = currentActionDate.value.split('/');
|
|
|
|
|
+ await createProviderBlockedDay({
|
|
|
|
|
+ provider_id: user.user.provider_id,
|
|
|
|
|
+ date: `${year}-${month}-${day}`,
|
|
|
|
|
+ period: 'all',
|
|
|
|
|
+ init_hour: '00:00',
|
|
|
|
|
+ end_hour: '23:59',
|
|
|
|
|
+ });
|
|
|
|
|
+ await loadBlockedDays();
|
|
|
|
|
+ currentActionDate.value = null;
|
|
|
|
|
+ $q.notify({ type: 'positive', message: t('profile.availability.blocked_success') });
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ $q.notify({ type: 'negative', message: t('http.errors.failed') });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loadingBlock.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const confirmBlockPeriod = async () => {
|
|
|
|
|
+ if (!selectedBlockPeriod.value) return;
|
|
|
|
|
+ loadingBlock.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const [year, month, day] = currentActionDate.value.split('/');
|
|
|
|
|
+ const isMorning = selectedBlockPeriod.value === 'morning';
|
|
|
|
|
+ await createProviderBlockedDay({
|
|
|
|
|
+ provider_id: user.user.provider_id,
|
|
|
|
|
+ date: `${year}-${month}-${day}`,
|
|
|
|
|
+ period: selectedBlockPeriod.value,
|
|
|
|
|
+ init_hour: isMorning ? '07:00' : '14:00',
|
|
|
|
|
+ end_hour: isMorning ? '13:00' : '20:00',
|
|
|
|
|
+ });
|
|
|
|
|
+ await loadBlockedDays();
|
|
|
|
|
+ currentActionDate.value = null;
|
|
|
|
|
+ $q.notify({ type: 'positive', message: t('profile.availability.blocked_success') });
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ $q.notify({ type: 'negative', message: t('http.errors.failed') });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loadingBlock.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const unblockDay = async (blockedDay) => {
|
|
|
|
|
+ loadingUnblock.value = { ...loadingUnblock.value, [blockedDay.id]: true };
|
|
|
|
|
+ try {
|
|
|
|
|
+ await deleteProviderBlockedDay(blockedDay.id);
|
|
|
|
|
+ await loadBlockedDays();
|
|
|
|
|
+ if (currentDateBlockedDays.value.length === 0) {
|
|
|
|
|
+ closeActionSheet();
|
|
|
|
|
+ }
|
|
|
|
|
+ $q.notify({ type: 'positive', message: t('profile.availability.unblocked_success') });
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ $q.notify({ type: 'negative', message: t('http.errors.failed') });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ const updated = { ...loadingUnblock.value };
|
|
|
|
|
+ delete updated[blockedDay.id];
|
|
|
|
|
+ loadingUnblock.value = updated;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ await Promise.all([loadWorkingDays(), loadBlockedDays()]);
|
|
|
|
|
+});
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.working-days-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(7, 1fr);
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.day-column {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.day-label {
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ padding: 2px 0;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+
|
|
|
|
|
+ &.day-label-active {
|
|
|
|
|
+ background: linear-gradient(-90deg, #ec48d1 5%, #6b11cb 65%, #2574fc 100%);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.day-label-inactive {
|
|
|
|
|
+ background: #e0e0e0;
|
|
|
|
|
+ color: #757575;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.period-chip {
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ width: 36px;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+ margin-right: auto;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+
|
|
|
|
|
+ &.chip-active {
|
|
|
|
|
+ background-color: #00ff7f;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.chip-inactive {
|
|
|
|
|
+ background-color: #e4e4e4;
|
|
|
|
|
+ color: #c0c0c0;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.availability-notice-card {
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-sheet-overlay {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.801);
|
|
|
|
|
+ z-index: 100;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-sheet-panel {
|
|
|
|
|
+ border-radius: 20px 20px 0 0;
|
|
|
|
|
+ box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.block-confirm-card {
|
|
|
|
|
+ background: linear-gradient(135deg, #7C3AED, #9333EA);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.calendar-custom {
|
|
|
|
|
+ border-radius: 20px;
|
|
|
|
|
+ background-color: white !important;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.q-date__main) {
|
|
|
|
|
+ background-color: white !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.q-date__content) {
|
|
|
|
|
+ background-color: white !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.q-date__calendar) {
|
|
|
|
|
+ background-color: white !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Dias fora do mês atual (mais apagados)
|
|
|
|
|
+ :deep(.q-date__calendar-item--out) {
|
|
|
|
|
+ .q-btn__content {
|
|
|
|
|
+ color: #CBD5E1 !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Dias do mês atual
|
|
|
|
|
+ :deep(.q-date__calendar-days .q-btn__content) {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #1E293B;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Dias da semana (Dom, Seg...) no header do calendário
|
|
|
|
|
+ :deep(.q-date__calendar-weekdays > div) {
|
|
|
|
|
+ color: #6366F1;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ opacity: 0.8;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Header de navegação do minimal (< Mês Ano >)
|
|
|
|
|
+ :deep(.q-date__navigation) {
|
|
|
|
|
+ .q-btn {
|
|
|
|
|
+ color: #1E293B !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ .q-btn__content {
|
|
|
|
|
+ color: #1E293B !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Texto do mês/ano no header de navegação
|
|
|
|
|
+ :deep(.q-date__nav-btn-month),
|
|
|
|
|
+ :deep(.q-date__nav-btn-year) {
|
|
|
|
|
+ color: #6366F1 !important;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Evento (ponto abaixo do dia bloqueado)
|
|
|
|
|
+ :deep(.q-date__event) {
|
|
|
|
|
+ bottom: 4px;
|
|
|
|
|
+ height: 6px;
|
|
|
|
|
+ width: 6px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Dia de hoje
|
|
|
|
|
+ :deep(.q-date__today) {
|
|
|
|
|
+ .q-btn__content {
|
|
|
|
|
+ color: #7c4dff !important;
|
|
|
|
|
+ background: #7c4dff15;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Dia selecionado
|
|
|
|
|
+ :deep(.q-date__selected) {
|
|
|
|
|
+ .q-btn__content {
|
|
|
|
|
+ background: #6366F1 !important;
|
|
|
|
|
+ color: white !important;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ box-shadow: 0 4px 10px rgba(99, 102, 241, 0.4);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Views de meses e anos (quando clica no nome do mês/ano para selecionar)
|
|
|
|
|
+ :deep(.q-date__view--months),
|
|
|
|
|
+ :deep(.q-date__view--years) {
|
|
|
|
|
+ .q-btn {
|
|
|
|
|
+ color: #1E293B !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.fade-sheet-enter-active,
|
|
|
|
|
+.fade-sheet-leave-active {
|
|
|
|
|
+ transition: opacity 0.25s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.fade-sheet-enter-from,
|
|
|
|
|
+.fade-sheet-leave-to {
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|