|
|
@@ -0,0 +1,339 @@
|
|
|
+<template>
|
|
|
+ <div class="q-pa-md">
|
|
|
+
|
|
|
+ <div v-if="loading" class="flex flex-center q-py-xl">
|
|
|
+ <q-spinner color="primary" size="48px" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-else-if="filteredItems.length === 0"
|
|
|
+ class="text-center text-grey q-py-xl"
|
|
|
+ >
|
|
|
+ Nenhum produto encontrado
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else class="row q-col-gutter-md items-stretch">
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-for="item in filteredItems"
|
|
|
+ :key="item.id"
|
|
|
+ class="col-12 col-sm-6 col-md-4"
|
|
|
+ >
|
|
|
+
|
|
|
+ <q-card flat bordered class="store-card">
|
|
|
+
|
|
|
+ <q-card-section class="q-pa-sm">
|
|
|
+
|
|
|
+ <div class="row no-wrap store-card-inner">
|
|
|
+
|
|
|
+ <!-- LEFT -->
|
|
|
+ <div class="col column justify-between q-pr-sm">
|
|
|
+
|
|
|
+ <div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="text-subtitle1 text-weight-bold text-violet-medium q-mb-xs"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="item.description"
|
|
|
+ class="text-caption text-grey-7 q-mb-sm ellipsis-2-lines"
|
|
|
+ >
|
|
|
+ {{ item.description }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- VARIACOES -->
|
|
|
+ <template v-if="item.variations?.length">
|
|
|
+
|
|
|
+ <div class="text-caption text-grey-6 q-mb-xs">
|
|
|
+ {{ variationTypeLabel(item) }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="row q-gutter-xs q-mb-sm">
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-for="v in item.variations"
|
|
|
+ :key="v.id"
|
|
|
+ :class="[
|
|
|
+ 'variation-tag',
|
|
|
+ activeVariation(item)?.id === v.id
|
|
|
+ ? 'variation-tag--selected'
|
|
|
+ : 'variation-tag--default'
|
|
|
+ ]"
|
|
|
+ @click="selectVariation(item, v)"
|
|
|
+ >
|
|
|
+ {{ v.variation_label }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </template>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- PRECO -->
|
|
|
+ <div class="column items-start q-mt-sm">
|
|
|
+
|
|
|
+ <span
|
|
|
+ v-if="item.price"
|
|
|
+ class="text-caption text-grey-5 text-strike"
|
|
|
+ >
|
|
|
+ R$ {{ formatPrice(item.price) }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <span
|
|
|
+ class="text-subtitle1 text-weight-bold text-violet-dark"
|
|
|
+ >
|
|
|
+ R$ {{ formatPrice(displayPrice(item)) }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- RIGHT -->
|
|
|
+ <div class="store-card-right column items-stretch">
|
|
|
+
|
|
|
+ <div class="store-card-image">
|
|
|
+
|
|
|
+ <img
|
|
|
+ v-if="item.media?.length"
|
|
|
+ :src="item.media[0].url"
|
|
|
+ :alt="item.name"
|
|
|
+ class="store-card-img"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-icon
|
|
|
+ v-else
|
|
|
+ name="mdi-image-off-outline"
|
|
|
+ size="32px"
|
|
|
+ color="grey-4"
|
|
|
+ class="absolute-center"
|
|
|
+ />
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- BOTAO -->
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ size="sm"
|
|
|
+ class="btn-gradient q-mt-xs"
|
|
|
+ :color="item.user_interested ? 'negative' : 'primary'"
|
|
|
+ :icon="
|
|
|
+ item.user_interested
|
|
|
+ ? 'mdi-heart-remove'
|
|
|
+ : 'mdi-heart-outline'
|
|
|
+ "
|
|
|
+ :label="
|
|
|
+ item.user_interested
|
|
|
+ ? 'Remover Interesse'
|
|
|
+ : 'Demonstrar Interesse'
|
|
|
+ "
|
|
|
+ @click="toggleInterest(item)"
|
|
|
+ />
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </q-card-section>
|
|
|
+
|
|
|
+ </q-card>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed, onMounted } from "vue";
|
|
|
+import { useQuasar } from "quasar";
|
|
|
+
|
|
|
+import {
|
|
|
+ getStoreItems,
|
|
|
+ toggleInterest as apiToggleInterest,
|
|
|
+} from "src/api/storeItem";
|
|
|
+
|
|
|
+const $q = useQuasar();
|
|
|
+
|
|
|
+const items = ref([]);
|
|
|
+const loading = ref(true);
|
|
|
+
|
|
|
+const selectedVariations = ref({});
|
|
|
+
|
|
|
+const filteredItems = computed(() => items.value);
|
|
|
+
|
|
|
+const selectVariation = (item, variation) => {
|
|
|
+ selectedVariations.value[item.id] = variation;
|
|
|
+};
|
|
|
+
|
|
|
+const activeVariation = (item) =>
|
|
|
+ selectedVariations.value[item.id] ?? item.variations?.[0] ?? null;
|
|
|
+
|
|
|
+const displayPrice = (item) => {
|
|
|
+ const v = activeVariation(item);
|
|
|
+
|
|
|
+ if (v?.variation_value != null) {
|
|
|
+ return v.variation_value;
|
|
|
+ }
|
|
|
+
|
|
|
+ return item.associate_price ?? item.price;
|
|
|
+};
|
|
|
+
|
|
|
+const variationTypeLabel = (item) => {
|
|
|
+ const type = item.variations?.[0]?.variation_type;
|
|
|
+
|
|
|
+ if (type === "tamanho") return "Tamanho";
|
|
|
+ if (type === "cor") return "Cor";
|
|
|
+ if (type === "modelo") return "Modelo";
|
|
|
+
|
|
|
+ return "";
|
|
|
+};
|
|
|
+
|
|
|
+const formatPrice = (price) =>
|
|
|
+ Number(price).toLocaleString("pt-BR", {
|
|
|
+ minimumFractionDigits: 2,
|
|
|
+ });
|
|
|
+
|
|
|
+const toggleInterest = async (item) => {
|
|
|
+ try {
|
|
|
+ const result = await apiToggleInterest(item.id);
|
|
|
+
|
|
|
+ item.user_interested = result.interested;
|
|
|
+
|
|
|
+ item.interests_count += result.interested ? 1 : -1;
|
|
|
+
|
|
|
+ $q.notify({
|
|
|
+ type: "positive",
|
|
|
+ message: result.interested
|
|
|
+ ? "Interesse demonstrado!"
|
|
|
+ : "Interesse removido!",
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch {
|
|
|
+ $q.notify({
|
|
|
+ type: "negative",
|
|
|
+ message: "Erro ao processar interesse",
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ try {
|
|
|
+ const response = await getStoreItems();
|
|
|
+
|
|
|
+ items.value = response;
|
|
|
+
|
|
|
+ response.forEach((item) => {
|
|
|
+ if (item.variations?.length) {
|
|
|
+ selectedVariations.value[item.id] = item.variations[0];
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.store-card {
|
|
|
+ transition: 0.2s;
|
|
|
+ border-radius: 12px;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ box-shadow: 0 4px 16px rgba(102, 29, 117, 0.12);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.store-card-inner {
|
|
|
+ min-height: 140px;
|
|
|
+}
|
|
|
+
|
|
|
+.store-card-right {
|
|
|
+ width: 110px;
|
|
|
+ min-width: 110px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.store-card-image {
|
|
|
+ position: relative;
|
|
|
+ height: 110px;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #f5f0f7;
|
|
|
+ border: 1px solid rgba(102, 29, 117, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.store-card-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.variation-tag {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ min-width: 28px;
|
|
|
+ height: 26px;
|
|
|
+
|
|
|
+ padding: 0 7px;
|
|
|
+
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ transition: 0.15s;
|
|
|
+
|
|
|
+ &--default {
|
|
|
+ background: #ede0f5;
|
|
|
+ color: #4d1658;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--selected {
|
|
|
+ background: #4d1658;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-gradient {
|
|
|
+ background: linear-gradient(
|
|
|
+ 90deg,
|
|
|
+ #4d1658 0%,
|
|
|
+ #8b30a5 100%
|
|
|
+ ) !important;
|
|
|
+
|
|
|
+ color: white !important;
|
|
|
+
|
|
|
+ border-radius: 8px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.text-violet-dark {
|
|
|
+ color: #4d1658;
|
|
|
+}
|
|
|
+
|
|
|
+.text-violet-medium {
|
|
|
+ color: #7b2d97;
|
|
|
+}
|
|
|
+
|
|
|
+.ellipsis-2-lines {
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+</style>
|