+ wishlist item is active

This commit is contained in:
xds
2025-07-17 13:07:32 +03:00
parent 70cd88c35d
commit 632fdf4f57
6 changed files with 178 additions and 23 deletions

View File

@@ -16,7 +16,7 @@
<div class="bg-gray-100 h-12 block lg:hidden"></div>
</div>
<div id="footer" class="flex flex-col w-full h-fit bg-gray-200 p-4 gap-4 ">
<div v-if="userStore.user && !route.path.startsWith('/mywishlist')" id="footer" class="flex flex-col w-full h-fit bg-gray-200 p-4 gap-4 ">
<div class="flex flex-row items-start gap-2 ">
<div class="flex flex-row items-center gap-2 min-w-fit">
<img alt="logo" src="/apple-touch-icon.png" width="48" height="48"/>
@@ -37,9 +37,9 @@
</div>
</div>
<div class="flex flex-row justify-between">
<div>Ваши предложения можно направлять в <a href="https://t.me/voroninv" class="hover:underline text-blue-600">https://t.me/@voroninv</a>
</div>
<div class="flex flex-row w-full items-end">
<!-- <div>Ваши предложения можно направлять в <a href="https://t.me/voroninv" class="hover:underline text-blue-600">https://t.me/@voroninv</a>-->
<!-- </div>-->
<div>v0.0.2</div>
</div>
<div class="h-16 lg:h-0"/>

View File

