| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
- <template>
- <q-dialog
- ref="dialogRef"
- maximized
- persistent
- transition-hide="slide-right"
- transition-show="slide-left"
- >
- <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
- class="header-back-btn"
- color="primary"
- dense
- flat
- icon="mdi-chevron-left"
- round
- />
- <q-space />
- <span
- class="text-subtitle1 text-primary font16 fontbold gradient-diarista"
- >
- {{ $t("profile.favorites.title") }}
- </span>
- <q-space />
- <div style="width: 32px" />
- </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
- class="q-mb-lg"
- fit="contain"
- style="width: 220px; height: 220px"
- :src="diarinho"
- />
- <p class="text-text text-center q-mb-xs">
- {{ $t("profile.favorites.empty_message") }}
- </p>
- <p class="text-primary text-center q-mb-xl">
- {{ $t("profile.favorites.empty_cta") }}
- </p>
- <q-btn
- v-close-popup
- class="full-width q-py-sm"
- color="primary"
- no-caps
- padding="10px 16px"
- rounded
- unelevated
- :label="$t('profile.favorites.search_btn')"
- @click="goToSearch"
- />
- <q-btn
- class="full-width q-py-sm q-mt-sm"
- color="primary"
- no-caps
- outline
- padding="10px 16px"
- rounded
- :label="$t('profile.favorites.code_btn')"
- @click="openCodeDialog"
- />
- </div>
- <div
- v-else
- class="col overflow-auto q-pb-xl"
- >
- <div class="q-mx-md q-mt-md q-pa-md">
- <p class="gradient-diarista text-center q-mb-md font12 fontbold">
- {{ $t("profile.favorites.indicate_title") }}
- </p>
- <div class="row justify-center q-mb-md">
- <q-btn
- color="primary"
- dense
- outline
- no-caps
- padding="6px 16px"
- rounded
- size="sm"
- icon="mdi-ticket-confirmation-outline"
- :label="$t('profile.favorites.code_btn')"
- @click="openCodeDialog"
- />
- </div>
- <div class="row items-center">
- <div class="col column items-center">
- <q-avatar
- class="text-h5 q-mb-sm shadow-1"
- size="72px"
- :style="avatarStyle(highlightedFavorite)"
- >
- <img
- v-if="getProfileMediaUrl(highlightedFavorite)"
- class="profile-image"
- :src="getProfileMediaUrl(highlightedFavorite)"
- />
- <span v-else>
- {{
- highlightedFavorite?.provider_name
- ?.charAt(0)
- ?.toUpperCase() ?? "?"
- }}
- </span>
- </q-avatar>
- <span class="font14 fontbold text-dark">
- {{ highlightedFavorite?.provider_name ?? "—" }}
- </span>
- <span
- v-if="getProviderGenderLabel(highlightedFavorite, t)"
- class="font10 fontmedium text-grey-6"
- >
- {{ getProviderGenderLabel(highlightedFavorite, t) }}
- </span>
- <span class="font12 fontmedium text-grey-6">
- {{ highlightedFavorite?.city_name ?? "—" }}
- </span>
- <div class="row items-center q-gutter-sm q-mt-sm">
- <q-btn
- color="primary"
- dense
- flat
- icon="mdi-chevron-left"
- round
- :disable="favorites.length <= 1"
- @click="prevHighlight"
- />
- <q-btn
- color="primary"
- no-caps
- padding="4px 20px"
- rounded
- size="sm"
- unelevated
- :label="$t('profile.favorites.indicate_btn')"
- @click="openIndicateDialog(highlightedFavorite)"
- />
- <q-btn
- color="primary"
- dense
- flat
- icon="mdi-chevron-right"
- round
- :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="font16 fontbold gradient-diarista">
- {{ $t("profile.favorites.providers_title") }}
- </span>
- <div class="row items-center">
- <q-icon
- :class="selectedHours === '2h' ? '' : 'cursor-pointer'"
- :color="selectedHours === '2h' ? 'grey-4' : 'grey-6'"
- name="mdi-chevron-left"
- size="20px"
- @click="nextSelectedHours"
- />
- <span
- class="font12 fontbold text-grey-6 q-px-xs"
- style="user-select: none"
- >
- {{ $t("profile.favorites.until") }} {{ selectedHours }}
- </span>
- <q-icon
- :class="selectedHours === '8h' ? '' : 'cursor-pointer'"
- :color="selectedHours === '8h' ? 'grey-4' : 'grey-6'"
- name="mdi-chevron-right"
- size="20px"
- @click="prevSelectedHours"
- />
- </div>
- </div>
- <div
- v-for="(item, index) in favorites"
- :key="item.id"
- :class="{ 'item-separator': index < favorites.length - 1 }"
- class="favorite-item row items-center no-wrap q-py-md"
- >
- <div
- class="relative-position q-mr-md"
- style="margin-right: 16px"
- >
- <q-avatar
- class="shadow-1"
- size="60px"
- :style="avatarStyle(item)"
- >
- <img
- v-if="getProfileMediaUrl(item)"
- class="profile-image"
- :src="getProfileMediaUrl(item)"
- />
- <span v-else>
- {{ item.provider_name?.charAt(0)?.toUpperCase() ?? "?" }}
- </span>
- </q-avatar>
- <q-icon
- class="absolute cursor-pointer"
- name="mdi-heart"
- size="26px"
- style="bottom: -4px; right: -4px; color: #f518e3; filter: drop-shadow(0 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 font12 fontmedium"
- style="color: #4a4a4a; line-height: 1.1"
- >
- {{ item.provider_name ?? "—" }}
- </span>
- <span
- v-if="getProviderGenderLabel(item, t)"
- class="font9 fontmedium text-grey-7 q-pt-xs"
- >
- {{ getProviderGenderLabel(item, t) }}
- </span>
- <span
- class="provider-city font9 fontbold text-grey-7 q-pt-xs"
- >
- {{ item.city_name ?? "—" }}
- </span>
- </div>
- <span
- class="provider-price font12 fontregular text-grey-7"
- style="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="margin-bottom: 2px"
- >
- <q-icon
- color="warning"
- name="mdi-star"
- size="12px"
- />
- <span
- class="q-ml-xs font9 fontregular"
- style="color: #666"
- >
- {{ item.average_rating ?? "—" }}
- </span>
- <span
- class="q-ml-xs font9 fontregular"
- style="margin-right: 2px"
- >
- ({{ item.total_services ?? 0 }})
- </span>
- <q-icon
- class="q-mx-xs"
- color="grey-4"
- name="mdi-circle-small"
- size="12px"
- />
- <q-icon
- name="mdi-broom"
- size="12px"
- style="color: #f518e3; transform: scaleX(-1)"
- />
- <span
- class="q-ml-xs font9 fontregular"
- style="color: #666"
- >
- {{ item.total_services ?? "0" }}
- </span>
- <q-icon
- class="q-mx-xs"
- color="grey-4"
- name="mdi-circle-small"
- size="16px"
- />
- <span class="font9 fontregular"> 0Km </span>
- </div>
- <q-btn
- class=""
- no-caps
- padding="4px 20px"
- rounded
- size="sm"
- style="background: #a63df7; color: white"
- unelevated
- :label="$t('dashboard_client.favorites.view_schedule')"
- @click="goToScheduling(item)"
- />
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </q-dialog>
- </template>
- <script setup>
- import diarinho from "src/assets/diarinho_perfil_cliente_favoritos.svg";
- import { avatarColorsWithPurple as avatarColors } from "src/helpers/avatarColors";
- import { Share } from "@capacitor/share";
- import { computed, onMounted, ref } from "vue";
- import {
- deleteClientFavoriteProvider,
- getClientFavoriteProviders,
- } from "src/api/clientFavoriteProvider";
- import { getProfileMediaUrl } from "src/helpers/profileMedia";
- import { getProviderGenderLabel } from "src/helpers/provider";
- import { useDialogPluginComponent, useQuasar } from "quasar";
- import { useI18n } from "vue-i18n";
- import { useRouter } from "vue-router";
- import { userStore } from "src/stores/user";
- import ProfileFavoriteCodeDialog from "./ProfileFavoriteCodeDialog.vue";
- import ProfileFavoriteRemoveDialog from "./ProfileFavoriteRemoveDialog.vue";
- import SchedulingDialog from "src/pages/search/components/SchedulingDialog.vue";
- defineEmits([...useDialogPluginComponent.emits]);
- const { dialogRef } = useDialogPluginComponent();
- const { t } = useI18n();
- const $q = useQuasar();
- const router = useRouter();
- const store = userStore();
- const CLIENT_APP_STORE_URL =
- "https://play.google.com/store/apps/details?id=br.com.diarista.client";
- const favorites = ref([]);
- const highlightIndex = ref(0);
- const loading = ref(false);
- const selectedHours = ref("6h");
- const highlightedFavorite = computed(
- () => favorites.value[highlightIndex.value] ?? null,
- );
- const hoursOptions = ["8h", "6h", "4h", "2h"];
- const avatarStyle = (item) => {
- const index = (item?.id ?? 0) % avatarColors.length;
- return avatarColors[index];
- };
- const confirmRemove = (item) => {
- $q.dialog({
- component: ProfileFavoriteRemoveDialog,
- }).onOk(async () => {
- try {
- await deleteClientFavoriteProvider(item.id);
- favorites.value = favorites.value.filter(
- (favorite) => favorite.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 formatPrice = (value) => {
- return Number(value).toLocaleString("pt-BR", {
- maximumFractionDigits: 2,
- minimumFractionDigits: 2,
- });
- };
- const getPriceByHours = (item) => {
- const priceKey = `daily_price_${selectedHours.value}`;
- return item[priceKey] ?? item.daily_price_8h ?? 0;
- };
- const goToSearch = () => {
- router.push({
- name: "SearchPage",
- });
- };
- const nextHighlight = () => {
- highlightIndex.value = (highlightIndex.value + 1) % favorites.value.length;
- };
- const nextSelectedHours = () => {
- const currentIndex = hoursOptions.indexOf(selectedHours.value);
- if (currentIndex < hoursOptions.length - 1) {
- selectedHours.value = hoursOptions[currentIndex + 1];
- }
- };
- const openCodeDialog = () => {
- $q.dialog({
- component: ProfileFavoriteCodeDialog,
- }).onOk(async () => {
- await loadFavorites();
- highlightIndex.value = 0;
- });
- };
- const openIndicateDialog = async (item) => {
- const message = t("profile.favorites.indicate_whatsapp_message", {
- city: item?.city_name ?? "",
- name: item?.provider_name ?? "",
- code: item?.share_code ?? "",
- link: CLIENT_APP_STORE_URL,
- button: t("profile.favorites.code_btn"),
- });
- try {
- await Share.share({
- title: t("profile.favorites.indicate_title"),
- text: message,
- dialogTitle: t("profile.favorites.indicate_btn"),
- });
- } catch (error) {
- if (error?.name === "AbortError") {
- return;
- }
- console.error("Erro ao indicar diarista:", error);
- }
- };
- const prevHighlight = () => {
- highlightIndex.value =
- highlightIndex.value === 0
- ? favorites.value.length - 1
- : highlightIndex.value - 1;
- };
- const prevSelectedHours = () => {
- const currentIndex = hoursOptions.indexOf(selectedHours.value);
- if (currentIndex > 0) {
- selectedHours.value = hoursOptions[currentIndex - 1];
- }
- };
- const loadFavorites = 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;
- }
- };
- const goToScheduling = (provider) => {
- $q.dialog({
- component: SchedulingDialog,
- componentProps: { provider },
- });
- dialogRef.value.hide();
- };
- onMounted(loadFavorites);
- </script>
- <style scoped lang="scss">
- .heart-badge {
- position: absolute;
- bottom: -2px;
- right: -2px;
- background: white;
- border-radius: 50%;
- padding: 1px;
- }
- .favorite-item {
- background: transparent;
- }
- .item-separator {
- border-bottom: 1px solid rgba(0, 0, 0, 0.07);
- }
- .provider-name {
- color: #2c3e50;
- line-height: 1.3;
- }
- .provider-city {
- color: #888;
- line-height: 1.2;
- }
- .provider-metrics {
- gap: 3px;
- }
- .metric-value {
- color: #3a3a4a;
- line-height: 1;
- }
- .metric-meta {
- color: #999;
- line-height: 1;
- }
- .metric-dot {
- color: #ccc;
- line-height: 1;
- padding: 0 1px;
- }
- .provider-price {
- color: #2c3e50;
- }
- .profile-image {
- height: 100%;
- object-fit: cover;
- width: 100%;
- }
- .shadow-profile {
- box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.1);
- }
- </style>
|