fix: menu and routing
This commit is contained in:
4
bo/components.d.ts
vendored
4
bo/components.d.ts
vendored
@@ -15,7 +15,7 @@ declare module 'vue' {
|
|||||||
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
|
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
|
||||||
CategoryMenu: typeof import('./src/components/inner/categoryMenu.vue')['default']
|
CategoryMenu: typeof import('./src/components/inner/categoryMenu.vue')['default']
|
||||||
CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default']
|
CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default']
|
||||||
copy: typeof import('./src/components/inner/categoryMenu copy.vue')['default']
|
copy: typeof import('./src/components/customer/PageOrders copy.vue')['default']
|
||||||
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
|
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
|
||||||
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
|
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
|
||||||
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']
|
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']
|
||||||
@@ -27,12 +27,14 @@ declare module 'vue' {
|
|||||||
PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default']
|
PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default']
|
||||||
PageCreateAccount: typeof import('./src/components/customer/PageCreateAccount.vue')['default']
|
PageCreateAccount: typeof import('./src/components/customer/PageCreateAccount.vue')['default']
|
||||||
PageCustomerData: typeof import('./src/components/customer/PageCustomerData.vue')['default']
|
PageCustomerData: typeof import('./src/components/customer/PageCustomerData.vue')['default']
|
||||||
|
PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default']
|
||||||
PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default']
|
PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default']
|
||||||
PageProductCardFull: typeof import('./src/components/customer/PageProductCardFull.vue')['default']
|
PageProductCardFull: typeof import('./src/components/customer/PageProductCardFull.vue')['default']
|
||||||
PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default']
|
PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default']
|
||||||
PageProductsList: typeof import('./src/components/admin/PageProductsList.vue')['default']
|
PageProductsList: typeof import('./src/components/admin/PageProductsList.vue')['default']
|
||||||
PageProfileDetails: typeof import('./src/components/customer/PageProfileDetails.vue')['default']
|
PageProfileDetails: typeof import('./src/components/customer/PageProfileDetails.vue')['default']
|
||||||
PageProfileDetailsAddInfo: typeof import('./src/components/customer/PageProfileDetailsAddInfo.vue')['default']
|
PageProfileDetailsAddInfo: typeof import('./src/components/customer/PageProfileDetailsAddInfo.vue')['default']
|
||||||
|
PageStatistic: typeof import('./src/components/customer/PageStatistic.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']
|
||||||
|
|||||||
@@ -1,63 +1,63 @@
|
|||||||
import type { NuxtUIOptions } from '@nuxt/ui/unplugin'
|
// import type { NuxtUIOptions } from '@nuxt/ui/unplugin'
|
||||||
|
|
||||||
export const uiOptions: NuxtUIOptions = {
|
// export const uiOptions: NuxtUIOptions = {
|
||||||
ui: {
|
// ui: {
|
||||||
pagination: {
|
// pagination: {
|
||||||
slots: {
|
// slots: {
|
||||||
root: '',
|
// root: '',
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
button: {
|
// button: {
|
||||||
slots: {
|
// slots: {
|
||||||
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
input: {
|
// input: {
|
||||||
slots: {
|
// slots: {
|
||||||
base: 'text-(--black) dark:text-white border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
// base: 'text-(--black) dark:text-white border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
||||||
error: 'text-red-600!'
|
// error: 'text-red-600!'
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
inputNumber: {
|
// inputNumber: {
|
||||||
slots: {
|
// slots: {
|
||||||
base: 'text-(--black) dark:text-white border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! pt-2 px-1! w-auto!',
|
// base: 'text-(--black) dark:text-white border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! pt-2 px-1! w-auto!',
|
||||||
increment: 'border-0! pe-0! ps-0!',
|
// increment: 'border-0! pe-0! ps-0!',
|
||||||
decrement: 'border-0! pe-0! ps-0!'
|
// decrement: 'border-0! pe-0! ps-0!'
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
select: {
|
// select: {
|
||||||
slots: {
|
// slots: {
|
||||||
base: 'w-full! cursor-pointer border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
// base: 'w-full! cursor-pointer border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
||||||
itemLabel: 'text-black! dark:text-white!',
|
// itemLabel: 'text-black! dark:text-white!',
|
||||||
itemTrailingIcon: 'text-black! dark:text-white!'
|
// itemTrailingIcon: 'text-black! dark:text-white!'
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
formField: {
|
// formField: {
|
||||||
slots: {
|
// slots: {
|
||||||
error: 'mt-1! text-[14px] text-error text-red-600! dark:text-red-400!',
|
// error: 'mt-1! text-[14px] text-error text-red-600! dark:text-red-400!',
|
||||||
label: 'text-[16px]'
|
// label: 'text-[16px]'
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
selectMenu: {
|
// selectMenu: {
|
||||||
slots: {
|
// slots: {
|
||||||
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
||||||
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
|
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
|
||||||
itemLeadingIcon: 'text-(--black)! dark:text-white!'
|
// itemLeadingIcon: 'text-(--black)! dark:text-white!'
|
||||||
}
|
// }
|
||||||
|
|
||||||
},
|
// },
|
||||||
table: {
|
// table: {
|
||||||
slots: {
|
// slots: {
|
||||||
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
||||||
tr: 'border-b! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! text-(--black)! dark:text-white!',
|
// tr: 'border-b! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! text-(--black)! dark:text-white!',
|
||||||
}
|
// }
|
||||||
|
|
||||||
},
|
// },
|
||||||
modal: {
|
// modal: {
|
||||||
slots: {
|
// slots: {
|
||||||
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
@@ -3,10 +3,11 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
|||||||
import LangSwitch from './inner/langSwitch.vue'
|
import LangSwitch from './inner/langSwitch.vue'
|
||||||
import ThemeSwitch from './inner/themeSwitch.vue'
|
import ThemeSwitch from './inner/themeSwitch.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { currentLang } from '@/router/langs'
|
import { currentLang } from '@/router/langs'
|
||||||
import type { LabelTrans, TopMenuItem } from '@/types'
|
import type { LabelTrans, TopMenuItem } from '@/types'
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui'
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
let menu = ref()
|
let menu = ref()
|
||||||
@@ -19,30 +20,43 @@ async function getTopMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const menuItems = computed(() => transformMenu(menu.value[0].children, currentLang.value?.iso_code))
|
const menuItems = computed(() => transformMenu(menu.value[0].children, currentLang.value?.iso_code))
|
||||||
function transformMenu(items: TopMenuItem[], locale: string | undefined): NavigationMenuItem[] {
|
function transformMenu(items: TopMenuItem[], locale: string | undefined): NavigationMenuItem[] {
|
||||||
|
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
let route = {
|
const route: NavigationMenuItem = {
|
||||||
icon: 'i-lucide-house',
|
icon: item.label.icon ? item.label.icon : 'i-lucide-house',
|
||||||
label: item.label.trans[locale as keyof LabelTrans].label,
|
label: item.label.trans[locale as keyof LabelTrans].label,
|
||||||
children: item.children ? transformMenu(item.children, locale) : undefined,
|
children: item.children
|
||||||
|
? transformMenu(item.children, locale)
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
route.onSelect = () => {
|
||||||
|
const query = {
|
||||||
|
name: item.params.route.name,
|
||||||
|
params: {
|
||||||
|
...(item.params.route.params || {}),
|
||||||
|
locale: currentLang.value?.iso_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(query)
|
||||||
}
|
}
|
||||||
if (item.params?.route) {
|
|
||||||
route = { ...route, ...{ to: { name: item.params.route.name, params: { locale: locale } } } }
|
|
||||||
}
|
|
||||||
|
|
||||||
return route
|
return route
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await getTopMenu()
|
await getTopMenu()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{ menuItems }}
|
<header
|
||||||
<!-- fixed top-0 left-0 right-0 z-50 -->
|
class="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
||||||
<header class="bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
<!-- px-4 sm:px-6 lg:px-8 -->
|
||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex items-center justify-between h-14">
|
<div class="flex items-center justify-between h-14">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
||||||
@@ -52,49 +66,18 @@ await getTopMenu()
|
|||||||
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
<UNavigationMenu :items="menuItems" class="w-full" />
|
<UNavigationMenu :type="'trigger'" :ui="{
|
||||||
|
root: 'justify-center',
|
||||||
<!-- {{ router }} -->
|
list: 'gap-4'
|
||||||
<!-- <RouterLink :to="{ name: 'admin-products' }">
|
}" :items="menuItems" class="w-full"></UNavigationMenu>
|
||||||
products list
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'admin-product-detail' }">
|
|
||||||
product detail
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{
|
|
||||||
name: 'customer-product', params: {
|
|
||||||
product_id: '51'
|
|
||||||
}
|
|
||||||
}">
|
|
||||||
Product (51)
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'addresses' }">
|
|
||||||
Addresses
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'profile-details' }">
|
|
||||||
Customer Data
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'cart' }">
|
|
||||||
Cart
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{
|
|
||||||
name: 'cart-details', params: {
|
|
||||||
cart_id: '1'
|
|
||||||
}
|
|
||||||
}">
|
|
||||||
Cart details (1)
|
|
||||||
</RouterLink> -->
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<!-- Language Switcher -->
|
<!-- Language Switcher -->
|
||||||
<LangSwitch />
|
<LangSwitch />
|
||||||
<!-- Theme Switcher -->
|
<!-- Theme Switcher -->
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
<!-- Logout Button (only when authenticated) -->
|
<!-- Logout Button (only when authenticated) -->
|
||||||
<button
|
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
||||||
v-if="authStore.isAuthenticated"
|
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark)">
|
||||||
@click="authStore.logout()"
|
|
||||||
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark)"
|
|
||||||
>
|
|
||||||
{{ $t('general.logout') }}
|
{{ $t('general.logout') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<div class="container mx-auto">
|
||||||
<h2
|
<h2
|
||||||
class="font-semibold text-black dark:text-white pb-6 text-2xl">
|
class="font-semibold text-black dark:text-white pb-6 text-2xl">
|
||||||
{{ t('Cart Items') }}
|
{{ t('Cart Items') }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<div class="container mx-auto">
|
||||||
<div class="flex flex-col gap-5 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 md:flex-row flex-col justify-between items-start md:items-center gap-5 md:gap-0">
|
<div class="flex md:flex-row flex-col justify-between items-start md:items-center gap-5 md:gap-0">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20 flex flex-col gap-5 md:gap-10">
|
<div class="container mx-auto flex flex-col gap-5 md:gap-10">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Shopping Cart') }}</h1>
|
<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 flex-col lg:flex-row gap-5 md:gap-10">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|||||||
9
bo/src/components/customer/PageOrders.vue
Normal file
9
bo/src/components/customer/PageOrders.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
Orders page
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Default from '@/layouts/default.vue'
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mt-20 mx-auto">
|
<div class="container mx-auto">
|
||||||
<div class="flex md:flex-row flex-col justify-between gap-8 my-6">
|
<div class="flex md:flex-row flex-col justify-between gap-8 my-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] ">
|
||||||
|
|||||||
430
bo/src/components/customer/PageProducts.vue
Normal file
430
bo/src/components/customer/PageProducts.vue
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
<template>
|
||||||
|
<suspense>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
||||||
|
<template #item="{ item, active }">
|
||||||
|
<div class="flex items-center gap-2 px-3 py-2">
|
||||||
|
<UIcon name="i-heroicons-book-open" />
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UNavigationMenu> -->
|
||||||
|
<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">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<CategoryMenuListing />
|
||||||
|
<UTable :data="productsList" :columns="columns" class="flex-1">
|
||||||
|
<template #expanded="{ row }">
|
||||||
|
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
||||||
|
thead: 'hidden'
|
||||||
|
}" />
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-center py-8">
|
||||||
|
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
|
||||||
|
</div>
|
||||||
|
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||||
|
No products found
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
</suspense>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, h, resolveComponent, computed } from 'vue'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import Default from '@/layouts/default.vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
|
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
reference: number
|
||||||
|
product_id: number
|
||||||
|
name: string
|
||||||
|
image_link: string
|
||||||
|
link_rewrite: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const page = computed({
|
||||||
|
get: () => Number(route.query.page) || 1,
|
||||||
|
set: (val: number) => {
|
||||||
|
router.push({
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
page: val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const sortField = computed({
|
||||||
|
get: () => [
|
||||||
|
route.query.sort as string | undefined,
|
||||||
|
route.query.direction as 'asc' | 'desc' | undefined
|
||||||
|
],
|
||||||
|
|
||||||
|
set: ([sort]: [string, 'asc' | 'desc']) => {
|
||||||
|
const currentSort = route.query.sort as string | undefined
|
||||||
|
const currentDirection = route.query.direction as 'asc' | 'desc' | undefined
|
||||||
|
|
||||||
|
let query = { ...route.query }
|
||||||
|
|
||||||
|
if (currentSort === sort) {
|
||||||
|
if (currentDirection === 'asc') {
|
||||||
|
query.direction = 'desc'
|
||||||
|
} else if (currentDirection === 'desc') {
|
||||||
|
delete query.sort
|
||||||
|
delete query.direction
|
||||||
|
} else {
|
||||||
|
query.direction = 'asc'
|
||||||
|
query.sort = sort
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query.sort = sort
|
||||||
|
query.direction = 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({ query })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const perPage = ref(15)
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
interface ApiResponse {
|
||||||
|
message: string
|
||||||
|
items: Product[]
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const productsList = ref<Product[]>([])
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const filters = computed<Record<string, string>>({
|
||||||
|
get: () => {
|
||||||
|
const q = { ...route.query }
|
||||||
|
delete q.page
|
||||||
|
delete q.sort
|
||||||
|
delete q.direction
|
||||||
|
return q as Record<string, string>
|
||||||
|
},
|
||||||
|
set: (val) => {
|
||||||
|
const baseQuery = { ...route.query }
|
||||||
|
|
||||||
|
Object.keys(baseQuery).forEach(key => {
|
||||||
|
if (!['page', 'sort', 'direction'].includes(key)) {
|
||||||
|
delete baseQuery[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
query: {
|
||||||
|
...baseQuery,
|
||||||
|
...val,
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function debounce(fn: Function, delay = 400) {
|
||||||
|
let t: any
|
||||||
|
return (...args: any[]) => {
|
||||||
|
clearTimeout(t)
|
||||||
|
t = setTimeout(() => fn(...args), delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFilter = debounce((columnId: string, val: string) => {
|
||||||
|
const newFilters = { ...filters.value }
|
||||||
|
|
||||||
|
if (val) newFilters[columnId] = val
|
||||||
|
else delete newFilters[columnId]
|
||||||
|
|
||||||
|
filters.value = newFilters
|
||||||
|
}, 400)
|
||||||
|
|
||||||
|
async function fetchProductList() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
|
Object.entries(route.query).forEach(([key, value]) => {
|
||||||
|
if (value) params.append(key, String(value))
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = `/api/v1/restricted/list-products/get-listing?${params}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
|
productsList.value = response.items || []
|
||||||
|
total.value = response.count || 0
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToProduct(productId: number) {
|
||||||
|
router.push({
|
||||||
|
name: 'product-detail',
|
||||||
|
params: { id: productId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedCount = ref({
|
||||||
|
product_id: null,
|
||||||
|
count: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
function getIcon(name: string) {
|
||||||
|
if (sortField.value[0] === name) {
|
||||||
|
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
||||||
|
if (sortField.value[1] === 'desc') return 'i-lucide-arrow-down-wide-narrow'
|
||||||
|
}
|
||||||
|
return 'i-lucide-arrow-up-down'
|
||||||
|
}
|
||||||
|
|
||||||
|
const UInputNumber = resolveComponent('UInputNumber')
|
||||||
|
const UInput = resolveComponent('UInput')
|
||||||
|
const UButton = resolveComponent('UButton')
|
||||||
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
|
||||||
|
const columns: TableColumn<Payment>[] = [
|
||||||
|
{
|
||||||
|
id: 'expand',
|
||||||
|
cell: ({ row }) =>
|
||||||
|
h(UButton, {
|
||||||
|
color: 'neutral',
|
||||||
|
variant: 'ghost',
|
||||||
|
icon: 'i-lucide-chevron-down',
|
||||||
|
square: true,
|
||||||
|
'aria-label': 'Expand',
|
||||||
|
ui: {
|
||||||
|
leadingIcon: [
|
||||||
|
'transition-transform',
|
||||||
|
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
|
||||||
|
]
|
||||||
|
},
|
||||||
|
onClick: () => row.toggleExpanded()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'product_id',
|
||||||
|
header: ({ column }) => {
|
||||||
|
return h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => {
|
||||||
|
sortField.value = ['product_id', 'asc']
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h('span', 'ID'),
|
||||||
|
h(UIcon, {
|
||||||
|
name: getIcon('product_id')
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => {
|
||||||
|
updateFilter(column.id, val)
|
||||||
|
},
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
},
|
||||||
|
// header: '#',
|
||||||
|
cell: ({ row }) => `#${row.getValue('product_id') as number}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'image_link',
|
||||||
|
header: 'Image',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h('img', {
|
||||||
|
src: row.getValue('image_link') as string,
|
||||||
|
style: 'width:40px;height:40px;object-fit:cover;'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: ({ column }) => {
|
||||||
|
return h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => {
|
||||||
|
sortField.value = ['name', 'asc']
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h('span', 'Name'),
|
||||||
|
h(UIcon, {
|
||||||
|
name: getIcon('name')
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => {
|
||||||
|
updateFilter(column.id, val)
|
||||||
|
},
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
},
|
||||||
|
cell: ({ row }) => row.getValue('name') as string,
|
||||||
|
filterFn: (row, columnId, value) => {
|
||||||
|
const name = row.getValue(columnId) as string
|
||||||
|
return name.toLowerCase().includes(value.toLowerCase())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'quantity',
|
||||||
|
header: ({ column }) => {
|
||||||
|
return h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => {
|
||||||
|
sortField.value = ['quantity', 'asc']
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h('span', 'In stock'),
|
||||||
|
h(UIcon, {
|
||||||
|
name: getIcon('quantity')
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
cell: ({ row }) => row.getValue('quantity') as number
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: 'Count',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UInputNumber, {
|
||||||
|
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
||||||
|
'onUpdate:modelValue': (val: number) => {
|
||||||
|
if (val)
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: row.original.product_id,
|
||||||
|
count: val
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: null,
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
max: row.original.quantity
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
onClick: () => {
|
||||||
|
console.log('Clicked', row.original)
|
||||||
|
},
|
||||||
|
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||||
|
disabled: selectedCount.value.product_id !== row.original.product_id,
|
||||||
|
variant: 'solid'
|
||||||
|
}, 'Add to cart')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const columnsChild: TableColumn<Payment>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'product_id',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => `#${row.getValue('product_id') as number}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'image_link',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h('img', {
|
||||||
|
src: row.getValue('image_link') as string,
|
||||||
|
style: 'width:40px;height:40px;object-fit:cover;'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => row.getValue('name') as string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'quantity',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => row.getValue('quantity') as number
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UInputNumber, {
|
||||||
|
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
||||||
|
'onUpdate:modelValue': (val: number) => {
|
||||||
|
if (val)
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: row.original.product_id,
|
||||||
|
count: val
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: null,
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
max: row.original.quantity
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
onClick: () => {
|
||||||
|
console.log('Clicked', row.original)
|
||||||
|
},
|
||||||
|
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||||
|
disabled: selectedCount.value.product_id !== row.original.product_id,
|
||||||
|
variant: 'solid'
|
||||||
|
}, 'Add to cart')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
() => {
|
||||||
|
fetchProductList()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<div class="container mx-auto">
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
<div class="flex flex-col gap-5 mb-6">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Customer Data') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Customer Data') }}</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +1,114 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<div class="container mx-auto">
|
||||||
<div class="max-w-2xl mx-auto">
|
<div class="max-w-2xl mx-auto">
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
<div class="flex flex-col gap-5 mb-6">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Create Account') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Create Account') }}</h1>
|
||||||
<div
|
<div
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
||||||
<UForm @submit.prevent="saveAccount" :validate="validate" class="space-y-6">
|
<UForm @submit.prevent="saveAccount" :validate="validate" class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
<h2
|
||||||
<UIcon name="mdi:domain"
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
<UIcon name="mdi:domain"
|
||||||
{{ t('Company Information') }}
|
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
</h2>
|
{{ t('Company Information') }}
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
</h2>
|
||||||
<div class="md:col-span-2">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
<div class="md:col-span-2">
|
||||||
t('Company Name') }} *</label>
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
<UInput v-model="formData.companyName" :placeholder="t('Enter company name')"
|
t('Company Name') }} *</label>
|
||||||
name="companyName" class="w-full" />
|
<UInput v-model="formData.companyName" :placeholder="t('Enter company name')"
|
||||||
</div>
|
name="companyName" class="w-full" />
|
||||||
<div class="md:col-span-2">
|
</div>
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
<div class="md:col-span-2">
|
||||||
t('Company Email') }} *</label>
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
<UInput v-model="formData.companyEmail" type="email"
|
t('Company Email') }} *</label>
|
||||||
:placeholder="t('Enter company email')" name="companyEmail" class="w-full" />
|
<UInput v-model="formData.companyEmail" type="email"
|
||||||
</div>
|
:placeholder="t('Enter company email')" name="companyEmail"
|
||||||
<div>
|
class="w-full" />
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
</div>
|
||||||
t('REGON') }}</label>
|
<div>
|
||||||
<UInput v-model="formData.regon" :placeholder="t('Enter REGON')" name="regon"
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
class="w-full" />
|
t('REGON') }}</label>
|
||||||
</div>
|
<UInput v-model="formData.regon" :placeholder="t('Enter REGON')" name="regon"
|
||||||
<div>
|
class="w-full" />
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{ t('NIP')
|
</div>
|
||||||
}}</label>
|
<div>
|
||||||
<UInput v-model="formData.nip" :placeholder="t('Enter NIP')" name="nip"
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
class="w-full" />
|
t('NIP')
|
||||||
</div>
|
}}</label>
|
||||||
<div>
|
<UInput v-model="formData.nip" :placeholder="t('Enter NIP')" name="nip"
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{ t('VAT')
|
class="w-full" />
|
||||||
}}</label>
|
</div>
|
||||||
<UInput v-model="formData.vat" :placeholder="t('Enter VAT')" name="vat"
|
<div>
|
||||||
class="w-full" />
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
|
t('VAT')
|
||||||
|
}}</label>
|
||||||
|
<UInput v-model="formData.vat" :placeholder="t('Enter VAT')" name="vat"
|
||||||
|
class="w-full" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<h2
|
||||||
<h2 class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:map-marker"
|
<UIcon name="mdi:map-marker"
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
{{ t('Select Addresses') }}
|
{{ t('Select Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div
|
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark)">
|
<div class="mb-4">
|
||||||
<div class="mb-4">
|
<UInput v-model="addressSearchQuery" type="text"
|
||||||
<UInput v-model="addressSearchQuery" type="text" :placeholder="t('Search address')"
|
:placeholder="t('Search address')"
|
||||||
class="w-full bg-white dark:bg-(--black) text-black dark:text-white" />
|
class="w-full bg-white dark:bg-(--black) 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 class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode }},
|
</p>
|
||||||
{{ address.city }}</p>
|
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.country }}
|
}},
|
||||||
</p>
|
{{ address.city }}</p>
|
||||||
</div>
|
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.country
|
||||||
</label>
|
}}
|
||||||
</div>
|
</p>
|
||||||
<div v-else class="text-center py-6">
|
</div>
|
||||||
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
</label>
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
</div>
|
||||||
<RouterLink :to="{ name: 'addresses' }"
|
<div v-else class="text-center py-6">
|
||||||
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
||||||
{{ t('Add Address') }}
|
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
||||||
</RouterLink>
|
<RouterLink :to="{ name: 'addresses' }"
|
||||||
|
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
||||||
|
{{ t('Add Address') }}
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="flex justify-end gap-3 pt-4">
|
||||||
<div class="flex justify-end gap-3 pt-4">
|
<UButton variant="outline" color="neutral" @click="goBack"
|
||||||
<UButton variant="outline" color="neutral" @click="goBack"
|
class="text-black dark:text-white">
|
||||||
class="text-black dark:text-white">
|
{{ t('Cancel') }}
|
||||||
{{ t('Cancel') }}
|
</UButton>
|
||||||
</UButton>
|
<UButton type="submit" color="primary"
|
||||||
<UButton type="submit" color="primary"
|
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
<UIcon name="mdi:content-save" />
|
||||||
<UIcon name="mdi:content-save" />
|
{{ t('Save') }}
|
||||||
{{ t('Save') }}
|
</UButton>
|
||||||
</UButton>
|
</div>
|
||||||
</div>
|
</UForm>
|
||||||
</UForm>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -170,7 +178,7 @@ function saveAccount() {
|
|||||||
vat: formData.value.vat,
|
vat: formData.value.vat,
|
||||||
companyAddressId: formData.value.companyAddressId,
|
companyAddressId: formData.value.companyAddressId,
|
||||||
billingAddressId: formData.value.billingAddressId,
|
billingAddressId: formData.value.billingAddressId,
|
||||||
companyAddress : ''
|
companyAddress: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
router.push({ name: 'profile-details' })
|
router.push({ name: 'profile-details' })
|
||||||
|
|||||||
9
bo/src/components/customer/PageStatistic.vue
Normal file
9
bo/src/components/customer/PageStatistic.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
Statistic page
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Default from '@/layouts/default.vue'
|
||||||
|
</script>
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
{{ locale }}
|
<USelectMenu v-model="locale" :items="langs"
|
||||||
<USelectMenu v-model="locale" :items="langs" class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!"
|
class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!" valueKey="iso_code"
|
||||||
valueKey="iso_code" :searchInput="false">
|
:searchInput="false">
|
||||||
<template #default="{ modelValue }">
|
<template #default="{ modelValue }">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<span class="text-md dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
<!-- <span class="text-md dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.flag}}</span> -->
|
||||||
<span class="font-medium dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.name}}</span>
|
<span class="font-medium dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.name}}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #item-leading="{ item }">
|
<template #item-leading="{ item }">
|
||||||
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
||||||
<span class="text-md ">{{ item.flag }}</span>
|
<!-- <span class="text-md ">{{ item.flag }}</span> -->
|
||||||
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,6 +23,7 @@ import { useRouter, useRoute } from 'vue-router'
|
|||||||
import { useCookie } from '@/composable/useCookie'
|
import { useCookie } from '@/composable/useCookie'
|
||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -50,9 +51,30 @@ const locale = computed({
|
|||||||
router.replace({ path: '/' + value + currentPath, query: route.query })
|
router.replace({ path: '/' + value + currentPath, query: route.query })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeLang()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function changeLang() {
|
||||||
|
try {
|
||||||
|
const { items } = await useFetchJson('/api/v1/public/auth/update-choice', {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (items?.token) {
|
||||||
|
cookie.setCookie('Aurrie', items.token, {
|
||||||
|
days: 60,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'Lax'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.params.locale,
|
() => route.params.locale,
|
||||||
(newLocale) => {
|
(newLocale) => {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import TopBar from '@/components/TopBar.vue';
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
||||||
<main class="p-10">
|
<main class="pt-20 pb-10">
|
||||||
<TopBar/>
|
<TopBar />
|
||||||
<slot></slot>
|
<div class="container mx-auto px-4">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -49,6 +49,42 @@ const router = createRouter({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const viewModules = import.meta.glob('/src/views/**/*.vue')
|
||||||
|
const componentModules = import.meta.glob('/src/components/**/*.vue')
|
||||||
|
|
||||||
|
async function setRoutes() {
|
||||||
|
const routes = await getRoutes()
|
||||||
|
|
||||||
|
for (const item of routes) {
|
||||||
|
const componentName = item.component
|
||||||
|
const [, folder] = componentName.split('/')
|
||||||
|
const componentPath = `/src${componentName}`
|
||||||
|
console.log(componentPath);
|
||||||
|
|
||||||
|
|
||||||
|
let modules =
|
||||||
|
folder === 'views' ? viewModules : componentModules
|
||||||
|
|
||||||
|
const importer = modules[componentPath]
|
||||||
|
|
||||||
|
if (!importer) {
|
||||||
|
console.error('Component not found:', componentPath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const importedComponent = (await importer()).default
|
||||||
|
|
||||||
|
router.addRoute('locale', {
|
||||||
|
path: item.path,
|
||||||
|
component: importedComponent,
|
||||||
|
name: item.name,
|
||||||
|
meta: item.meta ? JSON.parse(item.meta) : {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await setRoutes()
|
||||||
|
|
||||||
router.beforeEach((to, from) => {
|
router.beforeEach((to, from) => {
|
||||||
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)
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import type { MenuItem, Route } from "@/types/menu";
|
|||||||
|
|
||||||
export const getMenu = async () => {
|
export const getMenu = async () => {
|
||||||
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-menu');
|
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-menu');
|
||||||
|
|
||||||
return resp.items.children
|
return resp.items.children
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -13,5 +11,4 @@ export const getRoutes = async () => {
|
|||||||
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
||||||
|
|
||||||
return resp.items
|
return resp.items
|
||||||
|
|
||||||
}
|
}
|
||||||
13
bo/src/types/menu.d.ts
vendored
13
bo/src/types/menu.d.ts
vendored
@@ -31,14 +31,15 @@ export interface TopMenuItem {
|
|||||||
|
|
||||||
export interface Label {
|
export interface Label {
|
||||||
label: string
|
label: string
|
||||||
trans:LabelTrans
|
trans: LabelTrans
|
||||||
|
icon?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LabelTrans{
|
export interface LabelTrans {
|
||||||
pl:LabelItem
|
pl: LabelItem
|
||||||
en:LabelItem
|
en: LabelItem
|
||||||
de: LabelItem
|
de: LabelItem
|
||||||
}
|
}
|
||||||
export interface LabelItem {
|
export interface LabelItem {
|
||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ http:
|
|||||||
"limit": 50,
|
"limit": 50,
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
// "filter": "'attr.10'= 71",
|
// "filter": "'attr.10'= 71",
|
||||||
"facets":["category_ids", "price"]
|
// "facets":["category_ids", "price", "attr"],
|
||||||
// "facets": ["category_ids", "attr", "feat", "price"]
|
"attributesToRetrieve" : ["product_id"]
|
||||||
|
"facets": ["category_ids", "attr", "feat", "price"]
|
||||||
}
|
}
|
||||||
auth:
|
auth:
|
||||||
type: bearer
|
type: bearer
|
||||||
|
|||||||
Reference in New Issue
Block a user