@@ -16,6 +16,7 @@ import {useSpaceStore} from "@/stores/spaceStore";
import {useToast} from "primevue/usetoast";
import WishlistCreationView from "@/components/wishlists/WishlistCreationView.vue";
import {useConfirm} from "primevue/useconfirm";
import {useUserStore} from "@/stores/userStore";
const toast = useToast()
const confirm = useConfirm();
@@ -39,6 +40,8 @@ const wishlistCreationCanceled = () => {
creationOpened.value = false
}
const userStore = useUserStore();
const deleteWishlist = async (wishlist: WishList) => {
confirm.require({
message: `Вы действительно хотите удалить вишлист ${wishlist.name} ?`,
@@ -152,13 +155,13 @@ onMounted(async () => {
<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>
<button v-if="wishlist.owner.id==userStore.user.id" @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>
<button v-if="wishlist.owner.id==userStore.user.id" @click="deleteWishlist(wishlist)"><i class="pi pi-trash" /></button>
</div>
</div>

View File

@@ -72,6 +72,7 @@ const cancelCreation = () => {
<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" class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
<Button label="Отмена" severity="secondary" icon="pi pi-times-circle" @click="cancelCreation"/>

View File

@@ -9,8 +9,9 @@ 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 {addWishListItemRequest, updateWishListItemRequest} from "@/services/WishListService";
import {useToast} from "primevue/usetoast";
import Checkbox from "primevue/checkbox";
const toast = useToast()
@@ -19,9 +20,11 @@ const props = defineProps({
editItem: Object,
})
const emits = defineEmits(["item-created", "creation-cancelled"])
const emits = defineEmits(["item-created", "creation-cancelled", "item-updated"]);
const isEditing = ref(!!props.editItem)
const item = ref(props.editItem ? props.editItem : new WishlistItem())
const newItemRef = ref<WishlistItem>(new WishlistItem());
newItemRef.value.isActive = true;
const item = ref(props.editItem ? props.editItem : newItemRef.value)
const inputNameRef = ref()
const inputDescriptionRef = ref()
const inputPriceRef = ref()
@@ -63,6 +66,22 @@ const checkForm = () => {
return false
} else return true
}
const updateWishlistItem = async () => {
await updateWishListItemRequest(props.wishlistId, item.value)
.then(async (res) => {
emits("item-updated", res)
})
.catch((err) => {
toast.add({
severity: 'error',
summary: 'Ошибка сохранения желания',
detail: err.response.data.message,
life: 3000
})
})
}
const itemCreate = async () => {
if (checkForm()) {
await addWishListItemRequest(props.wishlistId, item.value)
@@ -112,8 +131,12 @@ const resetForm = () => {
<InputNumber :ref="inputPriceRef" v-model="item.price" placeholder="Сумма"/>
<InputGroupAddon>.00</InputGroupAddon>
</InputGroup>
<div class="flex items-center gap-2">
<Checkbox v-model="item.isActive" inputId="isActive" name="isActive" binary />
<label for="isActive">Активен?</label>
</div>
<div class="flex flex-row gap-2 justify-end items-center">
<Button label="Создать" severity="success" icon="pi pi-save" @click="itemCreate" class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
<Button :label="props.editItem ? 'Сохранить' : 'Создать'" severity="success" icon="pi pi-save" @click="props.editItem? updateWishlistItem() : itemCreate()" class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
<Button label="Отмена" severity="secondary" icon="pi pi-times-circle" @click="cancelCreation"/>
</div>
</div>

View File

@@ -26,6 +26,7 @@ import InputText from "primevue/inputtext";
import FloatLabel from "primevue/floatlabel";
import ConfirmDialog from "primevue/confirmdialog";
import {useConfirm} from "primevue/useconfirm";
import {useUserStore} from "@/stores/userStore";
const route = useRoute()
@@ -35,6 +36,9 @@ const confirm = useConfirm()
const loading = ref(true);
const wishlist = ref<WishList>()
const activeItems = ref<WishlistItem[]>([])
const disabledItems = ref<WishlistItem[]>([])
const disabledItemsShowed = ref<boolean>(false)
const selectedImage = reactive(new Map<string, string>())
const hoveredCancelReservationButton = reactive(new Map<string, boolean>())
@@ -44,6 +48,9 @@ const fetchWishlist = async () => {
wishlist.value = res
wishlist.value?.items.forEach((item: WishlistItem) => {
selectedImage.set(item.id, item.images[0])
if (item.isActive) {
activeItems.value.push(item)
} else disabledItems.value.push(item)
})
shareLink.value = window.location.origin + "/mywishlist/" + wishlist.value?.id
loading.value = false
@@ -235,6 +242,8 @@ const spaceStore = useSpaceStore()
const selectedSpace = computed(() => spaceStore.space)
const fileUploadLink = ref()
const userStore = useUserStore()
watch(
() => selectedSpace.value,
@@ -301,7 +310,8 @@ onMounted(async () => {
@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"/>
@creation-cancelled="editingItem = null;wishlistItemCreationOpened = false"
@item-updated="itemCreated"/>
</Dialog>
<Dialog :visible="shareDialogOpened" header="Поделиться" @hide="shareDialogOpened = false"
@update:visible="shareDialogOpened=false" modal>
@@ -340,17 +350,19 @@ onMounted(async () => {
<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" class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
<Button v-if="wishlist.owner.id==userStore.user.id" label="+ Создать"
@click="wishlistItemCreationOpened=true" size="small"
class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
</div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-2">
<div v-for="item in wishlist.items"
<div v-for="item in activeItems"
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 justify-between h-full">
<div class="flex flex-col w-full justify-center 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) "
@@ -359,16 +371,24 @@ onMounted(async () => {
imageClass="h-64 w-64 object-cover items-center justify-center justify-items-center"/>
</div>
<div v-else class="min-h-64 flex flex-col items-center justify-center gap-2">
<button @click="imageUploadVisible=true;imageUploadForItemId=item.id" class="flex flex-col items-center gap-2">
<div
class="rounded-full w-16 h-16 bg-gray-400 opacity-30 group-hover:opacity-70 flex items-center justify-center">
<button v-if="wishlist.owner.id==userStore.user.id"
@click="imageUploadVisible=true;imageUploadForItemId=item.id"
class="flex flex-col items-center gap-2">
<div
class="rounded-full w-16 h-16 bg-gray-400 opacity-30 group-hover:opacity-70 flex items-center justify-center">
<i class="pi pi-plus !font-bold !text-3xl text-white"/>
</div>
<span class="text-gray-600 text-xl">Загрузите изображение</span>
</div>
<span class="text-gray-600 text-xl">Загрузите изображение</span>
</button>
<div v-else
class="min-h-[304px] flex flex-col items-center w-full justify-center gap-2 bg-gray-100 rounded-lg max-w-128 h-full">
<i class="pi pi-images !text-gray-400 z-50" style="font-size: 8rem"/>
<span class="text-lg font-bold text-center text-gray-400">Изображение пока не загружено</span>
</div>
</div>
<div class="flex flex-row !h-12 gap-2">
@@ -380,13 +400,14 @@ onMounted(async () => {
imageClass="w-12 h-12 object-cover"/>
</button>
<!-- Иконка для удаления -->
<button @click="deleteImage(item, index)">
<button v-if="wishlist.owner.id==userStore.user.id" @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 v-if="wishlist.owner.id==userStore.user.id"
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">
@@ -401,7 +422,7 @@ onMounted(async () => {
<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">
<div v-if="wishlist.owner.id==userStore.user.id" class="flex flex-row gap-2">
<button @click="editingItem=item;wishlistItemCreationOpened=true"><i
class="pi pi-pen-to-square"/>
</button>
@@ -417,7 +438,113 @@ onMounted(async () => {
<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" class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
<Button label="В магазин" icon="pi pi-arrow-up-right" iconPos="right"
class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
</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 !bg-blue-300 hover:!bg-blue-400 !border-blue-300"
@click="hoveredCancelReservationButton.get(item.id) ? cancelReserve(item): ''"/>
</div>
</div>
</div>
</div>
<div class="flex flex-row gap-4 w-full ju">
<p class="text-2xl items-end text-gray-700">Архив</p>
<button @click="disabledItemsShowed=!disabledItemsShowed"><span class="text-sm text-gray-600">{{disabledItemsShowed ? 'Скрыть' : 'Раскрыть'}}<i
class="pi pi-arrow-up-right" style="font-size: 0.65rem"/></span></button></div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-2">
<div v-if="disabledItemsShowed" v-for="item in disabledItems"
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 justify-between h-full">
<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 v-else class="min-h-64 flex flex-col items-center justify-center gap-2">
<button v-if="wishlist.owner.id==userStore.user.id"
@click="imageUploadVisible=true;imageUploadForItemId=item.id"
class="flex flex-col items-center gap-2">
<div
class="rounded-full w-16 h-16 bg-gray-400 opacity-30 group-hover:opacity-70 flex items-center justify-center">
<i class="pi pi-plus !font-bold !text-3xl text-white"/>
</div>
<span class="text-gray-600 text-xl">Загрузите изображение</span>
</button>
<div v-else
class="min-h-[304px] flex flex-col items-center w-full justify-center gap-2 bg-gray-100 rounded-lg max-w-128 h-full">
<i class="pi pi-images !text-gray-400 z-50" style="font-size: 8rem"/>
<span class="text-lg font-bold text-center text-gray-400">Изображение пока не загружено</span>
</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 ">
<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 v-if="wishlist.owner.id==userStore.user.id" @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 v-if="wishlist.owner.id==userStore.user.id"
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 v-if="wishlist.owner.id==userStore.user.id" 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-blue-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 w-full justify-between items-center">
<a :href="item.link"
target="_blank">
<Button label="В магазин" icon="pi pi-arrow-up-right" iconPos="right"
class="!bg-blue-300 hover:!bg-blue-400 !border-blue-300"/>
</a>
<span v-if="!item.reservedBy" class="text-gray-500">Не забронировано</span>
<Button v-else @mouseover="hoveredCancelReservationButton.set(item.id, true)"

View File

@@ -16,6 +16,7 @@ export class WishlistItem {
price: number
link: string
images: string[]
isActive: boolean
updatedAt: Date
createdAt: Date
imagesWithLinks: []