|
@@ -0,0 +1,359 @@
|
|
|
|
|
+<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
|
|
|
|
|
+<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.favorites.title') }}</span>
|
|
|
|
|
+ <q-space />
|
|
|
|
|
+ <div style="width: 32px"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="loading" class="col flex flex-center">
|
|
|
|
|
+ <q-spinner color="primary" size="3em" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else-if="favorites.length === 0" class="col column items-center justify-center q-px-xl q-pb-xl">
|
|
|
|
|
+ <q-img
|
|
|
|
|
+ :src="diarinho"
|
|
|
|
|
+ style="width: 220px; height: 220px;"
|
|
|
|
|
+ fit="contain"
|
|
|
|
|
+ class="q-mb-lg"
|
|
|
|
|
+ />
|
|
|
|
|
+ <p class="text-text text-center q-mb-xs" style="font-size: 15px;">
|
|
|
|
|
+ {{ $t('profile.favorites.empty_message') }}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p class="text-primary text-center text-weight-bold q-mb-xl" style="font-size: 15px;">
|
|
|
|
|
+ {{ $t('profile.favorites.empty_cta') }}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-close-popup
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ rounded
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ class="full-width q-py-sm"
|
|
|
|
|
+ padding="10px 16px"
|
|
|
|
|
+ style="font-size: 1rem; font-weight: 700;"
|
|
|
|
|
+ :label="$t('profile.favorites.search_btn')"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="col overflow-auto q-pb-xl">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="q-mx-md q-mt-md q-pa-md">
|
|
|
|
|
+ <p class="text-weight-bold gradient-diarista text-center q-mb-md indicate-title">
|
|
|
|
|
+ {{ $t('profile.favorites.indicate_title') }}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <div class="row items-center">
|
|
|
|
|
+ <div class="col column items-center">
|
|
|
|
|
+ <q-avatar :style="avatarStyle(highlightedFavorite)" size="72px" class="text-weight-bold text-h5 q-mb-sm shadow-1">
|
|
|
|
|
+ {{ highlightedFavorite?.provider_name?.charAt(0)?.toUpperCase() ?? '?' }}
|
|
|
|
|
+ </q-avatar>
|
|
|
|
|
+ <span class="text-weight-bold text-dark" style="font-size: 15px;">{{ highlightedFavorite?.provider_name ?? '—' }}</span>
|
|
|
|
|
+ <span class="text-caption text-grey-6">{{ highlightedFavorite?.city_name ?? '—' }}</span>
|
|
|
|
|
+ <div class="row items-center q-gutter-sm q-mt-sm">
|
|
|
|
|
+ <q-btn flat round dense icon="mdi-chevron-left" color="primary" :disable="favorites.length <= 1" @click="prevHighlight" />
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ rounded
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ padding="4px 20px"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :label="$t('profile.favorites.indicate_btn')"
|
|
|
|
|
+ @click="openIndicateDialog(highlightedFavorite)"
|
|
|
|
|
+ />
|
|
|
|
|
+ <q-btn flat round dense icon="mdi-chevron-right" color="primary" :disable="favorites.length <= 1" @click="nextHighlight" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="q-mx-md q-mt-lg">
|
|
|
|
|
+ <div class="row items-center justify-between q-mb-md">
|
|
|
|
|
+ <span class="text-h6 text-weight-bold gradient-diarista">{{ $t('profile.favorites.providers_title') }}</span>
|
|
|
|
|
+ <div class="row items-center">
|
|
|
|
|
+ <q-icon
|
|
|
|
|
+ name="mdi-chevron-left"
|
|
|
|
|
+ size="20px"
|
|
|
|
|
+ :color="selectedHours === '2h' ? 'grey-4' : 'grey-6'"
|
|
|
|
|
+ :class="selectedHours === '2h' ? '' : 'cursor-pointer'"
|
|
|
|
|
+ @click="nextSelectedHours"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="text-caption text-grey-6 text-weight-medium q-px-xs" style="user-select: none; font-size: 13px;">
|
|
|
|
|
+ {{ $t('profile.favorites.until') }} {{ selectedHours }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <q-icon
|
|
|
|
|
+ name="mdi-chevron-right"
|
|
|
|
|
+ size="20px"
|
|
|
|
|
+ :color="selectedHours === '8h' ? 'grey-4' : 'grey-6'"
|
|
|
|
|
+ :class="selectedHours === '8h' ? '' : 'cursor-pointer'"
|
|
|
|
|
+ @click="prevSelectedHours"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(item, index) in favorites"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ class="favorite-item row items-center no-wrap q-py-md"
|
|
|
|
|
+ :class="{ 'item-separator': index < favorites.length - 1 }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="relative-position q-mr-md" style="margin-right: 16px;">
|
|
|
|
|
+ <q-avatar :style="avatarStyle(item)" size="60px" class="text-weight-bold avatar-font shadow-1">
|
|
|
|
|
+ {{ item.provider_name?.charAt(0)?.toUpperCase() ?? '?' }}
|
|
|
|
|
+ </q-avatar>
|
|
|
|
|
+ <q-icon
|
|
|
|
|
+ name="mdi-heart"
|
|
|
|
|
+ size="26px"
|
|
|
|
|
+ class="absolute cursor-pointer"
|
|
|
|
|
+ style="bottom: -4px; right: -4px; color: #f518e3; filter: drop-shadow(0px 1px 2px rgba(0,0,0,0.2));"
|
|
|
|
|
+ @click.stop="confirmRemove(item)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="col column justify-between" style="min-height: 60px;">
|
|
|
|
|
+ <div class="row items-start justify-between">
|
|
|
|
|
+ <div class="column">
|
|
|
|
|
+ <span class="provider-name text-weight-regular" style="font-size: 16px; color: #4a4a4a; line-height: 1.1;">
|
|
|
|
|
+ {{ item.provider_name ?? '—' }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="provider-city text-grey-6" style="font-size: 13px;">
|
|
|
|
|
+ {{ item.city_name ?? '—' }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="provider-price text-weight-regular text-grey-7" style="font-size: 16px; line-height: 1.1;">
|
|
|
|
|
+ R${{ getPriceByHours(item) ? formatPrice(getPriceByHours(item)) : '—' }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="row items-end justify-between q-mt-xs">
|
|
|
|
|
+ <div class="row items-center text-grey-5" style="font-size: 12px; margin-bottom: 2px;">
|
|
|
|
|
+ <q-icon name="mdi-star" color="warning" size="18px" />
|
|
|
|
|
+ <span class="q-ml-xs text-weight-medium" style="color: #666; font-size: 13px;">{{ item.average_rating ?? '—' }}</span>
|
|
|
|
|
+ <span class="q-ml-xs" style="margin-right: 2px;">({{ item.total_services ?? 0 }})</span>
|
|
|
|
|
+ <q-icon name="mdi-circle-small" color="grey-4" size="16px" class="q-mx-xs" />
|
|
|
|
|
+ <q-icon name="mdi-broom" size="16px" style="transform: scaleX(-1); color: #f518e3;" />
|
|
|
|
|
+ <span class="q-ml-xs text-weight-medium" style="color: #666; font-size: 13px;">{{ item.total_services ?? '0' }}</span>
|
|
|
|
|
+ <q-icon name="mdi-circle-small" color="grey-4" size="16px" class="q-mx-xs" />
|
|
|
|
|
+ <span>0Km</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ rounded
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ style="background: #a63df7; color: white;"
|
|
|
|
|
+ padding="4px 20px"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ class="text-weight-medium"
|
|
|
|
|
+ :label="$t('profile.favorites.schedule_btn')"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </q-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, computed, onMounted } from 'vue';
|
|
|
|
|
+import { useDialogPluginComponent, useQuasar } from 'quasar';
|
|
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
|
|
+import { userStore } from 'src/stores/user';
|
|
|
|
|
+import { getClientFavoriteProviders, deleteClientFavoriteProvider } from 'src/api/clientFavoriteProvider';
|
|
|
|
|
+import ProfileFavoriteRemoveDialog from './ProfileFavoriteRemoveDialog.vue';
|
|
|
|
|
+import diarinho from 'src/assets/diarinho_perfil_cliente_favoritos.svg';
|
|
|
|
|
+
|
|
|
|
|
+defineEmits([...useDialogPluginComponent.emits]);
|
|
|
|
|
+
|
|
|
|
|
+const { dialogRef } = useDialogPluginComponent();
|
|
|
|
|
+const $q = useQuasar();
|
|
|
|
|
+const { t } = useI18n();
|
|
|
|
|
+const store = userStore();
|
|
|
|
|
+
|
|
|
|
|
+const favorites = ref([]);
|
|
|
|
|
+const loading = ref(false);
|
|
|
|
|
+const highlightIndex = ref(0);
|
|
|
|
|
+const selectedHours = ref('8h');
|
|
|
|
|
+
|
|
|
|
|
+const hoursOptions = ['8h', '6h', '4h', '2h'];
|
|
|
|
|
+
|
|
|
|
|
+const prevSelectedHours = () => {
|
|
|
|
|
+ const currentIndex = hoursOptions.indexOf(selectedHours.value);
|
|
|
|
|
+ if (currentIndex > 0) {
|
|
|
|
|
+ selectedHours.value = hoursOptions[currentIndex - 1];
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const nextSelectedHours = () => {
|
|
|
|
|
+ const currentIndex = hoursOptions.indexOf(selectedHours.value);
|
|
|
|
|
+ if (currentIndex < hoursOptions.length - 1) {
|
|
|
|
|
+ selectedHours.value = hoursOptions[currentIndex + 1];
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const avatarColors = [
|
|
|
|
|
+ { background: '#ffd5df', color: '#932e57' },
|
|
|
|
|
+ { background: '#d7e8ff', color: '#2158a8' },
|
|
|
|
|
+ { background: '#dfd', color: '#2a7a3b' },
|
|
|
|
|
+ { background: '#ffe5cc', color: '#8a4500' },
|
|
|
|
|
+ { background: '#ede0ff', color: '#6200ea' },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const avatarStyle = (item) => {
|
|
|
|
|
+ const idx = (item?.id ?? 0) % avatarColors.length;
|
|
|
|
|
+ return avatarColors[idx];
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const formatPrice = (value) => {
|
|
|
|
|
+ return Number(value).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const getPriceByHours = (item) => {
|
|
|
|
|
+ const priceKey = `daily_price_${selectedHours.value}`;
|
|
|
|
|
+ return item[priceKey] ?? item.daily_price_8h ?? 0;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const highlightedFavorite = computed(() => favorites.value[highlightIndex.value] ?? null);
|
|
|
|
|
+
|
|
|
|
|
+const prevHighlight = () => {
|
|
|
|
|
+ highlightIndex.value = highlightIndex.value === 0
|
|
|
|
|
+ ? favorites.value.length - 1
|
|
|
|
|
+ : highlightIndex.value - 1;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const nextHighlight = () => {
|
|
|
|
|
+ highlightIndex.value = (highlightIndex.value + 1) % favorites.value.length;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const confirmRemove = (item) => {
|
|
|
|
|
+ $q.dialog({
|
|
|
|
|
+ component: ProfileFavoriteRemoveDialog,
|
|
|
|
|
+ }).onOk(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await deleteClientFavoriteProvider(item.id);
|
|
|
|
|
+ favorites.value = favorites.value.filter(f => f.id !== item.id);
|
|
|
|
|
+ if (highlightIndex.value >= favorites.value.length) {
|
|
|
|
|
+ highlightIndex.value = Math.max(0, favorites.value.length - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erro ao remover favorito:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const openIndicateDialog = (item) => {
|
|
|
|
|
+ const message = t('profile.favorites.indicate_whatsapp_message', {
|
|
|
|
|
+ name: item?.provider_name ?? '',
|
|
|
|
|
+ city: item?.city_name ?? '',
|
|
|
|
|
+ });
|
|
|
|
|
+ console.log('[Indicar diarista - WhatsApp]', message);
|
|
|
|
|
+ $q.notify({ type: 'info', message: t('profile.favorites.indicate_coming_soon') });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const clientId = store.user?.client_id;
|
|
|
|
|
+ if (clientId) {
|
|
|
|
|
+ favorites.value = await getClientFavoriteProviders(clientId);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erro ao carregar favoritos:', error);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.indicate-title {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.heart-badge {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: -2px;
|
|
|
|
|
+ right: -2px;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ padding: 1px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.avatar-font {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.favorite-item {
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.item-separator {
|
|
|
|
|
+ border-bottom: 1px solid rgba(0, 0, 0, 0.07);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.provider-name {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+ line-height: 1.3;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.provider-city {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ color: #888;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.provider-metrics {
|
|
|
|
|
+ gap: 3px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-value {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #3a3a4a;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-meta {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-dot {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ color: #ccc;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ padding: 0 1px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.provider-price {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.shadow-profile {
|
|
|
|
|
+ box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|