Merge branch 'front-styles' of ssh://git.ma-al.com:8822/goc_daniel/b2b into translate

This commit is contained in:
2026-04-15 13:47:29 +02:00
4 changed files with 213 additions and 273 deletions

View File

@@ -1,39 +1,13 @@
<template>
<div class="flex flex-col gap-5 md:gap-10">
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Shopping Cart') }}</h1>
<div class="flex flex-col lg:flex-row gap-5 md:gap-10">
<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">
<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') }}
</h2>
<div v-if="cartStore.items.length > 0">
<div v-for="item in cartStore.items" :key="item.id"
class="grid grid-cols-5 items-center p-4 border-b border-(--border-light) dark:border-(--border-dark) w-[100%]">
<div
class="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" />
<UIcon v-else name="mdi:package-variant" class="text-2xl text-gray-400" />
</div>
</div>
<div class="w-full">
<div
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') }}
</h2>
<div v-if="cartStore.items.length > 0">
<div v-for="item in cartStore.items" :key="item.id"
class="grid grid-cols-5 items-center p-4 border-b border-(--border-light) dark:border-(--border-dark) w-[100%]">
<div
class="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" />
<UIcon v-else name="mdi:package-variant" class="text-2xl text-gray-400" />
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Shopping Carts') }}</h1>
<div class="flex gap-3">
<UButton color="primary" @click="showCreateModal = true"
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
<UIcon name="mdi:plus" class="mr-1" />
{{ t('New Cart') }}
</UButton>
</div>
</div>
@@ -64,8 +38,7 @@
</div>
</div>
<div v-else class="p-8 text-center">
<UIcon name="mdi:cart-outline"
class="text-4xl text-gray-300 dark:text-gray-600 mb-2" />
<UIcon name="mdi:cart-outline" class="text-4xl text-gray-300 dark:text-gray-600 mb-2" />
<p class="text-gray-500 dark:text-gray-400">{{ t('No carts yet') }}</p>
</div>
</div>
@@ -94,32 +67,21 @@
</div>
</template>
</UModal>
</div>
</div>
</template>
<template #footer>
<div class="flex justify-end gap-2">
<UButton variant="outline" color="neutral" @click="showCreateModal = false">
{{ t('Cancel') }}
</UButton>
<UButton color="primary" @click="createCart" :disabled="!newCartName.trim()"
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white">
{{ t('Create') }}
</UButton>
</div>
</component>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useCartStore } from '@/stores/customer/cart'
import { useI18n } from 'vue-i18n'
import Default from '@/layouts/default.vue'
import { useRouter } from 'vue-router'
import { useAddressStore } from '@/stores/customer/address'
const cartStore = useCartStore()
const addressStore = useAddressStore()
const { t } = useI18n()
const router = useRouter()
const cartStore = useCartStore()
const { t } = useI18n()
const showCreateModal = ref(false)
const newCartName = ref('')

View File

