| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
- <template>
- <q-page class="bg-page">
- <div
- class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-search"
- >
- <q-btn
- color="primary"
- dense
- flat
- icon="mdi-chevron-left"
- round
- @click="router.back()"
- />
- <div
- class="col text-center font16 fontbold gradient-diarista text-primary"
- >
- {{ $t("search_page.title") }}
- </div>
- <div style="width: 36px" />
- </div>
- <div class="q-px-md q-pt-md q-pb-sm">
- <q-card
- class="custom-schedule-card bg-surface shadow-card q-pa-sm"
- :flat="false"
- >
- <q-card-section class="row items-center no-wrap q-pa-sm q-gutter-x-sm">
- <span class="col text-text font12 fontmedium fonte-hint">
- {{ $t("search_page.custom_schedule_description") }}
- </span>
- <q-btn
- class="font12 fontmedium custom-schedule-btn card-border"
- color="secondary"
- no-caps
- padding="8px 16px"
- unelevated
- @click="router.push({ name: 'SobMedidaPage' })"
- >
- <template #default>
- <div class="column items-center q-gutter-y-xs">
- <q-icon
- name="mdi-scissors-cutting"
- size="16px"
- />
- <span>{{ $t("search_page.custom_schedule_btn") }}</span>
- </div>
- </template>
- </q-btn>
- </q-card-section>
- </q-card>
- </div>
- <div class="row items-center q-px-md q-py-md q-gutter-x-sm">
- <q-input
- v-model="searchName"
- class="col bg-white search-input"
- clearable
- debounce="400"
- dense
- input-class="text-text"
- outlined
- rounded
- :placeholder="$t('search_page.search_placeholder')"
- @update:model-value="onNameChange"
- >
- <template #append>
- <q-icon
- color="grey-5"
- name="mdi-magnify"
- />
- </template>
- </q-input>
- <q-btn
- :class="{ 'filter-active': activeSort || activeDate }"
- color="grey-6"
- dense
- flat
- icon="mdi-tune-variant"
- round
- size="md"
- @click="openFilterDialog"
- />
- </div>
- <div class="row items-center justify-between no-wrap q-px-md q-pb-sm">
- <div class="dashboard-section-title font16 fontbold gradient-diarista">
- {{ $t("search_page.choose_provider") }}
- </div>
- <div class="row items-center no-wrap text-text">
- <q-btn
- color="text"
- dense
- flat
- icon="mdi-chevron-left"
- round
- size="sm"
- @click="setPeriodTypePrevious"
- />
- <span class="font10 fontbold">{{ periodLabel }}</span>
- <q-btn
- color="text"
- dense
- flat
- icon="mdi-chevron-right"
- round
- size="sm"
- @click="setPeriodTypeNext"
- />
- </div>
- </div>
- <div
- v-if="loading"
- class="row items-center justify-center q-py-xl"
- >
- <q-spinner-dots
- color="primary"
- size="40px"
- />
- </div>
- <template v-else>
- <div
- v-if="sortedProviders.length === 0"
- class="text-center text-grey-6 q-px-md q-py-lg text-body2"
- >
- {{ $t("search_page.no_results") }}
- </div>
- <div
- v-else
- class="column q-px-md q-pb-xl"
- >
- <q-card
- v-for="p in sortedProviders"
- :key="p.provider_id"
- :flat="false"
- class="card-border bg-page text-text q-mb-sm"
- >
- <q-card-section class="row no-wrap q-pa-sm">
- <div class="row no-wrap full-width">
- <div class="col-2">
- <q-avatar
- :style="
- p.profile_media_url
- ? {}
- : avatarColors[p.provider_id % avatarColors.length]
- "
- >
- <img
- v-if="p.profile_media_url"
- :src="p.profile_media_url"
- style="object-fit: cover; width: 100%; height: 100%"
- />
- <span v-else>{{
- getFirstName(p.provider_name)?.slice(0, 1).toUpperCase() ??
- "—"
- }}</span>
- </q-avatar>
- </div>
- <div class="col-10 row">
- <div class="column col-9 justify-between">
- <span class="text-provider-close-nam font12 fontbold">
- {{
- getFirstName(p.provider_name) || "Prestador"
- }}
- </span>
- <span
- v-if="getProviderGenderLabel(p, t)"
- class="font9 fontmedium text-grey-7"
- >
- {{ getProviderGenderLabel(p, t) }}
- </span>
- <span
- class="text-provider-close-region font9 fontbold text-grey-7"
- >
- {{ p.district }}
- </span>
- <div class="row items-center justify-between q-pr-lg">
- <div class="row items-center">
- <q-icon
- color="warning"
- name="mdi-star"
- size="12px"
- />
- <span class="text-provider-close-rating font9 fontmedium">
- {{
- p.average_rating != null
- ? Number(p.average_rating).toFixed(1) +
- " (" +
- (p.total_reviews ?? 0) +
- ")"
- : "(" + (p.total_reviews ?? 0) + ")"
- }}
- </span>
- </div>
- <div class="row items-center">
- <q-icon
- color="secondary"
- name="mdi-broom"
- size="12px"
- />
- <span class="text-provider-close-jobs font9 fontmedium">
- {{ p.total_services ?? 0 }}
- </span>
- </div>
- <div class="row items-center">
- <q-icon
- color="text"
- name="mdi-map-marker-outline"
- size="12px"
- />
- <span class="text-provider-close-jobs font9 fontmedium">
- {{ (p.distance_km ?? "--") + " km" }}
- </span>
- </div>
- </div>
- </div>
- <div
- class="column col-3 justify-between text-center items-center"
- >
- <span class="text-provider-close-price font12 fontbold">
- {{ priceByPeriod(p) }}
- </span>
- <div class="full-width">
- <q-btn
- color="primary"
- no-caps
- padding="3px 12px"
- rounded
- size="sm"
- unelevated
- :label="$t('search_page.schedule_btn')"
- @click="goToScheduling(p)"
- />
- </div>
- </div>
- </div>
- </div>
- </q-card-section>
- </q-card>
- </div>
- </template>
- </q-page>
- </template>
- <script setup>
- import { avatarColors } from "src/helpers/avatarColors";
- import { buscaPrestadores } from "src/api/dashboard";
- import { computed, onMounted, ref } from "vue";
- import { formatCurrency, getFirstName } from "src/helpers/utils";
- import { getProviderGenderLabel } from "src/helpers/provider";
- import { useI18n } from "vue-i18n";
- import { useQuasar } from "quasar";
- import { useRouter } from "vue-router";
- import SchedulingDialog from "src/pages/search/components/SchedulingDialog.vue";
- import SearchFilterDialog from "src/pages/search/components/SearchFilterDialog.vue";
- const { t } = useI18n();
- const router = useRouter();
- const $q = useQuasar();
- const activeDate = ref(null);
- const activeSort = ref(null);
- const allProviders = ref([]);
- const currentPeriodType = ref(8);
- const loading = ref(true);
- const searchName = ref("");
- const periodTypeMap = {
- 2: "daily_price_2h",
- 4: "daily_price_4h",
- 6: "daily_price_6h",
- 8: "daily_price_8h",
- };
- const periodLabel = computed(() => {
- const labels = {
- 8: t("search_page.until_8h"),
- 6: t("search_page.until_6h"),
- 4: t("search_page.until_4h"),
- 2: t("search_page.until_2h"),
- };
- return labels[currentPeriodType.value] ?? "";
- });
- const sortedProviders = computed(() => {
- const list = [...allProviders.value];
- const priceKey = periodTypeMap[currentPeriodType.value];
- switch (activeSort.value) {
- case "price_asc":
- return list.sort(
- (a, b) => Number(a[priceKey] ?? 0) - Number(b[priceKey] ?? 0),
- );
- case "price_desc":
- return list.sort(
- (a, b) => Number(b[priceKey] ?? 0) - Number(a[priceKey] ?? 0),
- );
- case "rating_desc":
- return list.sort(
- (a, b) => Number(b.average_rating ?? 0) - Number(a.average_rating ?? 0),
- );
- case "rating_asc":
- return list.sort(
- (a, b) => Number(a.average_rating ?? 0) - Number(b.average_rating ?? 0),
- );
- case "reviews_desc":
- return list.sort(
- (a, b) => Number(b.total_reviews ?? 0) - Number(a.total_reviews ?? 0),
- );
- case "reviews_asc":
- return list.sort(
- (a, b) => Number(a.total_reviews ?? 0) - Number(b.total_reviews ?? 0),
- );
- case "services_desc":
- return list.sort(
- (a, b) => Number(b.total_services ?? 0) - Number(a.total_services ?? 0),
- );
- case "services_asc":
- return list.sort(
- (a, b) => Number(a.total_services ?? 0) - Number(b.total_services ?? 0),
- );
- case "oldest":
- return list.sort(
- (a, b) => new Date(a.created_at) - new Date(b.created_at),
- );
- case "newest":
- return list.sort(
- (a, b) => new Date(b.created_at) - new Date(a.created_at),
- );
- default:
- return list.sort(
- (a, b) => Number(b.average_rating ?? 0) - Number(a.average_rating ?? 0),
- );
- }
- });
- // eslint-disable-next-line no-unused-vars
- const formatDistance = (distance) => {
- if (distance === null || distance === undefined || distance === "")
- return "—";
- const numericDistance = Number(distance);
- if (!Number.isFinite(numericDistance)) return "—";
- return `${numericDistance.toLocaleString("pt-BR", { maximumFractionDigits: 1 })} km`;
- };
- const goToScheduling = (provider) => {
- $q.dialog({
- component: SchedulingDialog,
- componentProps: { provider },
- });
- };
- const loadProviders = async () => {
- loading.value = true;
- try {
- allProviders.value =
- (await buscaPrestadores({
- date: activeDate.value ?? "",
- name: searchName.value,
- })) ?? [];
- } catch {
- allProviders.value = [];
- } finally {
- loading.value = false;
- }
- };
- const onNameChange = () => {
- loadProviders();
- };
- const openFilterDialog = () => {
- $q.dialog({
- component: SearchFilterDialog,
- componentProps: {
- initialDate: activeDate.value,
- initialSort: activeSort.value,
- },
- }).onOk(({ date, sort }) => {
- const dateChanged = date !== activeDate.value;
- activeDate.value = date;
- activeSort.value = sort;
- if (dateChanged) loadProviders();
- });
- };
- const priceByPeriod = (provider) => {
- const key = periodTypeMap[currentPeriodType.value];
- return provider[key]
- ? formatCurrency(provider[key])
- : t("search_page.no_price");
- };
- const setPeriodTypeNext = () => {
- const next = currentPeriodType.value + 2;
- if (periodTypeMap[next]) currentPeriodType.value = next;
- };
- const setPeriodTypePrevious = () => {
- const previous = currentPeriodType.value - 2;
- if (periodTypeMap[previous]) currentPeriodType.value = previous;
- };
- onMounted(loadProviders);
- </script>
- <style scoped lang="scss">
- .shadow-search {
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
- }
- .search-input {
- :deep(.q-field__control) {
- border-radius: 28px;
- }
- }
- .custom-schedule-card {
- border-radius: 12px;
- }
- .custom-schedule-btn {
- flex-shrink: 0;
- min-width: 72px;
- }
- .filter-active {
- color: var(--q-primary) !important;
- }
- .fonte-hint {
- line-height: 100%;
- letter-spacing: -0.04em;
- vertical-align: middle;
- }
- </style>
|