fix: add page Product list and fix page Cart

This commit is contained in:
2026-03-19 15:46:18 +01:00
parent 1ea50af96a
commit 99fe11fbeb
17 changed files with 168 additions and 97 deletions

1
bo/components.d.ts vendored
View File

@@ -19,6 +19,7 @@ declare module 'vue' {
PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default'] PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default']
PageCart: typeof import('./src/components/customer/PageCart.vue')['default'] PageCart: typeof import('./src/components/customer/PageCart.vue')['default']
PageProductCardFull: typeof import('./src/components/customer/PageProductCardFull.vue')['default'] PageProductCardFull: typeof import('./src/components/customer/PageProductCardFull.vue')['default']
PageProductsList: typeof import('./src/components/customer/PageProductsList.vue')['default']
Pl_PrivacyPolicyView: typeof import('./src/components/terms/pl_PrivacyPolicyView.vue')['default'] Pl_PrivacyPolicyView: typeof import('./src/components/terms/pl_PrivacyPolicyView.vue')['default']
Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default'] Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default']
ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default'] ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default']

View File

@@ -31,6 +31,9 @@ const authStore = useAuthStore()
<RouterLink :to="{ name: 'cart' }"> <RouterLink :to="{ name: 'cart' }">
Cart Cart
</RouterLink> </RouterLink>
<RouterLink :to="{ name: 'products-list' }">
Products List
</RouterLink>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Language Switcher --> <!-- Language Switcher -->
<LangSwitch /> <LangSwitch />

View File

@@ -14,9 +14,9 @@ const authStore = useAuthStore()
<!-- Logo --> <!-- Logo -->
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2"> <RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-primary text-white flex items-center justify-center"> <div class="w-8 h-8 rounded-lg bg-primary text-white flex items-center justify-center">
<UIcon name="i-heroicons-clock" class="w-5 h-5" /> <UIcon name="carbon:ibm-webmethods-b2b-integration" class="w-5 h-5" />
</div> </div>
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span> <span class="font-semibold text-gray-900 dark:text-white">B2B</span>
</RouterLink> </RouterLink>
<!-- Right Side Actions --> <!-- Right Side Actions -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">

View File

