wishlists
This commit is contained in:
19
src/App.vue
19
src/App.vue
@@ -3,15 +3,16 @@
|
||||
<div id="app" class="flex flex-col h-screen bg-gray-100">
|
||||
<Toast/>
|
||||
<!-- MenuBar всегда фиксирован сверху -->
|
||||
<MenuBar v-if="userStore.user" class="w-full sticky hidden lg:block top-0 z-10"/>
|
||||
<ToolBar class=" fixed visible lg:invisible bottom-0 z-10"/>
|
||||
<MenuBar v-if="userStore.user && !route.path.startsWith('/mywishlist')" class="w-full sticky hidden lg:block top-0 z-10"/>
|
||||
<ToolBar v-if="userStore.user && !route.path.startsWith('/mywishlist')" class=" fixed visible lg:invisible bottom-0 z-10"/>
|
||||
|
||||
<!-- Контентная часть заполняет оставшееся пространство -->
|
||||
<div class="flex flex-col flex-grow">
|
||||
<!-- {{ tg_id }}-->
|
||||
<Button label="Sub" :class="checkNotif ? 'flex' : '!hidden'" @click="checkSubscribe"/>
|
||||
<!-- <Button label="Sub" :class="checkNotif ? 'flex' : '!hidden'" @click="checkSubscribe"/>-->
|
||||
|
||||
<router-view/>
|
||||
|
||||
<div class="bg-gray-100 h-12 block lg:hidden"></div>
|
||||
</div>
|
||||
|
||||
@@ -25,12 +26,13 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="grid grid-cols-2 sm:flex sm:flex-row w-full gap-1">
|
||||
<div class="grid grid-cols-2 sm:flex sm:flex-row w-full gap-2">
|
||||
<router-link to="/about" class="hover:underline">О проекте</router-link>
|
||||
<router-link to="/spaces" class="hover:underline">Пространства</router-link>
|
||||
<router-link to="/analytics" class="hover:underline">Аналитика</router-link>
|
||||
<router-link to="/budgets" class="hover:underline">Бюджеты</router-link>
|
||||
<router-link to="/transactions" class="hover:underline">Транзакции</router-link>
|
||||
<router-link to="/wishlists" class="hover:underline">Вишлисты</router-link>
|
||||
<router-link to="/settings" class="hover:underline">Настройки</router-link>
|
||||
|
||||
</div>
|
||||
@@ -64,8 +66,12 @@ import {useDrawerStore} from '@/stores/drawerStore'
|
||||
import TransactionForm from "@/components/transactions/TransactionForm.vue";
|
||||
import {useSpaceStore} from "@/stores/spaceStore";
|
||||
import Toast from "primevue/toast";
|
||||
import {useRoute} from "vue-router";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
const drawerStore = useDrawerStore();
|
||||
|
||||
const visible = computed(() => drawerStore.visible);
|
||||
@@ -130,6 +136,11 @@ onMounted(async () => {
|
||||
await spaceStore.fetchSpaces()
|
||||
}
|
||||
|
||||
// document.cookie = `aid=${crypto.randomUUID()}`
|
||||
if (!Cookies.get("aid")) {
|
||||
Cookies.set("aid", crypto.randomUUID(), { expires: 36500, path: "/" })
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import ProgressSpinner from "primevue/progressspinner";
|
||||
|
||||
const props = defineProps({
|
||||
halfscreen: Boolean ,
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-screen">
|
||||
<div class="relative w-full " :class="!props.halfscreen ? 'h-screen' : 'h-80'">
|
||||
<!-- Полупрозрачный белый фон -->
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-100 z-0"></div>
|
||||
|
||||
@@ -16,7 +18,7 @@ import ProgressSpinner from "primevue/progressspinner";
|
||||
style="width: 50px; height: 50px;"
|
||||
strokeWidth="8"
|
||||
fill="transparent"
|
||||
animationDuration=".5s"
|
||||
animationDuration="1s"
|
||||
aria-label="Custom ProgressSpinner"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- fill="var(--p-text-color)"-->
|
||||
<!-- />-->
|
||||
<!-- </svg>-->
|
||||
<img alt="logo" src="/apple-touch-icon.png" width="32" height="32"/>
|
||||
<button @click="router.push('/').then(router.go(0))"><img alt="logo" src="/apple-touch-icon.png" width="32" height="32"/></button>
|
||||
</template>
|
||||
<template #item="{ item, props, hasSubmenu, root }">
|
||||
<router-link :to="item.url" v-ripple class="flex items-center" v-bind="props.action">
|
||||
@@ -145,9 +145,14 @@ const items = ref([
|
||||
},
|
||||
{
|
||||
label: 'Транзакции',
|
||||
icon: "pi pi-star",
|
||||
icon: "pi pi-dollar",
|
||||
url: '/transactions'
|
||||
},
|
||||
{
|
||||
label: 'Вишлисты',
|
||||
icon: "pi pi-star",
|
||||
url: '/wishlists'
|
||||
},
|
||||
{
|
||||
label: 'Настройки',
|
||||
icon: 'pi pi-envelope',
|
||||
|
||||
@@ -32,7 +32,14 @@
|
||||
|
||||
<div class="flex flex-col gap-2 p-2">
|
||||
<router-link to="/transactions" class="items-center flex flex-col gap-2">
|
||||
<i class="pi pi-wallet text-2xl" style="font-size: 1rem"></i>
|
||||
<i class="pi pi-dollar text-2xl" style="font-size: 1rem"></i>
|
||||
<p>Транзакции</p>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 p-2">
|
||||
<router-link to="/transactions" class="items-center flex flex-col gap-2">
|
||||
<i class="pi pi-star text-2xl" style="font-size: 1rem"></i>
|
||||
<p>Транзакции</p>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
@@ -310,7 +310,7 @@ onMounted(async () => {
|
||||
<!-- «Растяжка», чтобы было за что «скроллить» -->
|
||||
<div class="min-w-[550px] md:min-w-[650px] lg:min-w-[850px]">
|
||||
<Chart
|
||||
type="line"
|
||||
type="bar"
|
||||
:data="preparedChartData"
|
||||
:options="chartOptions"
|
||||
class="h-72 sm:h-full sm:w-full "
|
||||
|
||||
@@ -106,7 +106,7 @@ const login = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await userStore.login(username.value, password.value)
|
||||
toast.add({ severity: 'success', summary: 'Успешный вход', detail: 'Добро пожаловать!', life: 3000 })
|
||||
// toast.add({ severity: 'success', summary: 'Успешный вход', detail: 'Добро пожаловать!', life: 3000 })
|
||||
// await router.push('/')
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: 'Ошибка входа', detail: 'Неверные данные', life: 3000 })
|
||||
|
||||
192
src/components/onboarding/OnboardingView.vue
Normal file
192
src/components/onboarding/OnboardingView.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, nextTick, ref} from "vue";
|
||||
import Stepper from "primevue/stepper";
|
||||
import StepList from "primevue/steplist";
|
||||
import Step from "primevue/step";
|
||||
import StepPanel from "primevue/steppanel"
|
||||
import Button from "primevue/button";
|
||||
|
||||
import SpaceCreationFormView from "@/components/spaces/SpaceCreationFormView.vue";
|
||||
import {Category, CategoryType} from "@/models/Category";
|
||||
import {deleteCategoryRequest, getCategories, getCategoryTypes} from "@/services/categoryService";
|
||||
import CategoryListItem from "@/components/settings/categories/CategoryListItem.vue";
|
||||
import {useSpaceStore} from "@/stores/spaceStore";
|
||||
// import BudgetCreationView from "@/components/budgets/BudgetCreationView.vue";
|
||||
import RecurrentListItem from "@/components/settings/recurrent/RecurrentListItem.vue";
|
||||
import RecurrentCreationView from "@/components/settings/recurrent/RecurrentCreationView.vue";
|
||||
import {RecurrentPayment} from "@/models/Recurrent";
|
||||
import {getRecurrentPayments} from "@/services/recurrentService";
|
||||
import router from "@/router";
|
||||
import CreateCategoryModal from "@/components/settings/categories/CreateCategoryModal.vue";
|
||||
import {useConfirm} from "primevue/useconfirm";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
|
||||
const stepperValue = ref("1")
|
||||
|
||||
const space = computed(() => {
|
||||
return spaceStore.space
|
||||
})
|
||||
const spaceStore = useSpaceStore()
|
||||
|
||||
const spaceCreated = async (createdSpace) => {
|
||||
spaceStore.setSpace(createdSpace)
|
||||
console.log(space.value)
|
||||
await fetchCategories()
|
||||
stepperValue.value = '2'
|
||||
}
|
||||
|
||||
const categories = ref<Category[]>([])
|
||||
const categoryTypes = ref<CategoryType[]>([]);
|
||||
|
||||
const fetchCategories = async () => {
|
||||
if (space.value) {
|
||||
await getCategories().then((res) => {
|
||||
categories.value = res.data
|
||||
})
|
||||
await getCategoryTypes().then((res) => {
|
||||
categoryTypes.value = res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const showCreateCategoryModal = ref(false)
|
||||
|
||||
const recurrentCreated = async () => {
|
||||
await fetchRecurrents()
|
||||
}
|
||||
|
||||
const recurrents = ref<RecurrentPayment[]>()
|
||||
const fetchRecurrents = async () => {
|
||||
await getRecurrentPayments().then((res) => {
|
||||
recurrents.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class=" flex justify-center bg-gray-100 h-full">
|
||||
<div class="flex flex-col bg-white !h-fit p-4 rounded-xl m-4 w-5/6 lg:w-3/6">
|
||||
<Stepper :value="stepperValue" class="b ">
|
||||
<StepList>
|
||||
<Step value="1">Пространства</Step>
|
||||
<Step value="2">Категории</Step>
|
||||
<Step value="3">Ежемесячные платежи</Step>
|
||||
<Step value="4">Бюджет</Step>
|
||||
</StepList>
|
||||
<StepPanels>
|
||||
<StepPanel v-slot="{ activateCallback }" value="1" class="">
|
||||
<div class="p-2">
|
||||
<h1 class="text-xl font-light">Привет!</h1>
|
||||
<p class="text-sm text-gray-500">В первую очередь для работы с Luminic Space нужно создать свое
|
||||
пространство.</p>
|
||||
<p class="text-sm text-gray-500">Пространство - это обособленное место, где идет хранение информации.</p>
|
||||
<p class="text-sm text-gray-500">Твои транзакции, бюджеты, категории и прочие вещи хранятся в границах
|
||||
одного пространства.</p>
|
||||
<p class="text-sm text-gray-500">Пространство может быть твоим личным, либо ты можешь пригласить в него
|
||||
доверенных тебе людей.</p>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
|
||||
<SpaceCreationFormView opened @space-created="spaceCreated"/>
|
||||
</div>
|
||||
<!-- <div class="flex pt-6 justify-end ">-->
|
||||
<!-- <!– <Button label="Back" severity="secondary" icon="pi pi-arrow-left" @click="activateCallback('1')" />–>-->
|
||||
<!-- <Button label="Пропустить" icon="pi pi-arrow-right" severity="secondary" iconPos="right"-->
|
||||
<!-- @click="activateCallback('2')"/>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <div class="flex flex-col h-48">-->
|
||||
<!-- <div class="border-2 border-dashed border-surface-200 dark:border-surface-700 rounded bg-surface-50 dark:bg-surface-950 flex-auto flex justify-center items-center font-medium">Content I</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="flex pt-6 justify-end">-->
|
||||
<!-- </div>-->
|
||||
</StepPanel>
|
||||
<StepPanel v-slot="{ activateCallback }" value="2">
|
||||
<div class="p-2">
|
||||
<p v-if="!space">Нет пространства - нет категорий. Возвращайся и создавай пространство!</p>
|
||||
<div v-else>
|
||||
<h1 class="text-xl font-light">Теперь - категории!</h1>
|
||||
<p class="text-sm text-gray-500">Если ты указал создание категорий на предыдущем этапе - этот можно
|
||||
пропустить или просмотреть созданные категории и отредактировать их.</p>
|
||||
<p class="text-sm text-gray-500">Категория - логическое разделение трат. Они могут быть 2 типов:
|
||||
Поступления и расходы. Категории так же могут иметь тэги. Самые важные из них - loans и savings. На
|
||||
основе них идет расчет сумм по долгам и сбережениям на странице бюджета, но об этом позже</p>
|
||||
<!-- <p class="text-sm text-gray-500">Твои транзакции, бюджеты, категории и прочие вещи хранятся в границах одного пространства.</p>-->
|
||||
<!-- <p class="text-sm text-gray-500">Пространство может быть твоим личным, либо ты можешь пригласить в него доверенных тебе людей.</p>-->
|
||||
</div>
|
||||
</div>
|
||||
<!-- <button @click="showCreateCategoryModal=true">hui</button>-->
|
||||
<div class="flex flex-col gap-2">
|
||||
<Button label="Добавить категорию" icon="pi pi-plus" class="text-sm"
|
||||
@click="showCreateCategoryModal=true"/>
|
||||
|
||||
<CreateCategoryModal v-if="showCreateCategoryModal" :show="showCreateCategoryModal"
|
||||
:category-types="categoryTypes" :show-tags="false" @save-category="fetchCategories"
|
||||
@close-modal="showCreateCategoryModal=false"
|
||||
/>
|
||||
<div class="grid lg:grid-cols-2 gap-2">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex pt-6 justify-between">
|
||||
<Button label="Вернуться" severity="secondary" icon="pi pi-arrow-left" @click="activateCallback('1')"/>
|
||||
<Button label="Пропустить" icon="pi pi-arrow-right" iconPos="right" @click="stepperValue='3'"/>
|
||||
</div>
|
||||
</StepPanel>
|
||||
<StepPanel v-slot="{ activateCallback }" value="3">
|
||||
<div class="p-2">
|
||||
<h1 class="text-xl font-light">Теперь давай заполним твои ежемесячные платежи</h1>
|
||||
<p class="text-sm text-gray-500">Ежемесячные платежи - это такие платежи, которые повторяются раз из раза
|
||||
каждый месяц: кредит, кварплата, арендная плата и так далее.</p>
|
||||
<p class="text-sm text-gray-500">Каждый месяц первого числа мы создадим плановые транзакции по твоим
|
||||
повторяющимся платежам. </p>
|
||||
<!-- <p class="text-sm text-gray-500">Твои транзакции, бюджеты, категории и прочие вещи хранятся в границах одного пространства.</p>-->
|
||||
<!-- <p class="text-sm text-gray-500">Пространство может быть твоим личным, либо ты можешь пригласить в него доверенных тебе людей.</p>-->
|
||||
</div>
|
||||
<div class="grid lg:grid-cols-1 gap-2">
|
||||
<RecurrentCreationView v-if="stepperValue=='3'"
|
||||
:expense-categories="categories.filter(cat => cat.type.code=='EXPENSE')"
|
||||
:income-categories="categories.filter(cat => cat.type.code=='INCOME')"
|
||||
:category-types="categoryTypes" @save-payment="recurrentCreated"/>
|
||||
</div>
|
||||
<div class="grid lg:grid-cols-2 gap-2">
|
||||
<RecurrentListItem v-for="recurrent in recurrents" :payment="recurrent"/>
|
||||
</div>
|
||||
<div class="pt-6 flex justify-between w-full">
|
||||
<Button label="Назад" severity="secondary" icon="pi pi-arrow-left" @click="activateCallback('2')"/>
|
||||
<Button label="Дальше" icon="pi pi-arrow-right" iconPos="right" @click="stepperValue='4'"/>
|
||||
</div>
|
||||
</StepPanel>
|
||||
<StepPanel v-slot="{ activateCallback }" value="4">
|
||||
<div class="p-2">
|
||||
<h1 class="text-xl font-light">Теперь можно создать и твой первый бюджет!</h1>
|
||||
<p class="text-sm text-gray-500">Бюджет - объединение общей информации о твоих плановых и фактических
|
||||
транзакциях, о категориях и лимитах по ним и другое.</p>
|
||||
<p class="text-sm text-gray-500">Транзакции не привязаны к бюджету. Бюджет учитывает любые транзакции,
|
||||
дата которых входит в период действия бюджета.</p>
|
||||
<!-- <p class="text-sm text-gray-500">Твои транзакции, бюджеты, категории и прочие вещи хранятся в границах одного пространства.</p>-->
|
||||
<!-- <p class="text-sm text-gray-500">Пространство может быть твоим личным, либо ты можешь пригласить в него доверенных тебе людей.</p>-->
|
||||
</div>
|
||||
<!-- <BudgetCreationView opened @budgetCreated="router.push('/budgets').then((res) => router.go(0))"/>-->
|
||||
<div class="pt-6 flex justify-between w-full">
|
||||
<Button label="Назад" severity="secondary" icon="pi pi-arrow-left" @click="activateCallback('3')"/>
|
||||
<Button label="В работу!" icon="pi pi-arrow-right" iconPos="right"
|
||||
@click="router.push('/budgets').then(router.go(0))"/>
|
||||
</div>
|
||||
</StepPanel>
|
||||
</StepPanels>
|
||||
</Stepper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -268,16 +268,11 @@ onMounted(async () => {
|
||||
:key="user.id"
|
||||
@mouseover="user.isHovered = true"
|
||||
@mouseleave="user.isHovered = false">
|
||||
<div
|
||||
|
||||
class="relative flex bg-emerald-300 rounded-full w-10 h-10 items-center justify-center"
|
||||
|
||||
>
|
||||
<div class="relative flex bg-emerald-300 rounded-full w-10 h-10 items-center justify-center">
|
||||
<!-- Первая буква имени -->
|
||||
<span class="text-white text-center">
|
||||
{{ user.firstName.substring(0, 1) }}
|
||||
</span>
|
||||
|
||||
<!-- Иконка короны для владельца -->
|
||||
<i
|
||||
v-if="space.owner.id === user.id"
|
||||
|
||||
@@ -116,7 +116,8 @@ const selectedSpace = computed(() => spaceStore.space)
|
||||
|
||||
watch(selectedSpace, async (newValue, oldValue) => {
|
||||
if (newValue != oldValue) {
|
||||
await fetchTransactions(false)
|
||||
transactions.value = [];
|
||||
await fetchTransactions(true)
|
||||
}
|
||||
})
|
||||
const types = ref([])
|
||||
@@ -183,7 +184,7 @@ onUnmounted(async () => {
|
||||
@transaction-updated="fetchTransactions(true)"
|
||||
@delete-transaction="fetchTransactions(true)"
|
||||
/>
|
||||
<div class="flex items-center justify-center px-2 py-1 mb-5">
|
||||
<div v-if="!loading" class="flex items-center justify-center px-2 py-1 mb-5">
|
||||
<Button @click="fetchTransactions(false)">Загрузить следующие...</Button>
|
||||
</div>
|
||||
<!-- Показать спиннер загрузки, если идет загрузка -->
|
||||
|
||||
174
src/components/wishlists/WishListListView.vue
Normal file
174
src/components/wishlists/WishListListView.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import router from "@/router";
|
||||
import {formatDate} from "@/utils/utils";
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import StatusView from "@/components/StatusView.vue";
|
||||
import BudgetCreationView from "@/components/budgets/BudgetCreationView.vue";
|
||||
import Button from "primevue/button";
|
||||
import ConfirmDialog from "primevue/confirmdialog";
|
||||
import Toast from "primevue/toast";
|
||||
import Dialog from "primevue/dialog";
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import {WishList} from "@/models/WishList";
|
||||
import {deleteWishlistRequest, getWishlists} from "@/services/WishListService";
|
||||
import {useSpaceStore} from "@/stores/spaceStore";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
import WishlistCreationView from "@/components/wishlists/WishlistCreationView.vue";
|
||||
import {useConfirm} from "primevue/useconfirm";
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm();
|
||||
|
||||
|
||||
const loading = ref(true)
|
||||
const creationOpened = ref(false);
|
||||
|
||||
const editingWishlist = ref();
|
||||
|
||||
const wishlists = ref<WishList[]>([]);
|
||||
const fetchWishlists = async () => {
|
||||
wishlists.value = await getWishlists()
|
||||
}
|
||||
|
||||
const wishlistCreated = async () => {
|
||||
creationOpened.value = false;
|
||||
await fetchWishlists();
|
||||
}
|
||||
|
||||
const wishlistCreationCanceled = () => {
|
||||
creationOpened.value = false
|
||||
}
|
||||
|
||||
const deleteWishlist = async (wishlist: WishList) => {
|
||||
confirm.require({
|
||||
message: `Вы действительно хотите удалить вишлист ${wishlist.name} ?`,
|
||||
header: 'Удаление вишлиста',
|
||||
icon: 'pi pi-info-circle',
|
||||
rejectLabel: 'Отмена',
|
||||
rejectProps: {
|
||||
label: 'Отмена',
|
||||
severity: 'secondary',
|
||||
outlined: true
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'Удалить',
|
||||
severity: 'danger'
|
||||
},
|
||||
accept: async () => {
|
||||
try {
|
||||
await deleteWishlistRequest(wishlist.id)
|
||||
await fetchWishlists()
|
||||
toast.add({severity: 'success', summary: 'Успешно!', detail: 'Вишлист удален!', life: 3000});
|
||||
} catch (e: Error) {
|
||||
toast.add({severity: 'error', summary: "Ошибка при удалении", detail: e.response.data.message, life: 3000});
|
||||
}
|
||||
},
|
||||
reject: () => {
|
||||
toast.add({severity: 'info', summary: 'Отменено', detail: 'Вы отменили удаление', life: 3000});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const spaceStore = useSpaceStore()
|
||||
const selectedSpace = computed(() => spaceStore.space)
|
||||
|
||||
watch(
|
||||
() => selectedSpace.value,
|
||||
async (newValue, oldValue) => {
|
||||
|
||||
if (newValue != oldValue || !oldValue) {
|
||||
try {
|
||||
loading.value = true;
|
||||
// Если выбранный space изменился, получаем новую информацию о бюджете
|
||||
await fetchWishlists();
|
||||
loading.value = false;
|
||||
} catch (error) {
|
||||
console.error('Error fetching wishlists infos:', error);
|
||||
toast.add({severity: 'error', summary: 'Ошибка получения вишлистов', detail: error.response.data.message});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
if (selectedSpace.value) {
|
||||
loading.value = true;
|
||||
await fetchWishlists();
|
||||
loading.value = false;
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoadingView v-if="loading"/>
|
||||
<div v-else class="p-4 bg-gray-100 h-full flex flex-col gap-4 ">
|
||||
<Dialog :visible="creationOpened" modal :header="editingWishlist ? 'Изменить вишлист' : 'Создать новый вишлист'" :style="{ width: '25rem' }" @hide="wishlistCreationCanceled"
|
||||
@update:visible="wishlistCreationCanceled">
|
||||
<WishlistCreationView :editing-wishlist="editingWishlist" @wishlist-created="wishlistCreated" @cancel-creation="wishlistCreationCanceled" />
|
||||
</Dialog>
|
||||
|
||||
<!-- Заголовок -->
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<h2 class="text-4xl font-bold">Вишлисты</h2>
|
||||
<Button label="+ Создать" @click="creationOpened=true" size="small"/>
|
||||
<!-- <StatusView :show="creationSuccessModal" :is-error="false" :message="'Бюджет создан!'"/>-->
|
||||
</div>
|
||||
<!-- Плитка с бюджетами -->
|
||||
<div v-if="!selectedSpace" class="flex w-full h-full items-center justify-center">
|
||||
<p>Сперва нужно выбрать Пространство.
|
||||
<button class="text-blue-500 hover:underline" @click="router.push('/spaces').then((res) => router.go(0))">
|
||||
Перейти
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="wishlists.length==0" class="flex w-full h-full items-center justify-center">
|
||||
<p>Кажется, в этом пространстве еще нет вишлистов
|
||||
<button class="text-blue-500 hover:underline" @click="creationOpened=true">создайте один.</button>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Будущие и текущие бюджеты -->
|
||||
<ConfirmDialog/>
|
||||
<Toast/>
|
||||
|
||||
<div v-for="wishlist in wishlists" :key="wishlist.id" class="p-4 shadow-lg rounded-lg bg-white">
|
||||
<div class="flex flex-row justify-between gap-4">
|
||||
<div class="flex flex-col justify-between gap-5">
|
||||
<router-link :to="'/wishlists/'+wishlist.id">
|
||||
<div class="text-xl font-bold ">{{ wishlist.name }}</div>
|
||||
</router-link>
|
||||
<div class="text-sm font-light ">{{ wishlist.description }}</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<div class="relative flex bg-emerald-300 rounded-full w-10 h-10 items-center justify-center">
|
||||
<!-- Первая буква имени -->
|
||||
<span class="text-white text-center">{{ wishlist.owner.firstName.substring(0, 1) }}</span>
|
||||
</div>
|
||||
<div class="text-lg font-semibold">{{ wishlist.owner.firstName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-between items-end gap-4">
|
||||
<div class="flex flex-row gap-2">
|
||||
<button @click="editingWishlist=wishlist;creationOpened=true"><i class="pi pi-pen-to-square" style="font-size: 1rem"/></button>
|
||||
<router-link :to="'/wishlists/'+wishlist.id">
|
||||
<i class="pi pi-arrow-circle-right " style=""/>
|
||||
</router-link>
|
||||
|
||||
</div>
|
||||
<button @click="deleteWishlist(wishlist)"><i class="pi pi-trash" /></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
85
src/components/wishlists/WishlistCreationView.vue
Normal file
85
src/components/wishlists/WishlistCreationView.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Checkbox from "primevue/checkbox";
|
||||
import Textarea from "primevue/textarea";
|
||||
import InputText from "primevue/inputtext";
|
||||
import Button from "primevue/button";
|
||||
import FloatLabel from "primevue/floatlabel";
|
||||
import {WishList} from "@/models/WishList";
|
||||
import {reactive, ref} from "vue";
|
||||
import {createWishlistRequest, updateWishlistRequest} from "@/services/WishListService";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
|
||||
const toast = useToast()
|
||||
const props = defineProps({
|
||||
editingWishlist: Object
|
||||
})
|
||||
|
||||
const emits = defineEmits(['wishlist-created', "cancel-creation"])
|
||||
|
||||
const wishlist = ref<WishList>(props.editingWishlist ? props.editingWishlist : new WishList())
|
||||
|
||||
|
||||
|
||||
const createWishlist = async () => {
|
||||
if (!props.editingWishlist) {
|
||||
|
||||
|
||||
await createWishlistRequest(wishlist.value)
|
||||
.then((res) => {
|
||||
emits("wishlist-created", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка создания вишлиста',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
} else {
|
||||
await updateWishlistRequest(wishlist.value)
|
||||
.then((res) => {
|
||||
emits("wishlist-created", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка изменения вишлиста',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const cancelCreation = () => {
|
||||
emits("cancel-creation")
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col gap-4 mt-1">
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="name">Название</label>
|
||||
<InputText v-model="wishlist.name" id="name" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="name">Описание</label>
|
||||
<Textarea v-model="wishlist.description" id="name" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<div class="flex flex-row gap-2 justify-end items-center">
|
||||
<Button label="Создать" severity="success" icon="pi pi-save" @click="createWishlist"/>
|
||||
<Button label="Отмена" severity="secondary" icon="pi pi-times-circle" @click="cancelCreation"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
217
src/components/wishlists/WishlistExternalView.vue
Normal file
217
src/components/wishlists/WishlistExternalView.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {formatAmount} from "@/utils/utils";
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {cancelReserveWishlistItem, getWishlistExternal, reserveWishlistItem} from "@/services/WishListService";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
import {WishList, WishlistItem} from "@/models/WishList";
|
||||
import Button from "primevue/button";
|
||||
import Image from "primevue/image";
|
||||
import InputText from "primevue/inputtext";
|
||||
import FloatLabel from "primevue/floatlabel";
|
||||
import Dialog from "primevue/dialog";
|
||||
|
||||
import apiClient from "@/services/axiosSetup";
|
||||
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
|
||||
|
||||
const aidCookie = Cookies.get("aid");
|
||||
const loading = ref(true);
|
||||
const wishlist = ref<WishList>()
|
||||
const selectedImage = reactive(new Map<string, string>())
|
||||
const selectedReserveItem = ref()
|
||||
const reserveModalShow = ref(false)
|
||||
const reservedBy = ref(Cookies.get("name") ? Cookies.get("name") : null);
|
||||
const reserveItem = async () => {
|
||||
Cookies.set("name", reservedBy.value, { expires: 36500, path: "/" })
|
||||
await reserveWishlistItem(wishlist.value?.id, selectedReserveItem.value.id, reservedBy.value, aidCookie)
|
||||
.then(async (res) => {
|
||||
reserveModalShow.value = false
|
||||
selectedReserveItem.value = false
|
||||
// reservedBy.value = null
|
||||
await fetchWishlist()
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Успех!',
|
||||
detail: 'Успешно забронировано',
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка резервации',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const cancelReserve = async (item) => {
|
||||
await cancelReserveWishlistItem(wishlist.value?.id, item.id, reservedBy.value, aidCookie)
|
||||
.then(async (res) => {
|
||||
reserveModalShow.value = false
|
||||
selectedReserveItem.value = false
|
||||
await fetchWishlist()
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Успех!',
|
||||
detail: 'Бронь снята',
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка резервации',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const fetchWishlist = async () => {
|
||||
await getWishlistExternal(route.params.id)
|
||||
.then((res) => {
|
||||
wishlist.value = res
|
||||
wishlist.value?.items.forEach((item: WishlistItem) => {
|
||||
selectedImage.set(item.id, item.images[0])
|
||||
})
|
||||
loading.value = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка получения вишлиста',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchWishlist();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoadingView v-if="loading"/>
|
||||
<div v-else class="p-4 bg-gray-100 h-full flex flex-col gap-10">
|
||||
<Dialog :visible="reserveModalShow" header="Резерв желания"
|
||||
@hide="reserveModalShow = false; selectedReserveItem=null" modal
|
||||
@update:visible="reserveModalShow = false;selectedReserveItem=null" class="!w-2/6 ">
|
||||
<div class="flex flex-col gap-4 mt-1">
|
||||
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="link">Ваше имя</label>
|
||||
<InputText id="link" type="text" v-model="reservedBy" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<div class="flex flex-row w-full justify-between">
|
||||
<Button label="Сохранить" @click="reserveItem"/>
|
||||
<Button label="Отмена" @click="reserveModalShow=false;selectedReserveItem=null;reservedBy=null"
|
||||
severity="secondary"/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<div class="flex flex-row items-center gap-2 min-w-fit">
|
||||
<img alt="logo" src="/apple-touch-icon.png" width="48" height="48"/>
|
||||
<div class="flex flex-col items-start">
|
||||
<p class="text-xl font-bold">Luminic Space</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class=" flex flex-col gap-3 justify-between h-full ">
|
||||
<div class="flex flex-col justify-between gap-5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2 items-end">
|
||||
<h2 class="text-4xl font-bold text-gray-700">Вишлист {{ wishlist.name }} </h2>
|
||||
</div>
|
||||
<p class="text-lg text-gray-500">{{ wishlist.description }}</p>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<div class="relative flex bg-emerald-300 rounded-full w-10 h-10 items-center justify-center">
|
||||
<!-- Первая буква имени -->
|
||||
<span class="text-white text-center">{{ wishlist.owner.firstName.substring(0, 1) }}</span>
|
||||
</div>
|
||||
<div class="text-lg font-semibold">{{ wishlist.owner.firstName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-4">
|
||||
<p class="text-2xl text-gray-700">Желания</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-2">
|
||||
<div v-for="item in wishlist.items"
|
||||
class=" bg-white flex flex-col p-4 flex-shrink-0 gap-4 shadow-md rounded-lg max-w-128 h-full">
|
||||
<div class="flex flex-col w-full justify-center gap-2">
|
||||
<div class="flex w-full justify-center">
|
||||
<Image
|
||||
:src="selectedImage.get(item.id).startsWith('http') ? selectedImage.get(item.id) : apiClient.defaults.baseURL+'/'+selectedImage.get(item.id) "
|
||||
alt="Image"
|
||||
width="128" height="128" show="show" preview
|
||||
imageClass="h-64 w-64 object-cover items-center justify-center justify-items-center"/>
|
||||
</div>
|
||||
<div class="flex flex-row !h-12 gap-2">
|
||||
|
||||
<div v-for="(image, index) in item.images" class="group relative h-12 w-12 rounded-lg shadow-md ">
|
||||
<button @click="selectedImage.set(item.id, image)">
|
||||
<Image
|
||||
:src="image.startsWith('http') ? image : apiClient.defaults.baseURL+'/'+image " alt="Image"
|
||||
width="48" height="48" show="show"
|
||||
imageClass="w-12 h-12 object-cover"/>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between gap-2 h-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row w-full justify-between">
|
||||
<p class="font-semibold text-xl text-gray-700">{{ item.name }}</p>
|
||||
</div>
|
||||
<p class="font-bold text-lg text-emerald-700">{{ formatAmount(item.price) }} ₽</p>
|
||||
<p class="font-light text-gray-700 text-wrap">{{ item.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 ">
|
||||
<a :href="item.link"
|
||||
target="_blank" class="w-fit">
|
||||
<Button label="В магазин" icon="pi pi-arrow-up-right" iconPos="right"/>
|
||||
</a>
|
||||
<Button
|
||||
:label="!item.reservedBy ? 'Я беру!' : item.reservedBy.aid != aidCookie ? 'Забронировано.' : 'Отменить'"
|
||||
:severity="item.reservedBy && item.reservedBy.aid == aidCookie ? 'danger' : 'secondary'"
|
||||
:disabled="item.reservedBy && aidCookie != item.reservedBy.aid ? true : false"
|
||||
@click="item.reservedBy && item.reservedBy.aid == aidCookie ? cancelReserve(item): reserveModalShow=true;selectedReserveItem=item"
|
||||
v-tooltip="item.reservedBy ? 'Зарезервированно за ' + item.reservedBy.name : ''"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.flex-row > Image {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.p-image-preview-mask {
|
||||
position: absolute !important;
|
||||
}
|
||||
</style>
|
||||
125
src/components/wishlists/WishlistItemCreationView.vue
Normal file
125
src/components/wishlists/WishlistItemCreationView.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Textarea from "primevue/textarea";
|
||||
import InputText from "primevue/inputtext";
|
||||
import Button from "primevue/button";
|
||||
import FloatLabel from "primevue/floatlabel";
|
||||
import {WishlistItem} from "@/models/WishList";
|
||||
import {ref} from "vue";
|
||||
import InputGroup from "primevue/inputgroup";
|
||||
import InputNumber from "primevue/inputnumber";
|
||||
import InputGroupAddon from "primevue/inputgroupaddon";
|
||||
import {addWishListItemRequest} from "@/services/WishListService";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps({
|
||||
wishlistId: String,
|
||||
editItem: Object,
|
||||
})
|
||||
|
||||
const emits = defineEmits(["item-created", "creation-cancelled"])
|
||||
const isEditing = ref(!!props.editItem)
|
||||
const item = ref(props.editItem ? props.editItem : new WishlistItem())
|
||||
const inputNameRef = ref()
|
||||
const inputDescriptionRef = ref()
|
||||
const inputPriceRef = ref()
|
||||
const inputLinkRef = ref()
|
||||
|
||||
const checkForm = () => {
|
||||
console.log(inputNameRef.value)
|
||||
if (!item.value.name || item.value.name.length === 0) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка создания желания',
|
||||
detail: "Название должно быть введено",
|
||||
life: 3000
|
||||
})
|
||||
return false
|
||||
} else if (!item.value.description || item.value.description.length === 0) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка создания желания',
|
||||
detail: "Описание должно быть введено",
|
||||
life: 3000
|
||||
})
|
||||
return false
|
||||
} else if (!item.value.link || item.value.link.length === 0) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка создания желания',
|
||||
detail: "Ссылка должна быть введена",
|
||||
life: 3000
|
||||
})
|
||||
return false
|
||||
} else if (!item.value.price || item.value.price == 0) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка создания желания',
|
||||
detail: "Цена должна быть введена",
|
||||
life: 3000
|
||||
})
|
||||
return false
|
||||
} else return true
|
||||
}
|
||||
const itemCreate = async () => {
|
||||
if (checkForm()) {
|
||||
await addWishListItemRequest(props.wishlistId, item.value)
|
||||
.then(res => {
|
||||
emits("item-created", res)
|
||||
resetForm()
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка создания желания',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
const cancelCreation = () => {
|
||||
resetForm()
|
||||
emits("creation-cancelled")
|
||||
}
|
||||
const resetForm = () => {
|
||||
item.value = new WishlistItem()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col gap-4 mt-1">
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="name">Название</label>
|
||||
<InputText :ref="inputNameRef" v-model="item.name" id="name" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="name">Описание</label>
|
||||
<Textarea :ref="inputDescriptionRef" v-model="item.description" id="name" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="name">Ссылка на товар</label>
|
||||
<InputText :ref="inputLinkRef" v-model="item.link" id="name" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<!-- Сумма -->
|
||||
<InputGroup class="w-full">
|
||||
<InputGroupAddon>₽</InputGroupAddon>
|
||||
<InputNumber :ref="inputPriceRef" v-model="item.price" placeholder="Сумма"/>
|
||||
<InputGroupAddon>.00</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<div class="flex flex-row gap-2 justify-end items-center">
|
||||
<Button label="Создать" severity="success" icon="pi pi-save" @click="itemCreate"/>
|
||||
<Button label="Отмена" severity="secondary" icon="pi pi-times-circle" @click="cancelCreation"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
462
src/components/wishlists/WishlistView.vue
Normal file
462
src/components/wishlists/WishlistView.vue
Normal file
@@ -0,0 +1,462 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {formatAmount} from "@/utils/utils";
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import {computed, onMounted, reactive, ref, watch} from "vue";
|
||||
import {
|
||||
cancelReserveWishlistItem,
|
||||
deleteWishListItemRequest,
|
||||
deleteWishlistRequest,
|
||||
getWishlist,
|
||||
updateWishListItemRequest
|
||||
} from "@/services/WishListService";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
import {useSpaceStore} from "@/stores/spaceStore";
|
||||
import {WishList, WishlistItem} from "@/models/WishList";
|
||||
import Button from "primevue/button";
|
||||
import Image from "primevue/image";
|
||||
import Dialog from "primevue/dialog";
|
||||
import Divider from "primevue/divider";
|
||||
import FileUpload from 'primevue/fileupload';
|
||||
import {uploadStatic} from "@/services/StaticService";
|
||||
import apiClient from "@/services/axiosSetup";
|
||||
import WishlistItemCreationView from "@/components/wishlists/WishlistItemCreationView.vue";
|
||||
import InputText from "primevue/inputtext";
|
||||
import FloatLabel from "primevue/floatlabel";
|
||||
import ConfirmDialog from "primevue/confirmdialog";
|
||||
import {useConfirm} from "primevue/useconfirm";
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
|
||||
|
||||
const loading = ref(true);
|
||||
const wishlist = ref<WishList>()
|
||||
const selectedImage = reactive(new Map<string, string>())
|
||||
const hoveredCancelReservationButton = reactive(new Map<string, boolean>())
|
||||
|
||||
const fetchWishlist = async () => {
|
||||
await getWishlist(route.params.id)
|
||||
.then((res) => {
|
||||
wishlist.value = res
|
||||
wishlist.value?.items.forEach((item: WishlistItem) => {
|
||||
selectedImage.set(item.id, item.images[0])
|
||||
})
|
||||
shareLink.value = window.location.origin + "/mywishlist/" + wishlist.value?.id
|
||||
loading.value = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка получения вишлиста',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const wishlistItemCreationOpened = ref(false);
|
||||
const itemCreated = () => {
|
||||
wishlistItemCreationOpened.value = false;
|
||||
fetchWishlist();
|
||||
}
|
||||
const editingItem = ref()
|
||||
|
||||
const imageUploadVisible = ref(false);
|
||||
const imageUploadForItemId = ref()
|
||||
|
||||
const fileupload = ref()
|
||||
const onFileSelect = (event) => {
|
||||
fileupload.value = event.files[0];
|
||||
}
|
||||
const upload = async () => {
|
||||
const item = wishlist.value.items.filter((item) => item.id == imageUploadForItemId.value)[0]
|
||||
if (fileUploadLink.value) {
|
||||
item.images.push(fileUploadLink.value)
|
||||
imageUploadVisible.value = false
|
||||
await updateWishListItemRequest(wishlist.value.id, item)
|
||||
.then(async (res) => {
|
||||
await fetchWishlist();
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка сохранения желания ',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
|
||||
} else {
|
||||
await uploadStatic(item.id, fileupload.value).then(async (res) => {
|
||||
item.images.push(res)
|
||||
imageUploadVisible.value = false
|
||||
await updateWishListItemRequest(wishlist.value.id, item)
|
||||
.then(async (res) => {
|
||||
await fetchWishlist();
|
||||
fileupload.value = null
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка сохранения желания ',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка загрузки файла',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
const deleteImage = async (item: WishlistItem, index: number) => {
|
||||
item.images.splice(index, 1)
|
||||
await updateWishListItemRequest(wishlist.value.id, item)
|
||||
.then(async (res) => {
|
||||
await fetchWishlist();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка загрузки файла',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const deleteItem = async (item: WishlistItem) => {
|
||||
console.log("here")
|
||||
confirm.require({
|
||||
message: `Вы действительно хотите удалить желание ${item.name} ?`,
|
||||
header: 'Удаление вишлиста',
|
||||
icon: 'pi pi-info-circle',
|
||||
rejectLabel: 'Отмена',
|
||||
rejectProps: {
|
||||
label: 'Отмена',
|
||||
severity: 'secondary',
|
||||
outlined: true
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'Удалить',
|
||||
severity: 'danger'
|
||||
},
|
||||
accept: async () => {
|
||||
await deleteWishListItemRequest(wishlist.value.id, item)
|
||||
.then(async (res) => {
|
||||
await fetchWishlist();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка удаление желания',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
reject: () => {
|
||||
toast.add({severity: 'info', summary: 'Отменено', detail: 'Вы отменили удаление', life: 3000});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
const cancelReserve = async (item: WishlistItem) => {
|
||||
console.log("here")
|
||||
confirm.require({
|
||||
message: `Вы действительно хотите отменить бронь у желания ${item.name} ?`,
|
||||
header: 'Отмена брони',
|
||||
icon: 'pi pi-info-circle',
|
||||
rejectLabel: 'Отмена',
|
||||
rejectProps: {
|
||||
label: 'Отмена',
|
||||
severity: 'secondary',
|
||||
outlined: true
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'Отменить',
|
||||
severity: 'danger'
|
||||
},
|
||||
accept: async () => {
|
||||
await cancelReserveWishlistItem(wishlist.value.id, item.id)
|
||||
.then(async (res) => {
|
||||
await fetchWishlist();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка отмены бронирования',
|
||||
detail: err.response.data.message,
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
reject: () => {
|
||||
toast.add({severity: 'info', summary: 'Отменено', detail: 'Вы отменили удаление', life: 3000});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
const shareDialogOpened = ref(false)
|
||||
const shareLink = ref()
|
||||
|
||||
const copied = ref(false);
|
||||
|
||||
const copyToClipboard = async (text) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
copied.value = true;
|
||||
// setTimeout(() => copied.value = false, 1500); // Убираем сообщение через 1.5 сек
|
||||
} catch (err) {
|
||||
console.error('Ошибка копирования:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const spaceStore = useSpaceStore()
|
||||
const selectedSpace = computed(() => spaceStore.space)
|
||||
const fileUploadLink = ref()
|
||||
|
||||
|
||||
watch(
|
||||
() => selectedSpace.value,
|
||||
async (newValue, oldValue) => {
|
||||
|
||||
if (newValue != oldValue || !oldValue) {
|
||||
try {
|
||||
loading.value = true;
|
||||
// Если выбранный space изменился, получаем новую информацию о бюджете
|
||||
await fetchWishlist()
|
||||
loading.value = false;
|
||||
} catch (error) {
|
||||
console.error('Error fetching wishlists infos:', error);
|
||||
toast.add({severity: 'error', summary: 'Ошибка получения вишлиста', detail: error.response.data.message});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
if (selectedSpace.value) {
|
||||
loading.value = true;
|
||||
await fetchWishlist();
|
||||
loading.value = false;
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoadingView v-if="loading"/>
|
||||
<div v-else class="p-4 bg-gray-100 h-full ">
|
||||
<ConfirmDialog/>
|
||||
<Dialog :visible="imageUploadVisible" header="Загрузка изображения"
|
||||
@hide="imageUploadVisible = false; fileUploadLink=null" modal
|
||||
@update:visible="imageUploadVisible = false;fileUploadLink=null" class="w-5/6 md:!w-2/6">
|
||||
<div class="flex flex-col gap-2 m-1">
|
||||
<FileUpload v-if="!fileUploadLink" accept="image/*/" @select="onFileSelect" :maxFileSize="5*1024*1024"
|
||||
cancelLabel="cancel"
|
||||
mode="basic" name="demo[]"
|
||||
chooseLabel="Browse"/>
|
||||
<Divider v-if="!fileUploadLink && !fileupload">или укажите ссылку</Divider>
|
||||
<FloatLabel v-if="!fileupload" variant="on" class="w-full">
|
||||
<label for="link">Ссылка</label>
|
||||
<InputText id="link" v-model="fileUploadLink" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<div class="flex justify-center">
|
||||
<Image v-if="fileUploadLink" :src="fileUploadLink" width="240" preview class="w-fit"/>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 w-full justify-center">
|
||||
<Button label="Загрузить" @click="upload"/>
|
||||
<Button label="Сбросить" @click="fileUploadLink=null;fileupload=null" severity="secondary"/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<Dialog :visible="wishlistItemCreationOpened" modal
|
||||
:header=" editingItem ? 'Редактирование желания':'Создание нового желаемого'"
|
||||
@hide="editingItem = null;wishlistItemCreationOpened = false"
|
||||
@update:visible="editingItem = null;wishlistItemCreationOpened = false"
|
||||
class="w-5/6 md:!w-2/6">
|
||||
<WishlistItemCreationView :editItem="editingItem" :wishlistId="wishlist.id" @item-created="itemCreated"
|
||||
@creation-cancelled="editingItem = null;wishlistItemCreationOpened = false"/>
|
||||
</Dialog>
|
||||
<Dialog :visible="shareDialogOpened" header="Поделиться" @hide="shareDialogOpened = false"
|
||||
@update:visible="shareDialogOpened=false" modal>
|
||||
<div class="flex flex-row gap-2 w-full justify-center">
|
||||
<FloatLabel variant="on" class="w-full mt-1">
|
||||
<label for="link">Ссылка</label>
|
||||
<InputText id="link" v-model="shareLink" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<button @click="copyToClipboard(shareLink)">
|
||||
{{ !copied ? 'Копировать' : 'Скопировано!' }}
|
||||
</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<div class=" flex flex-col gap-3 justify-between h-full ">
|
||||
<div class="flex flex-col justify-between gap-5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col md:flex-row gap-2 items-start md:items-end">
|
||||
<h2 class="text-4xl font-bold text-gray-700">Вишлист {{ wishlist.name }} </h2>
|
||||
<button @click="shareDialogOpened=true"><span class="text-sm text-gray-600">Поделиться <i
|
||||
class="pi pi-arrow-up-right" style="font-size: 0.65rem"/></span></button>
|
||||
</div>
|
||||
<!-- <div class="flex flex-row gap-2 text-xl text-gray-700">{{ formatDate(budget.dateFrom) }} - -->
|
||||
<!-- {{ formatDate(budget.dateTo) }}-->
|
||||
<!-- </div>-->
|
||||
<p class="text-lg text-gray-500">{{ wishlist.description }}</p>
|
||||
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<div class="relative flex bg-emerald-300 rounded-full w-10 h-10 items-center justify-center">
|
||||
<!-- Первая буква имени -->
|
||||
<span class="text-white text-center">{{ wishlist.owner.firstName.substring(0, 1) }}</span>
|
||||
</div>
|
||||
<div class="text-lg font-semibold">{{ wishlist.owner.firstName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-4">
|
||||
<p class="text-2xl text-gray-700">Желания</p>
|
||||
<Button label="+ Создать" @click="wishlistItemCreationOpened=true" size="small"/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-2">
|
||||
<div v-for="item in wishlist.items"
|
||||
class=" bg-white flex flex-col p-4 flex-shrink-0 gap-4 shadow-md rounded-lg max-w-128 h-full">
|
||||
<!-- <div v-if="true" >-->
|
||||
<!-- <LoadingView :halfscreen="true"/>-->
|
||||
<!-- </div>-->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col w-full justify-center gap-2">
|
||||
<div v-if="item.images.length > 0 && selectedImage.get(item.id)" class="flex w-full justify-center">
|
||||
<Image
|
||||
:src="selectedImage.get(item.id).startsWith('http') ? selectedImage.get(item.id) : apiClient.defaults.baseURL+'/'+selectedImage.get(item.id) "
|
||||
alt="Image"
|
||||
width="128" height="128" show="show" preview
|
||||
imageClass="h-64 w-64 object-cover items-center justify-center justify-items-center"/>
|
||||
</div>
|
||||
<div class="flex flex-row !h-12 gap-2">
|
||||
|
||||
<div v-for="(image, index) in item.images" class="group relative h-12 w-12 rounded-lg shadow-md ">
|
||||
<button @click="selectedImage.set(item.id, image)">
|
||||
<Image
|
||||
:src="image.startsWith('http') ? image : apiClient.defaults.baseURL+'/'+image " alt="Image"
|
||||
width="48" height="48" show="show"
|
||||
imageClass="w-12 h-12 object-cover"/>
|
||||
</button>
|
||||
<!-- Иконка для удаления -->
|
||||
<button @click="deleteImage(item, index)">
|
||||
<i class="pi pi-minus absolute -top-2 right-0 text-red-400 z-10 bg-white rounded-full p-[0.2rem] border-2 !hidden group-hover:!block "
|
||||
style="font-size: 0.6rem"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-gray-100 rounded flex items-center justify-center group">
|
||||
<div
|
||||
class="rounded-full w-9 h-9 bg-gray-400 opacity-30 group-hover:opacity-70 flex items-center justify-center">
|
||||
<button @click="imageUploadVisible=true;imageUploadForItemId=item.id">
|
||||
<i class="pi pi-plus !font-bold !text-xl text-white"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-between gap-2 h-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row w-full justify-between">
|
||||
<p class="font-semibold text-xl text-gray-700">{{ item.name }}</p>
|
||||
<div class="flex flex-row gap-2">
|
||||
<button @click="editingItem=item;wishlistItemCreationOpened=true"><i
|
||||
class="pi pi-pen-to-square"/>
|
||||
</button>
|
||||
<button @click="deleteItem(item)"><i class="pi pi-trash"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="font-bold text-lg text-emerald-700">{{ formatAmount(item.price) }} ₽</p>
|
||||
<p class="font-light text-gray-700 text-wrap">{{ item.description }}</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="flex flex-row !h-12 gap-2">-->
|
||||
<!-- <div v-for="(image, index) in item.images" class="group relative h-12 w-12 rounded-lg shadow-md ">-->
|
||||
<!-- <Image-->
|
||||
<!-- :src="image.startsWith('http') ? image : apiClient.defaults.baseURL+'/'+image " alt="Image"-->
|
||||
<!-- width="48" height="48" show="show" preview-->
|
||||
<!-- imageClass="w-12 h-12 object-cover"/>-->
|
||||
<!-- <!– Иконка для удаления –>-->
|
||||
<!-- <button @click="deleteImage(item, index)">-->
|
||||
<!-- <i class="pi pi-minus absolute -top-2 right-0 text-red-400 z-10 bg-white rounded-full p-[0.2rem] border-2 !hidden group-hover:!block "-->
|
||||
<!-- style="font-size: 0.6rem"-->
|
||||
<!-- />-->
|
||||
<!-- </button>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="w-12 h-12 bg-gray-100 rounded flex items-center justify-center group">-->
|
||||
<!-- <div-->
|
||||
<!-- class="rounded-full w-9 h-9 bg-gray-400 opacity-30 group-hover:opacity-70 flex items-center justify-center">-->
|
||||
<!-- <button @click="imageUploadVisible=true;imageUploadForItemId=item.id">-->
|
||||
<!-- <i class="pi pi-plus !font-bold !text-xl text-white"/>-->
|
||||
<!-- </button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="grid grid-cols-2 w-full justify-between items-center">
|
||||
<a :href="item.link"
|
||||
target="_blank">
|
||||
<Button label="В магазин" icon="pi pi-arrow-up-right" iconPos="right"/>
|
||||
</a>
|
||||
<span v-if="!item.reservedBy" class="text-gray-500">Не забронировано</span>
|
||||
<Button v-else @mouseover="hoveredCancelReservationButton.set(item.id, true)"
|
||||
@mouseleave="hoveredCancelReservationButton.delete(item.id)"
|
||||
:label="hoveredCancelReservationButton.get(item.id) ? 'Отменить' : 'Забронировано'"
|
||||
:disabled="!hoveredCancelReservationButton.get(item.id)"
|
||||
:severity="hoveredCancelReservationButton.get(item.id) ? 'danger' : ''"
|
||||
class="w-full"
|
||||
@click="hoveredCancelReservationButton.get(item.id) ? cancelReserve(item): ''"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.flex-row > Image {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.p-image-preview-mask {
|
||||
position: absolute !important;
|
||||
}
|
||||
</style>
|
||||
22
src/models/WishList.ts
Normal file
22
src/models/WishList.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
export class WishList {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
isPrivate: boolean
|
||||
items: WishlistItem[]
|
||||
updatedAt: Date
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
export class WishlistItem {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
price: number
|
||||
link: string
|
||||
images: string[]
|
||||
updatedAt: Date
|
||||
createdAt: Date
|
||||
imagesWithLinks: []
|
||||
}
|
||||
@@ -16,11 +16,17 @@ import SpacesList from "@/components/spaces/SpacesList.vue";
|
||||
import SpaceInventationView from "@/components/spaces/SpaceInventationView.vue";
|
||||
import About from "@/components/faq/About.vue";
|
||||
import OnboardingView from "@/components/onboarding/OnboardingView.vue";
|
||||
import WishListListView from "@/components/wishlists/WishListListView.vue";
|
||||
import WishlistView from "@/components/wishlists/WishlistView.vue";
|
||||
import WishlistExternalView from "@/components/wishlists/WishlistExternalView.vue";
|
||||
|
||||
const routes = [
|
||||
{path: '/login', component: LoginView},
|
||||
{path: '/register', component: RegisterView},
|
||||
{path: '/', name: 'Budgets Main', component: BudgetList, meta: {requiresAuth: true}},
|
||||
{path: '/wishlists', name: 'Wishlists', component: WishListListView, meta: {requiresAuth: true}},
|
||||
{path: '/wishlists/:id', name: 'Wishlist view', component: WishlistView, meta: {requiresAuth: true}},
|
||||
{path: '/mywishlist/:id', name: 'Wishlist view export', component: WishlistExternalView},
|
||||
// {path: '/onboarding', name: 'Onboarding', component: OnboardingView, meta: {requiresAuth: true}},
|
||||
{path: '/about', name: 'About', component: About, meta: {requiresAuth: true}},
|
||||
{path: '/analytics', name: 'Analytics', component: AnalyticsView, meta: {requiresAuth: true}},
|
||||
|
||||
13
src/services/StaticService.ts
Normal file
13
src/services/StaticService.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {useSpaceStore} from "@/stores/spaceStore";
|
||||
import apiClient from "@/services/axiosSetup";
|
||||
|
||||
export const uploadStatic = async ( wishlistItemId: string, file) => {
|
||||
const spaceStore = useSpaceStore()
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
return await apiClient.post(`/static/${spaceStore.space?.id}/wishlists/${wishlistItemId}`, form)
|
||||
.then((res) => res.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
118
src/services/WishListService.ts
Normal file
118
src/services/WishListService.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import {useSpaceStore} from "@/stores/spaceStore";
|
||||
import apiClient from "@/services/axiosSetup";
|
||||
import {WishList, WishlistItem} from "@/models/WishList";
|
||||
|
||||
export const getWishlists = async () => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.get(`/spaces/${spaceStore.space?.id}/wishlists`)
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export const getWishlist = async (id: string) => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.get(`/spaces/${spaceStore.space?.id}/wishlists/${id}`).then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
export const getWishlistExternal = async (id: string) => {
|
||||
return await apiClient.get(`/wishlistexternal/${id}`).then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
export const reserveWishlistItem = async (wishlistId: string, wishlistItemId: string, reservedBy: string, aidCookie: string) => {
|
||||
const form = new FormData();
|
||||
form.append("reservedBy", reservedBy);
|
||||
return await apiClient.post(`/wishlistexternal/${wishlistId}/${wishlistItemId}/reserve/_create`, {
|
||||
'aid': aidCookie,
|
||||
'name': reservedBy
|
||||
}, {headers: {"Content-Type": "application/json"}})
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const cancelReserveWishlistItem = async (wishlistId: string, wishlistItemId: string, reservedBy = null, aidCookie = null) => {
|
||||
if (reservedBy) {
|
||||
return await apiClient.post(`/wishlistexternal/${wishlistId}/${wishlistItemId}/reserve/_cancel`, {
|
||||
'aid': aidCookie,
|
||||
'name': reservedBy
|
||||
}, {headers: {"Content-Type": "application/json"}})
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
} else {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.post(`/spaces/${spaceStore.space?.id}/wishlists/${wishlistId}/${wishlistItemId}/reserve/_cancel`, {
|
||||
'aid': aidCookie,
|
||||
'name': reservedBy
|
||||
}, {headers: {"Content-Type": "application/json"}})
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const createWishlistRequest = async (data: WishList) => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.post(`/spaces/${spaceStore.space?.id}/wishlists`, data)
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export const updateWishlistRequest = async (data: WishList) => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.patch(`/spaces/${spaceStore.space?.id}/wishlists/${data.id}`, data)
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export const addWishListItemRequest = async (wishlistId: string, data: WishlistItem) => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.post(`/spaces/${spaceStore.space?.id}/wishlists/${wishlistId}/items`, data)
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export const updateWishListItemRequest = async (wishlistId: string, data: WishlistItem) => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.patch(`/spaces/${spaceStore.space?.id}/wishlists/${wishlistId}/items/${data.id}`, data)
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export const deleteWishListItemRequest = async (wishlistId: string, data: WishlistItem) => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.delete(`/spaces/${spaceStore.space?.id}/wishlists/${wishlistId}/items/${data.id}`)
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const deleteWishlistRequest = async (data: string) => {
|
||||
const spaceStore = useSpaceStore();
|
||||
return await apiClient.delete(`/spaces/${spaceStore.space?.id}/wishlists/${data}`)
|
||||
.then((data: any) => data.data)
|
||||
.catch((err) => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user