Преглед на файлове

profile do prestador funcional

Gustavo Zanatta преди 1 месец
родител
ревизия
1992f142df

+ 6 - 0
src/api/provider.js

@@ -0,0 +1,6 @@
+import api from 'src/api';
+
+export const updateProvider = async (id, data) => {
+  const { data: response } = await api.put(`/provider/${id}`, data);
+  return response.payload;
+};

+ 16 - 0
src/api/providerBlockedDay.js

@@ -0,0 +1,16 @@
+import api from "src/api";
+
+export const getProviderBlockedDays = async (providerId) => {
+  const { data } = await api.get(`/provider/blocked-days/${providerId}`);
+  return data.payload;
+};
+
+export const createProviderBlockedDay = async (info) => {
+  const { data } = await api.post("/provider/blocked-day", info);
+  return data.payload;
+};
+
+export const deleteProviderBlockedDay = async (id) => {
+  const { data } = await api.delete(`/provider/blocked-day/${id}`);
+  return data.payload;
+};

+ 16 - 0
src/api/providerServiceType.js

@@ -0,0 +1,16 @@
+import api from 'src/api';
+
+export const getProviderServiceTypes = async (providerId) => {
+  const { data } = await api.get(`/provider/service-types/${providerId}`);
+  return data.payload;
+};
+
+export const createProviderServiceType = async (providerId, info) => {
+  const { data } = await api.post(`/provider/service-types/${providerId}`, info);
+  return data.payload;
+};
+
+export const deleteProviderServiceType = async (providerId, id) => {
+  const { data } = await api.delete(`/provider/service-types/${providerId}/${id}`);
+  return data.payload;
+};

+ 18 - 0
src/api/providerWorkingDay.js

@@ -0,0 +1,18 @@
+import api from "src/api";
+
+export const getProviderWorkingDays = async (providerId) => {
+  const { data } = await api.get(`/provider/working-days/${providerId}`);
+  return data.payload;
+};
+
+export const createProviderWorkingDay = async (info) => {
+  const { data } = await api.post("/provider/working-day", info);
+  return data.payload;
+};
+
+export const deleteProviderWorkingDay = async (day, period, providerId) => {
+  const { data } = await api.delete(
+    `/provider/working-day/${providerId}?day=${day}&period=${period}`
+  );
+  return data.payload;
+};

Файловите разлики са ограничени, защото са твърде много
+ 6 - 0
src/assets/diarinho_suporte.svg


+ 615 - 0
src/components/profile/ProfileAvailabilityDialog.vue

@@ -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>

+ 88 - 0
src/components/profile/ProfileBlockDayDialog.vue

@@ -0,0 +1,88 @@
+<template>
+  <q-dialog ref="dialogRef">
+    <q-card class="block-confirm-card shadow-card" style="border-radius: 20px; min-width: 280px;">
+      <q-card-section class="text-white text-center q-pa-lg">
+        <div class="text-h6 text-weight-bold q-mb-sm">{{ title }}</div>
+        <div class="text-caption q-mb-md">{{ subtitle }}</div>
+        <div v-if="dateLabel" class="text-subtitle1 text-weight-bold">{{ dateLabel }}</div>
+        
+        <div v-if="isPeriod" class="row justify-center q-gutter-lg q-mt-md">
+          <q-radio
+            v-model="selectedPeriod"
+            val="morning"
+            :label="$t('profile.availability.morning_hours')"
+            color="white"
+            dark
+            class="text-white"
+          />
+          <q-radio
+            v-model="selectedPeriod"
+            val="afternoon"
+            :label="$t('profile.availability.afternoon_hours')"
+            color="white"
+            dark
+            class="text-white"
+          />
+        </div>
+      </q-card-section>
+      
+      <q-card-actions align="center" class="q-pb-xl">
+        <q-btn
+          rounded
+          no-caps
+          unelevated
+          class="full-width q-mx-lg text-weight-bold"
+          style="background-color: #ff70ff; color: white; height: 50px; border-radius: 15px;"
+          :label="$t('profile.availability.block_btn')"
+          :loading="loading"
+          :disable="isPeriod && !selectedPeriod"
+          @click="onOk"
+        />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useDialogPluginComponent } from 'quasar';
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  subtitle: {
+    type: String,
+    default: ''
+  },
+  dateLabel: {
+    type: String,
+    default: ''
+  },
+  isPeriod: {
+    type: Boolean,
+    default: false
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  }
+});
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogOK } = useDialogPluginComponent();
+const selectedPeriod = ref('morning');
+
+const onOk = () => {
+  onDialogOK(props.isPeriod ? selectedPeriod.value : true);
+};
+</script>
+
+<style scoped lang="scss">
+.block-confirm-card {
+  background: #9078ff;
+}
+
+</style>

+ 225 - 0
src/components/profile/ProfileHelpDialog.vue