@@ -1,8 +1,8 @@
<template> <template>
<div class="container mx-auto mt-10"> <div class="container mx-auto mt-10">
<div class="flex flex-col mb-6"> <div class="flex flex-col gap-5 mb-6">
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Addresses') }}</h1> <h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Addresses') }}</h1>
<div class="flex justify-between items-center"> <div class="flex md:flex-row flex-col justify-between items-start md:items-center gap-5 md:gap-0">
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
<UInput v-model="searchQuery" type="text" :placeholder="t('Search address')" <UInput v-model="searchQuery" type="text" :placeholder="t('Search address')"
class="bg-white dark:bg-gray-800 text-black dark:text-white absolute" /> class="bg-white dark:bg-gray-800 text-black dark:text-white absolute" />
@@ -16,7 +16,6 @@
</UButton> </UButton>
</div> </div>
</div> </div>
<div v-if="paginatedAddresses.length" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> <div v-if="paginatedAddresses.length" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<div v-for="address in paginatedAddresses" :key="address.id" <div v-for="address in paginatedAddresses" :key="address.id"
class="border border-(--border-light) dark:border-(--border-dark) rounded-md p-4 bg-(--second-light) dark:bg-(--main-dark) hover:shadow-md transition-shadow"> class="border border-(--border-light) dark:border-(--border-dark) rounded-md p-4 bg-(--second-light) dark:bg-(--main-dark) hover:shadow-md transition-shadow">
@@ -35,11 +34,9 @@
</div> </div>
</div> </div>
<div v-else class="text-center py-8 text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</div> <div v-else class="text-center py-8 text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</div>
<div class="mt-6 flex justify-center"> <div class="mt-6 flex justify-center">
<UPagination v-model:page="page" :total="totalItems" :page-size="pageSize" /> <UPagination v-model:page="page" :total="totalItems" :page-size="pageSize" />
</div> </div>
<UModal v-model:open="showModal" :overlay="true" class="max-w-md mx-auto"> <UModal v-model:open="showModal" :overlay="true" class="max-w-md mx-auto">
<template #content> <template #content>
<div class="p-6 flex flex-col gap-6"> <div class="p-6 flex flex-col gap-6">
@@ -74,7 +71,6 @@
</div> </div>
</template> </template>
</UModal> </UModal>
<UModal v-model:open="showDeleteConfirm" :overlay="true" class="max-w-md mx-auto"> <UModal v-model:open="showDeleteConfirm" :overlay="true" class="max-w-md mx-auto">
<template #content> <template #content>
<div class="p-6 flex flex-col gap-3"> <div class="p-6 flex flex-col gap-3">
@@ -106,7 +102,6 @@ import { useI18n } from 'vue-i18n'
const addressStore = useAddressStore() const addressStore = useAddressStore()
const { t } = useI18n() const { t } = useI18n()
const searchQuery = ref('') const searchQuery = ref('')
const showModal = ref(false) const showModal = ref(false)
const isEditing = ref(false) const isEditing = ref(false)
@@ -122,17 +117,14 @@ const totalItems = computed(() => addressStore.totalItems)
const pageSize = addressStore.pageSize const pageSize = addressStore.pageSize
watch(page, (newPage) => addressStore.setPage(newPage)) watch(page, (newPage) => addressStore.setPage(newPage))
watch(searchQuery, (val) => { watch(searchQuery, (val) => {
addressStore.setSearchQuery(val) addressStore.setSearchQuery(val)
}) })
function openCreateModal() { function openCreateModal() {
resetForm() resetForm()
isEditing.value = false isEditing.value = false
showModal.value = true showModal.value = true
} }
function openEditModal(address: any) { function openEditModal(address: any) {
formData.value = { formData.value = {
street: address.street, street: address.street,
@@ -144,17 +136,14 @@ function openEditModal(address: any) {
editingAddressId.value = address.id editingAddressId.value = address.id
showModal.value = true showModal.value = true
} }
function resetForm() { function resetForm() {
formData.value = { street: '', zipCode: '', city: '', country: '' } formData.value = { street: '', zipCode: '', city: '', country: '' }
editingAddressId.value = null editingAddressId.value = null
} }
function closeModal() { function closeModal() {
showModal.value = false showModal.value = false
resetForm() resetForm()
} }
function validate() { function validate() {
const errors = [] const errors = []
if (!formData.value.street) errors.push({ name: 'street', message: 'Street required' }) if (!formData.value.street) errors.push({ name: 'street', message: 'Street required' })
@@ -163,7 +152,6 @@ function validate() {
if (!formData.value.country) errors.push({ name: 'country', message: 'Country required' }) if (!formData.value.country) errors.push({ name: 'country', message: 'Country required' })
return errors.length ? errors : null return errors.length ? errors : null
} }
function saveAddress() { function saveAddress() {
if (validate()) return if (validate()) return
if (isEditing.value && editingAddressId.value) { if (isEditing.value && editingAddressId.value) {
@@ -173,12 +161,18 @@ function saveAddress() {
} }
closeModal() closeModal()
} }
// const Lera = ref('')
// function run (){
// if(Lera.value==='lera'){
// console.log('Leraa okokok')
// }else{
// console.log('LEra nonono')
// }
// }
function confirmDelete(id: number) { function confirmDelete(id: number) {
addressToDelete.value = id addressToDelete.value = id
showDeleteConfirm.value = true showDeleteConfirm.value = true
} }
function deleteAddress() { function deleteAddress() {
if (addressToDelete.value) { if (addressToDelete.value) {
addressStore.deleteAddress(addressToDelete.value) addressStore.deleteAddress(addressToDelete.value)

View File

@@ -3,11 +3,14 @@
<h1 class="text-2xl font-bold text-black dark:text-white mb-8">{{ t('Shopping Cart') }}</h1> <h1 class="text-2xl font-bold text-black dark:text-white mb-8">{{ t('Shopping Cart') }}</h1>
<div class="flex flex-col lg:flex-row gap-8 mb-8"> <div class="flex flex-col lg:flex-row gap-8 mb-8">
<div class="flex-1"> <div class="flex-1">
<div class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) overflow-hidden"> <div
<h2 class="text-lg font-semibold text-black dark:text-white p-4 border-b border-(--border-light) dark:border-(--border-dark)"> class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) overflow-hidden">
<h2
class="text-lg font-semibold text-black dark:text-white p-4 border-b border-(--border-light) dark:border-(--border-dark)">
{{ t('Selected Products') }} {{ t('Selected Products') }}
</h2> </h2>
<div class="hidden md:grid grid-cols-12 gap-4 p-4 bg-(--second-light) dark:bg-(--main-dark) text-sm font-medium text-gray-600 dark:text-gray-300 border-b border-(--border-light) dark:border-(--border-dark)"> <div
class="hidden md:grid grid-cols-12 gap-4 p-4 bg-(--second-light) dark:bg-(--main-dark) text-sm font-medium text-gray-600 dark:text-gray-300 border-b border-(--border-light) dark:border-(--border-dark)">
<div class="col-span-4">{{ t('Product') }}</div> <div class="col-span-4">{{ t('Product') }}</div>
<div class="col-span-2 text-right">{{ t('Price') }}</div> <div class="col-span-2 text-right">{{ t('Price') }}</div>
<div class="col-span-3 text-center">{{ t('Quantity') }}</div> <div class="col-span-3 text-center">{{ t('Quantity') }}</div>
@@ -15,10 +18,11 @@
<div class="col-span-1 text-center">{{ t('Actions') }}</div> <div class="col-span-1 text-center">{{ t('Actions') }}</div>
</div> </div>
<div v-if="cartStore.items.length > 0"> <div v-if="cartStore.items.length > 0">
<div v-for="item in cartStore.items" :key="item.id" <div v-for="item in cartStore.items" :key="item.id"
class="grid grid-cols-1 md:grid-cols-12 gap-4 p-4 border-b border-(--border-light) dark:border-(--border-dark) items-center"> class="grid grid-cols-1 md:grid-cols-12 gap-4 p-4 border-b border-(--border-light) dark:border-(--border-dark) items-center">
<div class="col-span-4 flex items-center gap-4"> <div class="col-span-4 flex items-center gap-4">
<div class="w-16 h-16 bg-(--second-light) dark:bg-(--main-dark) rounded flex items-center justify-center overflow-hidden"> <div
class="w-16 h-16 bg-(--second-light) dark:bg-(--main-dark) rounded flex items-center justify-center overflow-hidden">
<img v-if="item.image" :src="item.image" :alt="item.name" class="w-full h-full object-cover" /> <img v-if="item.image" :src="item.image" :alt="item.name" class="w-full h-full object-cover" />
<UIcon v-else name="mdi:package-variant" class="text-2xl text-gray-400" /> <UIcon v-else name="mdi:package-variant" class="text-2xl text-gray-400" />
</div> </div>
@@ -29,22 +33,18 @@
<span class="text-black dark:text-white">${{ item.price.toFixed(2) }}</span> <span class="text-black dark:text-white">${{ item.price.toFixed(2) }}</span>
</div> </div>
<div class="col-span-3 flex items-center justify-center"> <div class="col-span-3 flex items-center justify-center">
<div class="flex items-center border border-(--border-light) dark:border-(--border-dark) rounded"> <UInputNumber v-model="item.quantity" :min="1"
<button @click="decreaseQuantity(item)" class="px-3 py-1 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"> @update:model-value="(val: number) => cartStore.updateQuantity(item.id, val)" />
<UIcon name="mdi:minus" />
</button>
<span class="px-3 py-1 text-black dark:text-white min-w-[40px] text-center">{{ item.quantity }}</span>
<button @click="increaseQuantity(item)" class="px-3 py-1 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
<UIcon name="mdi:plus" />
</button>
</div>
</div> </div>
<div class="col-span-2 text-right"> <div class="col-span-2 text-right">
<span class="md:hidden text-gray-500 dark:text-gray-400 text-sm">{{ t('Total') }}: </span> <span class="md:hidden text-gray-500 dark:text-gray-400 text-sm">{{ t('Total') }}: </span>
<span class="text-black dark:text-white font-medium">${{ (item.price * item.quantity).toFixed(2) }}</span> <span class="text-black dark:text-white font-medium">${{ (item.price * item.quantity).toFixed(2)
}}</span>
</div> </div>
<div class="col-span-1 flex justify-center"> <div class="col-span-1 flex justify-center">
<button @click="removeItem(item.id)" class="p-2 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors" :title="t('Remove')"> <button @click="removeItem(item.id)"
class="p-2 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors"
:title="t('Remove')">
<UIcon name="material-symbols:delete" class="text-[20px]" /> <UIcon name="material-symbols:delete" class="text-[20px]" />
</button> </button>
</div> </div>
@@ -53,14 +53,16 @@
<div v-else class="p-8 text-center"> <div v-else class="p-8 text-center">
<UIcon name="mdi:cart-outline" class="text-6xl text-gray-300 dark:text-gray-600 mb-4" /> <UIcon name="mdi:cart-outline" class="text-6xl text-gray-300 dark:text-gray-600 mb-4" />
<p class="text-gray-500 dark:text-gray-400">{{ t('Your cart is empty') }}</p> <p class="text-gray-500 dark:text-gray-400">{{ t('Your cart is empty') }}</p>
<RouterLink :to="{ name: 'product-card-full' }" class="inline-block mt-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline"> <RouterLink :to="{ name: 'product-card-full' }"
class="inline-block mt-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
{{ t('Continue Shopping') }} {{ t('Continue Shopping') }}
</RouterLink> </RouterLink>
</div> </div>
</div> </div>
</div> </div>
<div class="lg:w-80"> <div class="lg:w-80">
<div class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6 sticky top-24"> <div
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6 sticky top-24">
<h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Order Summary') }}</h2> <h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Order Summary') }}</h2>
<div class="space-y-3 border-b border-(--border-light) dark:border-(--border-dark) pb-4 mb-4"> <div class="space-y-3 border-b border-(--border-light) dark:border-(--border-dark) pb-4 mb-4">
<div class="flex justify-between"> <div class="flex justify-between">
@@ -74,13 +76,15 @@
</span> </span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">{{ t('VAT') }} ({{ (cartStore.vatRate * 100).toFixed(0) }}%)</span> <span class="text-gray-600 dark:text-gray-400">{{ t('VAT') }} ({{ (cartStore.vatRate * 100).toFixed(0)
}}%)</span>
<span class="text-black dark:text-white">${{ cartStore.vatAmount.toFixed(2) }}</span> <span class="text-black dark:text-white">${{ cartStore.vatAmount.toFixed(2) }}</span>
</div> </div>
</div> </div>
<div class="flex justify-between mb-6"> <div class="flex justify-between mb-6">
<span class="text-black dark:text-white font-semibold text-lg">{{ t('Total') }}</span> <span class="text-black dark:text-white font-semibold text-lg">{{ t('Total') }}</span>
<span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-bold text-lg">${{ cartStore.orderTotal.toFixed(2) }}</span> <span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-bold text-lg">${{
cartStore.orderTotal.toFixed(2) }}</span>
</div> </div>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<UButton block color="primary" @click="placeOrder" :disabled="!canPlaceOrder" <UButton block color="primary" @click="placeOrder" :disabled="!canPlaceOrder"
@@ -97,19 +101,19 @@
</div> </div>
<div class="flex flex-col lg:flex-row gap-8"> <div class="flex flex-col lg:flex-row gap-8">
<div class="flex-1"> <div class="flex-1">
<div class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6"> <div
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6">
<h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Select Delivery Address') }}</h2> <h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Select Delivery Address') }}</h2>
<div class="mb-4"> <div class="mb-4">
<UInput v-model="addressSearchQuery" type="text" :placeholder="t('Search address')" <UInput v-model="addressSearchQuery" type="text" :placeholder="t('Search address')"
class="w-full bg-white dark:bg-gray-800 text-black dark:text-white" /> class="w-full bg-white dark:bg-gray-800 text-black dark:text-white" />
</div> </div>
<div v-if="addressStore.filteredAddresses.length > 0" class="space-y-3"> <div v-if="addressStore.filteredAddresses.length > 0" class="space-y-3">
<label v-for="address in addressStore.filteredAddresses" :key="address.id" <label v-for="address in addressStore.filteredAddresses" :key="address.id"
class="flex items-start gap-3 p-4 border rounded-lg cursor-pointer transition-colors" class="flex items-start gap-3 p-4 border rounded-lg cursor-pointer transition-colors" :class="cartStore.selectedAddressId === address.id
:class="cartStore.selectedAddressId === address.id ? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'"> : 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
<input type="radio" :value="address.id" v-model="selectedAddress" <input type="radio" :value="address.id" v-model="selectedAddress"
class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" /> class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
<div class="flex-1"> <div class="flex-1">
<p class="text-black dark:text-white font-medium">{{ address.street }}</p> <p class="text-black dark:text-white font-medium">{{ address.street }}</p>
@@ -121,22 +125,23 @@
<div v-else class="text-center py-6"> <div v-else class="text-center py-6">
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" /> <UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p> <p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
<RouterLink :to="{ name: 'addresses' }" class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline"> <RouterLink :to="{ name: 'addresses' }"
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
{{ t('Add Address') }} {{ t('Add Address') }}
</RouterLink> </RouterLink>
</div> </div>
</div> </div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6"> <div
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6">
<h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Delivery Method') }}</h2> <h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Delivery Method') }}</h2>
<div class="space-y-3"> <div class="space-y-3">
<label v-for="method in cartStore.deliveryMethods" :key="method.id" <label v-for="method in cartStore.deliveryMethods" :key="method.id"
class="flex items-center gap-3 p-4 border rounded-lg cursor-pointer transition-colors" class="flex items-center gap-3 p-4 border rounded-lg cursor-pointer transition-colors" :class="cartStore.selectedDeliveryMethodId === method.id
:class="cartStore.selectedDeliveryMethodId === method.id ? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'"> : 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
<input type="radio" :value="method.id" v-model="selectedDeliveryMethod" <input type="radio" :value="method.id" v-model="selectedDeliveryMethod"
class="w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" /> class="w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
<div class="flex-1"> <div class="flex-1">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
@@ -157,7 +162,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useCartStore, type CartItem } from '@/stores/cart' import { useCartStore } from '@/stores/cart'
import { useAddressStore } from '@/stores/address' import { useAddressStore } from '@/stores/address'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -186,19 +191,10 @@ watch(selectedDeliveryMethod, (newValue) => {
}) })
const canPlaceOrder = computed(() => { const canPlaceOrder = computed(() => {
return cartStore.items.length > 0 && return cartStore.items.length > 0 &&
cartStore.selectedAddressId !== null && cartStore.selectedAddressId !== null &&
cartStore.selectedDeliveryMethodId !== null cartStore.selectedDeliveryMethodId !== null
}) })
function increaseQuantity(item: CartItem) {
cartStore.updateQuantity(item.id, item.quantity + 1)
}
function decreaseQuantity(item: CartItem) {
cartStore.updateQuantity(item.id, item.quantity - 1)
}
function removeItem(itemId: number) { function removeItem(itemId: number) {
cartStore.removeItem(itemId) cartStore.removeItem(itemId)
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="container mt-14 mx-auto"> <div class="container mt-14 mx-auto">
<div class="flex justify-between gap-8 mb-6"> <div class="flex md:flex-row flex-col justify-between gap-8 mb-6">
<div class="flex-1"> <div class="flex-1">
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-8 flex items-center justify-center min-h-[300px]"> <div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-8 flex items-center justify-center min-h-[300px]">
<img :src="selectedColor?.image || productData.image" :alt="productData.name" <img :src="selectedColor?.image || productData.image" :alt="productData.name"
@@ -29,7 +29,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="flex justify-between items-end mb-8"> <div class="flex md:flex-row flex-col justify-between md:items-end items-start gap-5 md:gap-0 md:mb-8 mb-4">
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<span class="text-sm text-(--accent-blue-light) dark:text-(--accent-blue-dark) ">Colors:</span> <span class="text-sm text-(--accent-blue-light) dark:text-(--accent-blue-dark) ">Colors:</span>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -49,10 +49,10 @@
</div> </div>
<ProductCustomization /> <ProductCustomization />
<hr class="border-t border-(--border-light) dark:border-(--border-dark) mb-8" /> <hr class="border-t border-(--border-light) dark:border-(--border-dark) mb-8" />
<div class="mb-6 w-[55%]"> <div class="mb-6 w-[100%] xl:w-[60%]">
<div class="flex justify-between items-center gap-10 mb-8"> <div class="grid grid-cols-2 lg:grid-cols-4 gap-10 mb-8">
<UButton v-for="tab in tabs" :key="tab.id" @click="activeTab = tab.id" :class="[ <UButton v-for="tab in tabs" :key="tab.id" @click="activeTab = tab.id" :class="[
'px-15 py-2 cursor-pointer', 'px-15 py-2 cursor-pointer sm:text-nowrap flex items-center! justify-center!',
activeTab === tab.id activeTab === tab.id
? 'bg-blue-600 hover:text-black hover:dark:text-white text-white' ? 'bg-blue-600 hover:text-black hover:dark:text-white text-white'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'

View File

@@ -0,0 +1,89 @@
<template>
<div class="container mx-auto p-6">
<h1 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">Products</h1>
<div v-if="loading" class="text-center py-8">
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
</div>
<div v-else-if="error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
{{ error }}
</div>
<div v-else class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Product ID
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Reference
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Price
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
<tr v-for="product in products" :key="product.product_id"
class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{{ product.product_id }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{{ product.reference }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{{ product.price.toFixed(2) }}
</td>
</tr>
</tbody>
</table>
<div v-if="products.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
No products found
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useFetchJson } from '@/composable/useFetchJson'
interface Product {
product_id: number
reference: string
price: number
}
interface ApiResponse {
items: Product[]
count: number
message: string
}
const products = ref<Product[]>([])
const loading = ref(true)
const error = ref<string | null>(null)
async function fetchProducts() {
loading.value = true
error.value = null
try {
const response = await useFetchJson<ApiResponse>('/api/v1/restricted/list-products/get-listing?p=3&elems') as unknown as { items: Product[] }
products.value = response.items || []
} catch (e: any) {
error.value = e.message || 'Failed to load products'
console.error('Error fetching products:', e)
} finally {
loading.value = false
}
}
onMounted(() => {
fetchProducts()
})
</script>

View File

@@ -4,7 +4,7 @@
<p class="text-[24px] font-bold">Product customization</p> <p class="text-[24px] font-bold">Product customization</p>
<p class="text-[15px]">Don't forget to save your customization to be able to add to cart</p> <p class="text-[15px]">Don't forget to save your customization to be able to add to cart</p>
</div> </div>
<div class="grid grid-cols-3 gap-10"> <div class="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 gap-5 md:gap-10 ">
<UInput label="Podaj kolor kanapy narożnej" placeholder="Podaj kolor kanapy narożnej" class="dark:text-white text-black"/> <UInput label="Podaj kolor kanapy narożnej" placeholder="Podaj kolor kanapy narożnej" class="dark:text-white text-black"/>
<UInput label="Podaj kolor fotela" placeholder="Podaj kolor fotela" class="dark:text-white text-black"/> <UInput label="Podaj kolor fotela" placeholder="Podaj kolor fotela" class="dark:text-white text-black"/>
<UInput label="Podaj kolor kwadratu" placeholder="Podaj kolor kwadratu" class="dark:text-white text-black"/> <UInput label="Podaj kolor kwadratu" placeholder="Podaj kolor kwadratu" class="dark:text-white text-black"/>

View File

@@ -1,8 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import TopBar from '@/components/TopBar.vue'; import TopBar from '@/components/TopBar.vue';
</script> </script>
<template> <template>
<div class="h-screen grid grid-rows-[auto_1fr_auto]"> <div class="h-screen grid grid-rows-[auto_1fr_auto]">
<!-- <header class="w-full bg-gray-100 text-primary shadow border-b-gray-300 p-4 mb-8">Header</header> --> <!-- <header class="w-full bg-gray-100 text-primary shadow border-b-gray-300 p-4 mb-8">Header</header> -->

View File

@@ -9,11 +9,7 @@ function isAuthenticated(): boolean {
if (typeof document === 'undefined') return false if (typeof document === 'undefined') return false
return document.cookie.split('; ').some((c) => c === 'is_authenticated=1') return document.cookie.split('; ').some((c) => c === 'is_authenticated=1')
} }
await getSettings() await getSettings()
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.VITE_BASE_URL), history: createWebHistory(import.meta.env.VITE_BASE_URL),
routes: [ routes: [
@@ -34,6 +30,7 @@ const router = createRouter({
{ path: 'product-card-full/', component: () => import('../components/customer/PageProductCardFull.vue'), name: 'product-card-full' }, { path: 'product-card-full/', component: () => import('../components/customer/PageProductCardFull.vue'), name: 'product-card-full' },
{ path: 'addresses', component: () => import('../components/customer/PageAddresses.vue'), name: 'addresses' }, { path: 'addresses', component: () => import('../components/customer/PageAddresses.vue'), name: 'addresses' },
{ path: 'cart', component: () => import('../components/customer/PageCart.vue'), name: 'cart' }, { path: 'cart', component: () => import('../components/customer/PageCart.vue'), name: 'cart' },
{ path: 'products-list', component: () => import('../components/customer/PageProductsList.vue'), name: 'products-list' },
], ],
}, },
{ {
@@ -55,7 +52,6 @@ const router = createRouter({
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
const locale = to.params.locale as string const locale = to.params.locale as string
const localeLang = langs.find((x) => x.iso_code == locale) const localeLang = langs.find((x) => x.iso_code == locale)
if (locale && langs.length > 0) { if (locale && langs.length > 0) {
const authStore = useAuthStore() const authStore = useAuthStore()
console.log(authStore.isAuthenticated, to, from) console.log(authStore.isAuthenticated, to, from)
@@ -77,7 +73,6 @@ router.beforeEach((to, from, next) => {
if (!locale && to.path !== '/') { if (!locale && to.path !== '/') {
return next(`/${currentLang.value?.iso_code}${to.path}`) return next(`/${currentLang.value?.iso_code}${to.path}`)
} }
next() next()
}) })

View File

@@ -95,7 +95,6 @@ export const useAddressStore = defineStore('address', () => {
id: existing.id, id: existing.id,
...normalize(formData) ...normalize(formData)
} }
return true return true
} }
function deleteAddress(id: number): boolean { function deleteAddress(id: number): boolean {

View File

@@ -63,7 +63,6 @@ const PrivacyComponent = computed(() =>
<UButton @click="showTherms = false" class="mx-auto px-12">{{ $t('general.close') }}</UButton> <UButton @click="showTherms = false" class="mx-auto px-12">{{ $t('general.close') }}</UButton>
</template> </template>
</UDrawer> </UDrawer>
<!-- PrivacyPolicyView -->
<UDrawer v-model:open="showPrivacy" :overlay="false"> <UDrawer v-model:open="showPrivacy" :overlay="false">
<template #body> <template #body>
<component :is="PrivacyComponent" /> <component :is="PrivacyComponent" />
@@ -76,9 +75,9 @@ const PrivacyComponent = computed(() =>
<div class="text-center mb-15"> <div class="text-center mb-15">
<div <div
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30"> class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
<UIcon name="i-heroicons-clock" class="w-8 h-8" /> <UIcon name="carbon:ibm-webmethods-b2b-integration" class="w-8 h-8" />
</div> </div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1> <h1 class="text-3xl font-bold text-gray-900 dark:text-white">B2B</h1>
</div> </div>
<div class="w-full max-w-md flex flex-col gap-4"> <div class="w-full max-w-md flex flex-col gap-4">
<UForm :validate="validate" @submit="handleLogin" class="space-y-5"> <UForm :validate="validate" @submit="handleLogin" class="space-y-5">
@@ -94,11 +93,12 @@ const PrivacyComponent = computed(() =>
<UFormField :label="$t('general.password')" name="password" required class="w-full dark:text-white text-black"> <UFormField :label="$t('general.password')" name="password" required class="w-full dark:text-white text-black">
<UInput v-model="password" :placeholder="$t('general.enter_your_password')" <UInput v-model="password" :placeholder="$t('general.enter_your_password')"
:type="showPassword ? 'text' : 'password'" class="w-full placeholder:text-(--placeholder)" :ui="{ trailing: 'pe-1' }"> :type="showPassword ? 'text' : 'password'" class="w-full placeholder:text-(--placeholder)"
:ui="{ trailing: 'pe-1' }">
<template #trailing> <template #trailing>
<UIcon color="neutral" variant="link" size="sm" :name="showPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'" <UIcon color="neutral" variant="link" size="sm" :name="showPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'"
:aria-label="showPassword ? 'Hide password' : 'Show password'" :aria-pressed="showPassword" :aria-label="showPassword ? 'Hide password' : 'Show password'" :aria-pressed="showPassword"
aria-controls="password" @click="showPassword = !showPassword" class="mr-2"/> aria-controls="password" @click="showPassword = !showPassword" class="mr-2" />
</template> </template>
</UInput> </UInput>
</UFormField> </UFormField>
@@ -114,15 +114,11 @@ const PrivacyComponent = computed(() =>
{{ $t('general.sign_in') }} {{ $t('general.sign_in') }}
</UButton> </UButton>
</UForm> </UForm>
<!-- Divider -->
<div class="flex items-center gap-3 my-1"> <div class="flex items-center gap-3 my-1">
<div class="flex-1 h-px bg-gray-200 dark:dark:hover:bg-(--gray-dark)" /> <div class="flex-1 h-px bg-gray-200 dark:dark:hover:bg-(--gray-dark)" />
<span class="text-xs text-gray-400 dark:text-gray-500">{{ $t('general.or') }}</span> <span class="text-xs text-gray-400 dark:text-gray-500">{{ $t('general.or') }}</span>
<div class="flex-1 h-px bg-gray-200 dark:dark:hover:bg-(--gray-dark)" /> <div class="flex-1 h-px bg-gray-200 dark:dark:hover:bg-(--gray-dark)" />
</div> </div>
<!-- Google Sign In -->
<UButton type="button" color="neutral" variant="outline" size="lg" block :disabled="authStore.loading" <UButton type="button" color="neutral" variant="outline" size="lg" block :disabled="authStore.loading"
@click="authStore.loginWithGoogle()" @click="authStore.loginWithGoogle()"
class="flex items-center justify-center gap-2 dark:text-white text-black cursor-pointer"> class="flex items-center justify-center gap-2 dark:text-white text-black cursor-pointer">

View File

@@ -40,9 +40,9 @@ function validate(): FormError[] {
<div class="text-center mb-15"> <div class="text-center mb-15">
<div <div
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30"> class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
<UIcon name="i-heroicons-clock" class="w-8 h-8" /> <UIcon name="carbon:ibm-webmethods-b2b-integration" class="w-8 h-8" />
</div> </div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1> <h1 class="text-3xl font-bold text-gray-900 dark:text-white">B2B</h1>
</div> </div>
<div class="w-full max-w-md flex flex-col gap-4"> <div class="w-full max-w-md flex flex-col gap-4">

View File

@@ -21,9 +21,9 @@
<div class="text-center mb-15"> <div class="text-center mb-15">
<div <div
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30"> class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
<UIcon name="i-heroicons-clock" class="w-8 h-8" /> <UIcon name="carbon:ibm-webmethods-b2b-integration" class="w-8 h-8" />
</div> </div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1> <h1 class="text-3xl font-bold text-gray-900 dark:text-white">B2B</h1>
</div> </div>
<div class="w-full max-w-md"> <div class="w-full max-w-md">
<UForm :validate="validate" @submit="handleRegister" class="flex flex-col gap-3"> <UForm :validate="validate" @submit="handleRegister" class="flex flex-col gap-3">

View File

@@ -181,7 +181,7 @@ const columns = computed<TableColumn<IssueTimeSummary>[]>(() => [
</script> </script>
<template> <template>
<div class="container"> <div class="container mx-auto">
<div class="p-6 bg-(--main-light) dark:bg-(--black) font-sans"> <div class="p-6 bg-(--main-light) dark:bg-(--black) font-sans">
<h1 class="text-2xl font-bold mb-6 text-black dark:text-white">{{ $t('repo_chart.repository_work_chart') }} <h1 class="text-2xl font-bold mb-6 text-black dark:text-white">{{ $t('repo_chart.repository_work_chart') }}
</h1> </h1>

View File

@@ -53,9 +53,9 @@ function validate(): FormError[] {
<div class="text-center mb-15"> <div class="text-center mb-15">
<div <div
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30"> class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
<UIcon name="i-heroicons-clock" class="w-8 h-8" /> <UIcon name="carbon:ibm-webmethods-b2b-integration" class="w-8 h-8" />
</div> </div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1> <h1 class="text-3xl font-bold text-gray-900 dark:text-white">B2B</h1>
</div> </div>
<div class="w-full max-w-md flex flex-col gap-4"> <div class="w-full max-w-md flex flex-col gap-4">

View File

@@ -73,9 +73,9 @@ function goToLogin() {
<div class="text-center mb-8"> <div class="text-center mb-8">
<div <div
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30"> class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
<UIcon name="i-heroicons-clock" class="w-8 h-8" /> <UIcon name="carbon:ibm-webmethods-b2b-integration" class="w-8 h-8" />
</div> </div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1> <h1 class="text-3xl font-bold text-gray-900 dark:text-white">B2B</h1>
</div> </div>
<UCard class="shadow-xl shadow-gray-200/50 dark:shadow-gray-900/50"> <UCard class="shadow-xl shadow-gray-200/50 dark:shadow-gray-900/50">