@@ -39,9 +39,10 @@ function adaptMenu(menu: NavigationMenuItem[]) {
if (item.children && item.children.length > 0) {
item.open = path && path.includes(item.category_id) ? true : openAll.value
adaptMenu(item.children);
item.children.unshift({
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
name: 'admin-products-category', params: {
name: item.params.to, params: {
category_id: item.params.category_id,
link_rewrite: item.params.link_rewrite
}
@@ -49,7 +50,7 @@ function adaptMenu(menu: NavigationMenuItem[]) {
})
} else {
item.to = {
name: 'admin-products-category', params: {
name: item.params.to, params: {
category_id: item.params.category_id,
link_rewrite: item.params.link_rewrite
}

View File

@@ -1,5 +1,7 @@
import { useFetchJson } from '@/composable/useFetchJson'
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { AddressTemplate } from './cart'
export interface AddressFormData {
street: string
@@ -17,7 +19,6 @@ export interface Address {
}
export const useAddressStore = defineStore('address', () => {
const addresses = ref<Address[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
@@ -50,11 +51,6 @@ export const useAddressStore = defineStore('address', () => {
const totalItems = computed(() => filteredAddresses.value.length)
const totalPages = computed(() => Math.ceil(totalItems.value / pageSize))
const paginatedAddresses = computed(() => {
const start = (currentPage.value - 1) * pageSize
return filteredAddresses.value.slice(start, start + pageSize)
})
function getAddressById(id: number) {
return addresses.value.find(addr => addr.id === id)
}
@@ -72,41 +68,6 @@ export const useAddressStore = defineStore('address', () => {
return Math.max(0, ...addresses.value.map(a => a.id)) + 1
}
function addAddress(formData: AddressFormData): Address {
const newAddress: Address = {
id: generateId(),
...normalize(formData)
}
addresses.value.unshift(newAddress)
resetPagination()
return newAddress
}
function updateAddress(id: number, formData: AddressFormData): boolean {
const index = addresses.value.findIndex(a => a.id === id)
if (index === -1) return false
const existing = addresses.value[index]
if (!existing) return false
addresses.value[index] = {
id: existing.id,
...normalize(formData)
}
return true
}
function deleteAddress(id: number): boolean {
const index = addresses.value.findIndex(a => a.id === id)
if (index === -1) return false
addresses.value.splice(index, 1)
resetPagination()
return true
}
function setPage(page: number) {
currentPage.value = page
}
@@ -122,6 +83,121 @@ export const useAddressStore = defineStore('address', () => {
initMockData()
const addresses = ref<Address[]>([])
const addressLoading = ref(false)
const addressError = ref<string | null>(null)
const addressSearchQuery = ref('')
const addressCurrentPage = ref(1)
const addressPageSize = 20
const addressTotalCount = ref(0)
function transformAddressResponse(address: any): Address {
const info = address.address_info || address.addressInfo || {}
return {
id: address.id ?? 0,
country_id: address.country_id ?? address.countryId ?? 1,
customer_id: address.customer_id ?? address.customerId ?? 0,
address_info: info as Record<string, string>
}
}
async function fetchAddresses() {
addressLoading.value = true
addressError.value = null
try {
const queryParam = addressSearchQuery.value ? `&query=${encodeURIComponent(addressSearchQuery.value)}` : ''
const response = await useFetchJson<Address[]>(`/api/v1/restricted/addresses/retrieve-addresses?page=${addressCurrentPage.value}&elems=${addressPageSize}${queryParam}`)
addresses.value = response.items || []
addressTotalCount.value = response.count ?? addresses.value.length
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to load addresses'
} finally {
addressLoading.value = false
}
}
async function addAddress(countryId: number, formData: AddressTemplate): Promise<Address | null> {
addressLoading.value = true
addressError.value = null
try {
const response = await useFetchJson<any>(`/api/v1/restricted/addresses/add-new-address?country_id=${countryId}`, {
method: 'POST',
body: JSON.stringify(formData)
})
await fetchAddresses()
return transformAddressResponse(response.items ?? response)
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to create address'
return null
} finally {
addressLoading.value = false
}
}
async function getAddressTemplate(countryId: number): Promise<AddressTemplate> {
const response = await useFetchJson<any>(`/api/v1/restricted/addresses/get-template?country_id=${countryId}`)
return response.items ?? {}
}
async function updateAddress(id: number, countryId: number, formData: AddressTemplate): Promise<boolean> {
addressLoading.value = true
addressError.value = null
try {
await useFetchJson<any>(`/api/v1/restricted/addresses/modify-address?country_id=${countryId}&address_id=${id}`, {
method: 'POST',
body: JSON.stringify(formData)
})
await fetchAddresses()
return true
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to update address'
return false
} finally {
addressLoading.value = false
}
}
async function deleteAddress(id: number): Promise<boolean> {
addressLoading.value = true
addressError.value = null
try {
await useFetchJson<any>(`/api/v1/restricted/addresses/delete-address?address_id=${id}`, {
method: 'DELETE'
})
await fetchAddresses()
return true
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to delete address'
return false
} finally {
addressLoading.value = false
}
}
const totalAddressItems = computed(() => addressTotalCount.value || addresses.value.length)
const totalAddressPages = computed(() => Math.ceil(totalAddressItems.value / addressPageSize))
const paginatedAddresses = computed(() => addresses.value)
function setAddressPage(page: number) {
addressCurrentPage.value = page
return fetchAddresses()
}
function setAddressSearchQuery(query: string) {
addressSearchQuery.value = query
addressCurrentPage.value = 1
return fetchAddresses()
}
return {
addresses,
loading,
@@ -139,6 +215,17 @@ export const useAddressStore = defineStore('address', () => {
deleteAddress,
setPage,
setSearchQuery,
resetPagination
resetPagination,
addressLoading,
addressError,
addressSearchQuery,
addressCurrentPage,
addressPageSize,
totalAddressItems,
totalAddressPages,
fetchAddresses,
setAddressPage,
setAddressSearchQuery,
getAddressTemplate
}
})

View File

@@ -24,121 +24,18 @@ export const useCartStore = defineStore('cart', () => {
const activeCartId = ref<number | null>(null)
const error = ref<string | null>(null)
const addresses = ref<Address[]>([])
const addressLoading = ref(false)
const addressError = ref<string | null>(null)
const addressSearchQuery = ref('')
const addressCurrentPage = ref(1)
const addressPageSize = 20
const addressTotalCount = ref(0)
function transformAddressResponse(address: any): Address {
const info = address.address_info || address.addressInfo || {}
return {
id: address.id ?? 0,
country_id: address.country_id ?? address.countryId ?? 1,
customer_id: address.customer_id ?? address.customerId ?? 0,
address_info: info as Record<string, string>
}
}
async function fetchAddresses() {
addressLoading.value = true
addressError.value = null
async function fetchCarts() {
try {
const queryParam = addressSearchQuery.value ? `&query=${encodeURIComponent(addressSearchQuery.value)}` : ''
const response = await useFetchJson<Address[]>(`/api/v1/restricted/addresses/retrieve-addresses?page=${addressCurrentPage.value}&elems=${addressPageSize}${queryParam}`)
addresses.value = response.items || []
addressTotalCount.value = response.count ?? addresses.value.length
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to load addresses'
} finally {
addressLoading.value = false
const res = await useFetchJson<ApiResponse>(
`/api/v1/restricted/carts/retrieve-carts-info`
)
carts.value = res.items
} catch (e: any) {
error.value = e?.message ?? 'Error loading carts'
}
}
async function addAddress(countryId: number, formData: AddressTemplate): Promise<Address | null> {
addressLoading.value = true
addressError.value = null
try {
const response = await useFetchJson<any>(`/api/v1/restricted/addresses/add-new-address?country_id=${countryId}`, {
method: 'POST',
body: JSON.stringify(formData)
})
await fetchAddresses()
return transformAddressResponse(response.items ?? response)
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to create address'
return null
} finally {
addressLoading.value = false
}
}
async function getAddressTemplate(countryId: number): Promise<AddressTemplate> {
const response = await useFetchJson<any>(`/api/v1/restricted/addresses/get-template?country_id=${countryId}`)
return response.items ?? {}
}
async function updateAddress(id: number, countryId: number, formData: AddressTemplate): Promise<boolean> {
addressLoading.value = true
addressError.value = null
try {
await useFetchJson<any>(`/api/v1/restricted/addresses/modify-address?country_id=${countryId}&address_id=${id}`, {
method: 'POST',
body: JSON.stringify(formData)
})
await fetchAddresses()
return true
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to update address'
return false
} finally {
addressLoading.value = false
}
}
async function deleteAddress(id: number): Promise<boolean> {
addressLoading.value = true
addressError.value = null
try {
await useFetchJson<any>(`/api/v1/restricted/addresses/delete-address?address_id=${id}`, {
method: 'DELETE'
})
await fetchAddresses()
return true
} catch (error: unknown) {
addressError.value = error instanceof Error ? error.message : 'Failed to delete address'
return false
} finally {
addressLoading.value = false
}
}
const totalAddressItems = computed(() => addressTotalCount.value || addresses.value.length)
const totalAddressPages = computed(() => Math.ceil(totalAddressItems.value / addressPageSize))
const paginatedAddresses = computed(() => addresses.value)
function setAddressPage(page: number) {
addressCurrentPage.value = page
return fetchAddresses()
}
function setAddressSearchQuery(query: string) {
addressSearchQuery.value = query
addressCurrentPage.value = 1
return fetchAddresses()
}
async function addNewCart(name: string) {
try {
error.value = null
@@ -205,22 +102,15 @@ export const useCartStore = defineStore('cart', () => {
})
return {
addresses,
addressLoading,
addressError,
addressSearchQuery,
addressCurrentPage,
addressPageSize,
paginatedAddresses,
totalAddressItems,
totalAddressPages,
fetchAddresses,
addAddress,
updateAddress,
deleteAddress,
setAddressPage,
setAddressSearchQuery,
getAddressTemplate,
initCart
carts,
activeCartId,
error,
errorMessage,
activeCart,
setActiveCart,
addProduct,
fetchCarts,
addNewCart,
initCart,
}
})