@@ -0,0 +1,225 @@
+<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.help.title') }}</span>
+        <q-space />
+        <div style="width: 32px"></div>
+      </div>
+
+      <div class="col overflow-auto">
+
+        <div class="support-banner row no-wrap">
+          <div class="col-8 q-pa-md">
+            <div class="row items-center q-mb-xs q-gutter-x-sm">
+              <span class="text-h6 text-white text-weight-bold">{{ $t('profile.help.support_title') }}</span>
+            </div>
+            <div class="row items-center q-gutter-x-xs q-mb-md">
+              <q-icon name="mdi-circle" color="green-4" size="10px" />
+              <span class="text-caption text-white">{{ $t('profile.help.online_status') }}</span>
+            </div>
+            <div class="row items-center q-gutter-x-sm">
+              <q-icon name="mdi-robot-outline" color="white" size="18px" />
+              <span class="text-caption text-white">{{ $t('profile.help.ai_assistant_label') }}</span>
+            </div>
+          </div>
+          <div class="col-4 flex items-end">
+            <q-img
+              :src="diarinho_suporte"
+              style="width: 110px; height: 110px; object-fit: cover;"
+            >
+              <template #error>
+                <div class="support-avatar-placeholder column flex-center">
+                  <q-icon name="mdi-face-agent" size="48px" color="white" />
+                </div>
+              </template>
+            </q-img>
+          </div>
+        </div>
+
+        <div class="q-px-md q-pt-lg q-pb-xl">
+
+          <q-card class="bg-surface shadow-card q-mb-lg border-message-support" style="border-radius: 16px;">
+            <q-card-section class="q-pb-xs">
+              <div class="row items-center q-gutter-x-sm q-mb-sm">
+                <q-icon name="mdi-message-outline" color="primary" size="18px" />
+                <span class="text-caption text-weight-bold text-primary">{{ $t('profile.help.virtual_assistant') }}</span>
+              </div>
+              <p class="text-text q-mb-xs">{{ $t('profile.help.greeting_message') }}</p>
+              <span class="text-caption text-grey-5">{{ currentTime }}</span>
+            </q-card-section>
+          </q-card>
+          <div class="q-pt-sm">
+            <div class="col-12 text-caption text-grey-6 q-mb-sm">{{ $t('profile.help.quick_suggestions') }}</div>
+            <div 
+              v-for="suggestion in suggestions"
+              :key="suggestion"
+              class="row col-12 q-py-xs"
+            >
+              <span class="text-text bg-surface suggestion-btn q-py-sm q-px-md">{{ suggestion }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="bg-surface chat-footer q-px-md q-py-sm shadow-up">
+        <div class="row items-center q-gutter-x-sm">
+          <q-input
+            v-model="messageInput"
+            dense
+            class="col input-suporte"
+            borderless
+            input-class="text-text"
+            :placeholder="$t('profile.help.message_placeholder')"
+            @keyup.enter="sendMessage"
+          />
+          <q-btn
+            round
+            unelevated
+            color="primary"
+            icon="mdi-send"
+            size="sm"
+            :disable="!messageInput.trim()"
+            @click="sendMessage"
+          />
+        </div>
+        <div class="footer-disclaimer text-text text-center q-my-md">
+          {{ $t('profile.help.footer_disclaimer') }}
+        </div>
+      </div>
+
+    </div>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+import { useDialogPluginComponent, useQuasar } from 'quasar';
+import { useI18n } from 'vue-i18n';
+import diarinho_suporte from 'src/assets/diarinho_suporte.svg';
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef } = useDialogPluginComponent();
+const $q = useQuasar();
+const { t } = useI18n();
+
+const messageInput = ref('');
+
+const currentTime = computed(() => {
+  return new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
+});
+
+const suggestions = computed(() => [
+  t('profile.help.suggestion_cancel'),
+  t('profile.help.suggestion_data'),
+  t('profile.help.suggestion_payment'),
+  t('profile.help.suggestion_human'),
+]);
+
+const sendMessage = () => {
+  if (!messageInput.value.trim()) return;
+  $q.notify({ type: 'info', message: t('profile.help.coming_soon') });
+  messageInput.value = '';
+};
+</script>
+
+<style scoped lang="scss">
+.support-banner {
+  background: linear-gradient(180deg, #6C54C1 0%, #9A7FF6 100%);
+  min-height: 120px;
+}
+
+.support-avatar-placeholder {
+  width: 90px;
+  height: 90px;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.2);
+}
+
+.suggestion-btn {
+  border-radius: 32px;
+  font-size: 12px;
+  height: auto;
+  min-height: unset;
+  border: 1.15px solid #d8d7d7ce;
+  font-family: Inter;
+  font-weight: 400;
+  font-style: Regular;
+  font-size: 12px;
+  line-height: 16px;
+  letter-spacing: 0px;
+}
+
+.chat-footer {
+  border-top: 1px solid rgba(0, 0, 0, 0.06);
+}
+
+.shadow-up {
+  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08);
+}
+
+.border-message-support {
+  border: 1px solid rgba(0, 0, 0, 0.151);
+}
+
+.input-suporte {
+  :deep(.q-field__control) {
+    background: transparent;
+    border: none;
+    box-shadow: none;
+    padding: 0;
+  }
+
+  :deep(.q-field__control-container) {
+    background: var(--q-page, #f5f5f5);
+    border: 1px solid rgba(0, 0, 0, 0.12);
+    border-radius: 32px;
+
+    padding: 10px 14px;
+  }
+
+  :deep(.q-field__native) {
+    padding: 0 !important;
+  }
+
+  :deep(.q-field__control::before),
+  :deep(.q-field__control::after) {
+    display: none !important;
+  }
+
+  :deep(.q-field--focused .q-field__control-container) {
+    box-shadow: none;
+  }
+
+  :deep(.q-field__bottom),
+  :deep(.q-field__marginal) {
+    display: none;
+  }
+
+  :deep(.q-field__control),
+  :deep(.q-field__control:before),
+  :deep(.q-field__control:after) {
+    border: none !important;
+    box-shadow: none !important;
+    outline: none !important;
+  }
+
+  :deep(.q-field--focused .q-field__control),
+  :deep(.q-field--focused .q-field__control-container) {
+    box-shadow: none !important;
+    border-color: rgba(0, 0, 0, 0.12) !important;
+  }
+}
+
+.footer-disclaimer {
+  font-family: Inter;
+  font-weight: 400;
+  font-style: Regular;
+  font-size: 12px;
+  line-height: 15px;
+  letter-spacing: 0px;
+  text-align: center;
+}
+</style>

+ 296 - 0
src/components/profile/ProfileServiceDataDialog.vue

@@ -0,0 +1,296 @@
+<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.service_data.title') }}</span>
+        <q-space />
+        <div style="width: 32px"></div>
+      </div>
+
+      <div class="col overflow-auto">
+        <div class="q-px-md q-pt-lg q-pb-xl">
+
+          <div class="text-h6 text-weight-bold gradient-diarista q-mb-xs">
+            {{ $t('profile.service_data.pricing_title') }}
+          </div>
+          <div class="text-caption text-text q-mb-md">
+            {{ $t('profile.service_data.pricing_subtitle') }}
+          </div>
+
+          <q-card class="q-pa-lg bg-surface shadow-card q-mb-lg" style="border-radius: 20px;" :flat="false">
+            <div class="text-weight-medium text-text q-mb-sm">
+              {{ $t('profile.service_data.price_8h_label') }}
+            </div>
+
+            <DefaultCurrencyInput
+              v-model="form.daily_price_8h"
+              class="q-mb-xs"
+              input-class="text-text"
+              :error="!!priceError"
+              :error-message="priceError"
+              hide-bottom-space
+              label=""
+            />
+
+            <div class="text-caption text-grey-6 text-center q-mb-lg">
+              {{ $t('profile.service_data.price_min_max') }}
+            </div>
+
+            <div class="text-text text-center q-mb-sm">
+              {{ $t('profile.service_data.shorter_services') }}
+            </div>
+
+            <div class="row q-col-gutter-sm q-mb-sm">
+              <div class="col-4">
+                <div class="text-caption text-weight-bold text-text text-center q-mb-xs">
+                  {{ $t('profile.service_data.up_to_6h') }}
+                </div>
+                <q-input
+                  :model-value="formatCurrency(form.daily_price_6h)"
+                  outlined
+                  dense
+                  readonly
+                  input-class="text-text text-center"
+                />
+              </div>
+              <div class="col-4">
+                <div class="text-caption text-weight-bold text-text text-center q-mb-xs">
+                  {{ $t('profile.service_data.up_to_4h') }}
+                </div>
+                <q-input
+                  :model-value="formatCurrency(form.daily_price_4h)"
+                  outlined
+                  dense
+                  readonly
+                  input-class="text-text text-center"
+                />
+              </div>
+              <div class="col-4">
+                <div class="text-caption text-weight-bold text-text text-center q-mb-xs">
+                  {{ $t('profile.service_data.up_to_2h') }}
+                </div>
+                <q-input
+                  :model-value="formatCurrency(form.daily_price_2h)"
+                  outlined
+                  dense
+                  readonly
+                  input-class="text-text text-center"
+                />
+              </div>
+            </div>
+          </q-card>
+
+          <div class="text-h6 text-weight-bold gradient-diarista q-mb-xs">
+            {{ $t('profile.service_data.services_title') }}
+          </div>
+          <div class="text-caption text-text q-mb-md">
+            {{ $t('profile.service_data.services_subtitle') }}
+          </div>
+
+          <q-card class="q-pa-lg bg-surface shadow-card q-mb-lg" style="border-radius: 20px;" :flat="false">
+            <div v-if="loadingServiceTypes" class="row justify-center q-py-md">
+              <q-spinner color="primary" size="28px" />
+            </div>
+
+            <div v-else-if="serviceOptions.length" class="row q-col-gutter-sm">
+              <div v-for="service in serviceOptions" :key="service.value" class="col-6">
+                <q-checkbox
+                  :model-value="isServiceSelected(service.value)"
+                  :label="service.label"
+                  :disable="togglingService[service.value]"
+                  color="primary"
+                  class="text-text"
+                  keep-color
+                  @update:model-value="(checked) => onServiceToggle(service, checked)"
+                />
+              </div>
+            </div>
+
+            <div v-else class="text-caption text-grey-7 text-center q-py-md">
+              {{ $t('profile.service_data.no_services') }}
+            </div>
+
+            <div class="text-caption text-grey-6 text-center q-mt-md">
+              {{ $t('profile.service_data.services_hint') }}
+            </div>
+          </q-card>
+
+          <q-btn
+            unelevated
+            rounded
+            no-caps
+            color="primary"
+            class="full-width q-py-md text-weight-bold"
+            style="font-size: 1.05rem; border-radius: 14px;"
+            :label="$t('common.actions.update')"
+            :loading="saving"
+            :disable="!!priceError || saving"
+            @click="save"
+          />
+
+        </div>
+      </div>
+
+    </div>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from 'vue';
+import { useDialogPluginComponent, useQuasar } from 'quasar';
+import { useI18n } from 'vue-i18n';
+import { getPublicServiceTypes } from 'src/api/serviceType';
+import { getProviderServiceTypes, createProviderServiceType, deleteProviderServiceType } from 'src/api/providerServiceType';
+import { getUser } from 'src/api/user';
+import { updateProvider } from 'src/api/provider';
+import { userStore } from 'src/stores/user';
+import DefaultCurrencyInput from 'src/components/defaults/DefaultCurrencyInput.vue';
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogOK } = useDialogPluginComponent();
+const $q = useQuasar();
+const { t } = useI18n();
+const user = userStore();
+
+const form = ref({
+  daily_price_8h: null,
+  daily_price_6h: null,
+  daily_price_4h: null,
+  daily_price_2h: null,
+});
+
+const serviceOptions = ref([]);
+const providerServiceTypes = ref([]);
+const loadingServiceTypes = ref(false);
+const togglingService = ref({});
+const saving = ref(false);
+
+const priceError = computed(() => {
+  const value = Number(form.value.daily_price_8h);
+  if (!form.value.daily_price_8h && form.value.daily_price_8h !== 0) return '';
+  if (Number.isNaN(value)) return t('validation.rules.required');
+  if (value < 100) return t('profile.service_data.price_min_error');
+  if (value > 500) return t('profile.service_data.price_max_error');
+  return '';
+});
+
+const formatCurrency = (value) => {
+  if (value === null || value === undefined || Number.isNaN(Number(value))) return 'R$ 0,00';
+  return Number(value).toLocaleString('pt-BR', {
+    style: 'currency',
+    currency: 'BRL',
+    minimumFractionDigits: 2,
+  });
+};
+
+const isServiceSelected = (serviceTypeId) => {
+  return providerServiceTypes.value.some((pst) => pst.service_type_id === serviceTypeId);
+};
+
+watch(
+  () => form.value.daily_price_8h,
+  (value) => {
+    const price = Number(value);
+    if (!price || Number.isNaN(price)) {
+      form.value.daily_price_6h = null;
+      form.value.daily_price_4h = null;
+      form.value.daily_price_2h = null;
+      return;
+    }
+    form.value.daily_price_6h = Number((price * 0.85).toFixed(2));
+    form.value.daily_price_4h = Number((price * 0.55).toFixed(2));
+    form.value.daily_price_2h = Number((price * 0.30).toFixed(2));
+  },
+  { immediate: true },
+);
+
+const loadData = async () => {
+  try {
+    const userData = await getUser();
+    form.value.daily_price_8h = userData.provider_daily_price_8h ?? null;
+    form.value.daily_price_6h = userData.provider_daily_price_6h ?? null;
+    form.value.daily_price_4h = userData.provider_daily_price_4h ?? null;
+    form.value.daily_price_2h = userData.provider_daily_price_2h ?? null;
+  } catch (error) {
+    console.error('Erro ao carregar dados do prestador:', error);
+  }
+};
+
+const loadServiceTypes = async () => {
+  loadingServiceTypes.value = true;
+  try {
+    const [allTypes, providerTypes] = await Promise.all([
+      getPublicServiceTypes(),
+      getProviderServiceTypes(user.user.provider_id),
+    ]);
+
+    serviceOptions.value = (Array.isArray(allTypes) ? allTypes : [])
+      .filter((item) => item?.is_active !== false)
+      .map((item) => ({ label: item.description, value: Number(item.id) }))
+      .filter((item) => Number.isInteger(item.value) && item.value > 0);
+
+    providerServiceTypes.value = Array.isArray(providerTypes) ? providerTypes : [];
+  } catch (error) {
+    console.error('Erro ao carregar tipos de serviço:', error);
+    $q.notify({ type: 'negative', message: t('http.errors.failed') });
+  } finally {
+    loadingServiceTypes.value = false;
+  }
+};
+
+const onServiceToggle = async (service, checked) => {
+  togglingService.value = { ...togglingService.value, [service.value]: true };
+  try {
+    if (checked) {
+      const created = await createProviderServiceType(user.user.provider_id, {
+        service_type_id: service.value,
+      });
+      providerServiceTypes.value = [...providerServiceTypes.value, created];
+    } else {
+      const existing = providerServiceTypes.value.find((pst) => pst.service_type_id === service.value);
+      if (existing) {
+        await deleteProviderServiceType(user.user.provider_id, existing.id);
+        providerServiceTypes.value = providerServiceTypes.value.filter((pst) => pst.id !== existing.id);
+      }
+    }
+  } catch {
+    $q.notify({ type: 'negative', message: t('http.errors.failed') });
+  } finally {
+    const updated = { ...togglingService.value };
+    delete updated[service.value];
+    togglingService.value = updated;
+  }
+};
+
+const save = async () => {
+  if (priceError.value) return;
+  saving.value = true;
+  try {
+    await updateProvider(user.user.provider_id, {
+      daily_price_8h: form.value.daily_price_8h,
+      daily_price_6h: form.value.daily_price_6h,
+      daily_price_4h: form.value.daily_price_4h,
+      daily_price_2h: form.value.daily_price_2h,
+    });
+    onDialogOK();
+  } catch {
+    $q.notify({ type: 'negative', message: t('http.errors.failed') });
+  } finally {
+    saving.value = false;
+  }
+};
+
+onMounted(async () => {
+  await Promise.all([loadData(), loadServiceTypes()]);
+});
+</script>
+
+<style scoped lang="scss">
+:deep(.q-field--outlined .q-field__control) {
+  border-radius: 10px;
+}
+</style>

+ 77 - 3
src/i18n/locales/en.json

@@ -14,6 +14,7 @@
       "cancel": "Cancel",
       "edit": "Edit",
       "add": "Add",
+      "update": "Update",
       "search": "Search",
       "delete": "Delete",
       "view": "View",
@@ -303,11 +304,63 @@
     },
     "availability": {
       "title": "Availability",
-      "description": "Days I am available"
+      "description": "Days I am available",
+      "working_days_title": "Service availability",
+      "working_days_subtitle": "Here you can define the best days to receive service requests.",
+      "working_days_hint": "Tap and block days or periods if you do not want to receive service requests during that period.",
+      "working_days_instructions": "To block individual days, click on the day; to block only periods, click on morning or afternoon.",
+      "morning": "morning",
+      "afternoon": "afternoon",
+      "days_short": {
+        "0": "SUN",
+        "1": "MON",
+        "2": "TUE",
+        "3": "WED",
+        "4": "THU",
+        "5": "FRI",
+        "6": "SAT"
+      },
+      "agenda_title": "Schedule",
+      "agenda_subtitle": "If you want to close a specific date, access the calendar and block it.",
+      "block_day_title": "Block day",
+      "block_day_description": "Block the entire day",
+      "block_period_title": "Block Period",
+      "block_period_description": "Block morning or afternoon",
+      "close_agenda_btn": "Close schedule",
+      "block_day_confirm_title": "Block day",
+      "block_day_confirm_subtitle": "Are you sure you want to block service calls for the day:",
+      "block_period_confirm_title": "Block Period",
+      "block_period_confirm_subtitle": "Select the time slot you want to close so no requests appear.",
+      "morning_hours": "7am to 1pm",
+      "afternoon_hours": "2pm to 8pm",
+      "block_btn": "Block",
+      "blocked_success": "Day blocked successfully!",
+      "already_blocked_title": "Day already blocked",
+      "unblock_btn": "Unblock",
+      "unblocked_success": "Day unblocked successfully!",
+      "period_labels": {
+        "morning": "Morning",
+        "afternoon": "Afternoon",
+        "all": "Full day"
+      }
     },
     "service_data": {
       "title": "Service data",
-      "description": "Rates and services"
+      "description": "Rates and services",
+      "pricing_title": "My rates",
+      "pricing_subtitle": "Set your 8-hour daily rate. The others are calculated automatically.",
+      "price_8h_label": "What is your rate for up to 8 hours?",
+      "price_min_max": "Minimum R$100.00. Maximum R$500.00.",
+      "price_min_error": "Minimum value R$ 100.00",
+      "price_max_error": "Maximum value R$ 500.00",
+      "shorter_services": "Based on the value above, the rates for shorter services will be:",
+      "up_to_6h": "up to 6 hours",
+      "up_to_4h": "up to 4 hours",
+      "up_to_2h": "up to 2 hours",
+      "services_title": "Other services",
+      "services_subtitle": "In addition to basic cleaning, what other services do you also provide?",
+      "services_hint": "This information will appear to those searching your profile.",
+      "no_services": "No services available at the moment."
     },
     "address": {
       "title": "Address",
@@ -328,7 +381,28 @@
     },
     "help": {
       "title": "Help",
-      "description": "Questions and support"
+      "description": "Questions and support",
+      "support_title": "Support",
+      "online_status": "Online",
+      "ai_assistant_label": "AI Assistant",
+      "virtual_assistant": "Virtual Assistant",
+      "greeting_message": "Hi! 👋 I'm the Diária virtual assistant. How can I help you today?",
+      "quick_suggestions": "Quick suggestions:",
+      "suggestion_cancel": "How to cancel a service?",
+      "suggestion_data": "Change registration data",
+      "suggestion_payment": "Questions about payment",
+      "suggestion_human": "Talk to an agent",
+      "contact_title": "Contact us",
+      "contact_subtitle": "Choose your preferred support channel.",
+      "channel_whatsapp": "WhatsApp",
+      "channel_whatsapp_desc": "Quick support via WhatsApp",
+      "channel_email": "E-mail",
+      "channel_email_desc": "Send your question by e-mail",
+      "channel_phone": "Phone",
+      "channel_phone_desc": "Talk directly with our team",
+      "message_placeholder": "Type your message...",
+      "footer_disclaimer": "⚡ Automatic AI responses • Human support available",
+      "coming_soon": "Coming soon"
     },
     "logout": {
       "title": "Logout",

+ 77 - 3
src/i18n/locales/es.json

@@ -14,6 +14,7 @@
       "cancel": "Cancelar",
       "edit": "Editar",
       "add": "Añadir",
+      "update": "Actualizar",
       "search": "Buscar",
       "delete": "Eliminar",
       "view": "Ver",
@@ -303,11 +304,63 @@
     },
     "availability": {
       "title": "Disponibilidad",
-      "description": "Días que estoy disponible"
+      "description": "Días que estoy disponible",
+      "working_days_title": "Disponibilidad de servicios",
+      "working_days_subtitle": "Aquí puede definir los mejores días para recibir solicitudes de servicios.",
+      "working_days_hint": "Toque y bloquee los días o períodos si no desea recibir solicitudes de servicios en ese período.",
+      "working_days_instructions": "Para bloquear días individuales, haga clic en el día; para bloquear solo períodos, haga clic en mañana o tarde.",
+      "morning": "mañana",
+      "afternoon": "tarde",
+      "days_short": {
+        "0": "DOM",
+        "1": "LUN",
+        "2": "MAR",
+        "3": "MIÉ",
+        "4": "JUE",
+        "5": "VIE",
+        "6": "SÁB"
+      },
+      "agenda_title": "Agenda",
+      "agenda_subtitle": "Si desea cerrar una fecha específica, acceda al calendario y realice el bloqueo.",
+      "block_day_title": "Bloquear día",
+      "block_day_description": "Bloquear el período completo",
+      "block_period_title": "Bloquear Período",
+      "block_period_description": "Bloquear mañana o tarde",
+      "close_agenda_btn": "Cerrar agenda",
+      "block_day_confirm_title": "Bloquear día",
+      "block_day_confirm_subtitle": "¿Está seguro de que desea bloquear las llamadas de servicio para el día:",
+      "block_period_confirm_title": "Bloquear Período",
+      "block_period_confirm_subtitle": "Seleccione el horario que desea cerrar para que no aparezcan pedidos.",
+      "morning_hours": "7h a las 13h",
+      "afternoon_hours": "14h a las 20h",
+      "block_btn": "Bloquear",
+      "blocked_success": "¡Día bloqueado con éxito!",
+      "already_blocked_title": "Día ya bloqueado",
+      "unblock_btn": "Desbloquear",
+      "unblocked_success": "¡Día desbloqueado con éxito!",
+      "period_labels": {
+        "morning": "Mañana",
+        "afternoon": "Tarde",
+        "all": "Día completo"
+      }
     },
     "service_data": {
       "title": "Datos de servicio",
-      "description": "Valores y servicios"
+      "description": "Valores y servicios",
+      "pricing_title": "Mis tarifas",
+      "pricing_subtitle": "Define el valor de tu jornada de 8 horas. Los demás se calculan automáticamente.",
+      "price_8h_label": "¿Cuál es tu tarifa por hasta 8 horas?",
+      "price_min_max": "Mínimo R$100,00. Máximo R$500,00.",
+      "price_min_error": "Valor mínimo R$ 100,00",
+      "price_max_error": "Valor máximo R$ 500,00",
+      "shorter_services": "Basado en el valor anterior, las tarifas para servicios más cortos serán:",
+      "up_to_6h": "hasta 6 horas",
+      "up_to_4h": "hasta 4 horas",
+      "up_to_2h": "hasta 2 horas",
+      "services_title": "Otros servicios",
+      "services_subtitle": "Además de la limpieza básica, ¿qué otros servicios también realizas?",
+      "services_hint": "Esta información aparecerá para quienes busquen tu perfil.",
+      "no_services": "No hay servicios disponibles por el momento."
     },
     "address": {
       "title": "Dirección",
@@ -328,7 +381,28 @@
     },
     "help": {
       "title": "Ayuda",
-      "description": "Dudas y soporte"
+      "description": "Dudas y soporte",
+      "support_title": "Soporte",
+      "online_status": "En línea",
+      "ai_assistant_label": "Asistente con IA",
+      "virtual_assistant": "Asistente Virtual",
+      "greeting_message": "¡Hola! 👋 Soy el asistente virtual de Diária. ¿Cómo puedo ayudarte hoy?",
+      "quick_suggestions": "Sugerencias rápidas:",
+      "suggestion_cancel": "¿Cómo cancelar un servicio?",
+      "suggestion_data": "Cambiar datos de registro",
+      "suggestion_payment": "Dudas sobre el pago",
+      "suggestion_human": "Hablar con un agente",
+      "contact_title": "Contáctenos",
+      "contact_subtitle": "Elige tu canal de atención preferido.",
+      "channel_whatsapp": "WhatsApp",
+      "channel_whatsapp_desc": "Atención rápida por WhatsApp",
+      "channel_email": "Correo electrónico",
+      "channel_email_desc": "Envía tu duda por correo electrónico",
+      "channel_phone": "Teléfono",
+      "channel_phone_desc": "Habla directamente con nuestro equipo",
+      "message_placeholder": "Escribe tu mensaje...",
+      "footer_disclaimer": "⚡ Respuestas automáticas por IA • Atención humana disponible",
+      "coming_soon": "Próximamente disponible"
     },
     "logout": {
       "title": "Salir",

+ 77 - 3
src/i18n/locales/pt.json

@@ -14,6 +14,7 @@
       "cancel": "Cancelar",
       "edit": "Editar",
       "add": "Adicionar",
+      "update": "Atualizar",
       "search": "Buscar",
       "delete": "Excluir",
       "view": "Visualizar",
@@ -303,11 +304,63 @@
     },
     "availability": {
       "title": "Disponibilidade",
-      "description": "Dias que estou disponível"
+      "description": "Dias que estou disponível",
+      "working_days_title": "Disponibilidade de serviços",
+      "working_days_subtitle": "Aqui você pode definir quais os melhores dias para receber solicitações de serviços.",
+      "working_days_hint": "Toque e bloqueie os dias ou períodos se você não deseja receber solicitações de serviços neste período.",
+      "working_days_instructions": "Para bloquear dias individuais, clique no dia, para bloquear apenas períodos, clique em manhã ou tarde.",
+      "morning": "manhã",
+      "afternoon": "tarde",
+      "days_short": {
+        "0": "DOM",
+        "1": "SEG",
+        "2": "TER",
+        "3": "QUA",
+        "4": "QUI",
+        "5": "SEX",
+        "6": "SÁB"
+      },
+      "agenda_title": "Agenda",
+      "agenda_subtitle": "Se desejar fechar uma data específica acesse o calendário e faça o bloqueio.",
+      "block_day_title": "Bloquear dia",
+      "block_day_description": "Bloquear período inteiro",
+      "block_period_title": "Bloquear Período",
+      "block_period_description": "Bloquear manhã ou tarde",
+      "close_agenda_btn": "Fechar agenda",
+      "block_day_confirm_title": "Bloquear dia",
+      "block_day_confirm_subtitle": "Tem certeza que deseja bloquear as chamadas de serviços para o dia:",
+      "block_period_confirm_title": "Bloquear Período",
+      "block_period_confirm_subtitle": "Selecione o horário que deseja fechar para que não apareçam pedidos.",
+      "morning_hours": "7h às 13h",
+      "afternoon_hours": "14h às 20h",
+      "block_btn": "Bloquear",
+      "blocked_success": "Dia bloqueado com sucesso!",
+      "already_blocked_title": "Dia já bloqueado",
+      "unblock_btn": "Desbloquear",
+      "unblocked_success": "Dia desbloqueado com sucesso!",
+      "period_labels": {
+        "morning": "Manhã",
+        "afternoon": "Tarde",
+        "all": "Dia todo"
+      }
     },
     "service_data": {
       "title": "Dados de serviço",
-      "description": "Valores e serviços"
+      "description": "Valores e serviços",
+      "pricing_title": "Meus valores",
+      "pricing_subtitle": "Defina o valor da sua diária de 8 horas. Os demais são calculados automaticamente.",
+      "price_8h_label": "Qual valor da sua diária de até 8 horas?",
+      "price_min_max": "Valor mínimo R$100,00. Valor máximo R$ 500,00.",
+      "price_min_error": "Valor mínimo R$ 100,00",
+      "price_max_error": "Valor máximo R$ 500,00",
+      "shorter_services": "Baseado no valor acima, os valores de serviços mais curtos serão:",
+      "up_to_6h": "até 6 horas",
+      "up_to_4h": "até 4 horas",
+      "up_to_2h": "até 2 horas",
+      "services_title": "Outros serviços",
+      "services_subtitle": "Além da limpeza básica, quais serviços você também realiza?",
+      "services_hint": "Essas informações aparecerão para quem buscar o seu perfil na busca.",
+      "no_services": "Nenhum serviço disponível no momento."
     },
     "address": {
       "title": "Endereço",
@@ -328,7 +381,28 @@
     },
     "help": {
       "title": "Ajuda",
-      "description": "Dúvidas e suporte"
+      "description": "Dúvidas e suporte",
+      "support_title": "Suporte",
+      "online_status": "Online",
+      "ai_assistant_label": "Assistente com IA",
+      "virtual_assistant": "Assistente Virtual",
+      "greeting_message": "Olá! 👋 Sou o assistente virtual do Diária. Como posso ajudar você hoje?",
+      "quick_suggestions": "Sugestões rápidas:",
+      "suggestion_cancel": "Como cancelar um serviço?",
+      "suggestion_data": "Alterar dados cadastrais",
+      "suggestion_payment": "Dúvidas sobre pagamento",
+      "suggestion_human": "Falar com atendente",
+      "contact_title": "Fale conosco",
+      "contact_subtitle": "Escolha o canal de atendimento de sua preferência.",
+      "channel_whatsapp": "WhatsApp",
+      "channel_whatsapp_desc": "Atendimento rápido pelo WhatsApp",
+      "channel_email": "E-mail",
+      "channel_email_desc": "Envie sua dúvida por e-mail",
+      "channel_phone": "Telefone",
+      "channel_phone_desc": "Fale diretamente com nossa equipe",
+      "message_placeholder": "Digite sua mensagem...",
+      "footer_disclaimer": "⚡ Respostas automáticas por IA • Atendimento humano disponível",
+      "coming_soon": "Em breve disponível"
     },
     "logout": {
       "title": "Sair",

+ 34 - 4
src/pages/profile/ProfilePage.vue

@@ -46,7 +46,7 @@
         <q-icon name="mdi-chevron-right" color="primary" size="md" />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openAvailabilityDialog">
         <div class="column">
           <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.availability.title') }}</span>
           <span class="menu-description text-text">{{ $t('profile.availability.description') }}</span>
@@ -55,7 +55,7 @@
         <q-icon name="mdi-chevron-right" color="primary" size="md" />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openServiceDataDialog">
         <div class="column">
           <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.service_data.title') }}</span>
           <span class="menu-description text-text">{{ $t('profile.service_data.description') }}</span>
@@ -73,7 +73,7 @@
         <q-icon name="mdi-chevron-right" color="primary" size="md" />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openHelpDialog">
         <div class="column">
           <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.help.title') }}</span>
           <span class="menu-description text-text">{{ $t('profile.help.description') }}</span>
@@ -84,7 +84,7 @@
 
       <q-separator class="q-my-sm bg-grey-3" inset />
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="logOutPrestador">
         <div class="column">
           <span class="menu-title text-weight-bold text-text">{{ $t('profile.logout.title') }}</span>
           <span class="menu-description text-text">{{ $t('profile.logout.description') }}</span>
@@ -99,13 +99,20 @@
 <script setup>
 import { ref, onMounted } from 'vue';
 import { useQuasar } from 'quasar';
+import { useAuth } from 'src/composables/useAuth';
 import { getUser } from 'src/api/user';
 import ProfileEditDialog from './ProfileEditDialog.vue';
 import ProfileAddressDialog from 'src/components/profile/ProfileAddressDialog.vue';
 import ProfileBankDataDialog from 'src/components/profile/ProfileBankDataDialog.vue';
+import ProfileAvailabilityDialog from 'src/components/profile/ProfileAvailabilityDialog.vue';
+import ProfileServiceDataDialog from 'src/components/profile/ProfileServiceDataDialog.vue';
+import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue';
+import { useRouter } from 'vue-router';
 
 const $q = useQuasar();
 
+const { logout } = useAuth();
+const router = useRouter();
 const user = ref({
   name: '',
   email: '',
@@ -123,6 +130,12 @@ const openEditProfile = () => {
   });
 };
 
+const openAvailabilityDialog = () => {
+  $q.dialog({
+    component: ProfileAvailabilityDialog
+  });
+};
+
 const openBankDataDialog = () => {
   $q.dialog({
     component: ProfileBankDataDialog
@@ -135,6 +148,23 @@ const openAddressDialog = () => {
   });
 };
 
+const openServiceDataDialog = () => {
+  $q.dialog({
+    component: ProfileServiceDataDialog
+  });
+};
+
+const openHelpDialog = () => {
+  $q.dialog({
+    component: ProfileHelpDialog
+  });
+};
+
+const logOutPrestador = async () => {
+  await logout();
+  router.push('/login');
+};
+
 onMounted(async () => {
   try {
     const data = await getUser();

Някои файлове не бяха показани, защото твърде много файлове са промени