1 Commits

Author SHA1 Message Date
12f6249721 fix: requests 2026-03-31 12:22:21 +02:00
14 changed files with 262 additions and 124 deletions

View File

@@ -18,10 +18,9 @@ type ProductDescription struct {
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
Usage string `gorm:"column:_usage_;type:text" json:"usage" form:"usage"`
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
ImageLink string `gorm:"column:image_link" json:"image_link"`
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
ExistsInDatabse bool `gorm:"-" json:"exists_in_database"`
}
type ProductRow struct {

View File

@@ -32,7 +32,7 @@ func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.Fi
ps.id_product AS product_id,
pl.name AS name,
pl.link_rewrite AS link_rewrite,
CONCAT(?, '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
CONCAT(?, ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
cl.name AS category_name,
p.reference AS reference,
COALESCE(v.variants_number, 0) AS variants_number,

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
@@ -37,36 +36,15 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
Select(`
`+dbmodel.PsProductLangCols.IDProduct.TabCol()+` AS id_product,
`+dbmodel.PsProductLangCols.IDShop.TabCol()+` AS id_shop,
`+dbmodel.PsProductLangCols.IDLang.TabCol()+` AS id_lang,
`+dbmodel.PsProductLangCols.Description.TabCol()+` AS description,
`+dbmodel.PsProductLangCols.DescriptionShort.TabCol()+` AS description_short,
`+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+` AS link_rewrite,
`+dbmodel.PsProductLangCols.MetaDescription.TabCol()+` AS meta_description,
`+dbmodel.PsProductLangCols.MetaKeywords.TabCol()+` AS meta_keywords,
`+dbmodel.PsProductLangCols.MetaTitle.TabCol()+` AS meta_title,
`+dbmodel.PsProductLangCols.Name.TabCol()+` AS name,
`+dbmodel.PsProductLangCols.AvailableNow.TabCol()+` AS available_now,
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS _usage_,
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
`, config.Get().Image.ImagePrefix).
Joins("JOIN " + dbmodel.TableNamePsImageShop +
" ON " + dbmodel.PsImageShopCols.IDProduct.TabCol() + "=" + dbmodel.PsProductLangCols.IDProduct.TabCol() +
" AND " + dbmodel.PsImageShopCols.Cover.TabCol() + " = 1").
First(&ProductDescription).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
// handle "not found" case only
ProductDescription.ExistsInDatabase = false
ProductDescription.ExistsInDatabse = false
} else if err != nil {
return nil, fmt.Errorf("database error: %w", err)
} else {
ProductDescription.ExistsInDatabase = true
ProductDescription.ExistsInDatabse = true
}
return &ProductDescription, nil

View File

@@ -1,11 +1,70 @@
<template>
<suspense>
<component :is="Default || 'div'">
<div class="flex gap-10">
<!-- <div class="w-64 h-128">
<CategoryMenu />
<div class="w-full flex flex-col items-center gap-4">
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
</div> -->
<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 class="container mx-auto mt-20">
<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">
Image</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Product Code</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Name</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Link</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 productsList" :key="product.product_id"
class="hover:bg-gray-50 dark:hover:bg-gray-800"
@click="goToProduct(product.product_id)">
<td class="px-6 py-4 whitespace-nowrap">
<img :src="product.image_link" alt="product image"
class="w-16 h-16 object-cover rounded" />
</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.name }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-blue-600 dark:text-blue-400">
{{ product.link_rewrite }}
</td>
</tr>
</tbody>
</table>
<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>
@@ -18,20 +77,26 @@ import { useFetchJson } from '@/composable/useFetchJson'
import Default from '@/layouts/default.vue'
import { useRoute, useRouter } from 'vue-router'
import type { TableColumn } from '@nuxt/ui'
import CategoryMenu from '../inner/categoryMenu.vue'
import type { Product } from '@/types/product'
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 perPage = ref(15)
const page = computed({
get: () => Number(route.query.p) || 1,
get: () => Number(route.query.page) || 1,
set: (val: number) => {
router.push({
query: {
...route.query,
p: val
page: val
}
})
}
@@ -67,6 +132,7 @@ const sortField = computed({
}
})
const perPage = ref(15)
const total = ref(0)
interface ApiResponse {
@@ -127,25 +193,17 @@ async function fetchProductList() {
error.value = null
const params = new URLSearchParams()
Object.entries(route.query).forEach(([key, value]) => {
if (value === undefined || value === null) return
if (Array.isArray(value)) {
value.forEach(v => params.append(key, String(v)))
} else {
params.append(key, String(value))
}
if (value) params.append(key, String(value))
})
if (route.params.category_id)
params.append('category_id', String(route.params.category_id))
const url = `/api/v1/restricted/list/list-products?${params}`
const url = `/api/v1/restricted/list/list-products?elems=${perPage.value}&${params.toString()}`
try {
const response = await useFetchJson<ApiResponse>(url)
productsList.value = response.items || []
total.value = Number(response.count) || 0
total.value = response.count || 0
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Failed to load products'
} finally {
@@ -155,11 +213,16 @@ async function fetchProductList() {
function goToProduct(productId: number) {
router.push({
name: 'customer-product-details',
params: { product_id: productId }
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'
@@ -173,7 +236,25 @@ const UInput = resolveComponent('UInput')
const UButton = resolveComponent('UButton')
const UIcon = resolveComponent('UIcon')
const columns: TableColumn<Product>[] = [
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 }) => {
@@ -264,17 +345,108 @@ const columns: TableColumn<Product>[] = [
},
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: () => {
goToProduct(row.original.product_id)
console.log('Clicked', row.original)
},
color: 'primary',
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
disabled: selectedCount.value.product_id !== row.original.product_id,
variant: 'solid'
}, () => 'Show product')
}, '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')
},
}
]

View File

@@ -19,7 +19,7 @@
</div>
<div v-else class="overflow-x-auto">
<div class="flex gap-2">
<CategoryMenu />
<CategoryMenuListing />
<UTable :data="productsList" :columns="columns" class="flex-1">
<template #expanded="{ row }">
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
@@ -46,7 +46,7 @@ import { useFetchJson } from '@/composable/useFetchJson'
import Default from '@/layouts/default.vue'
import { useRoute, useRouter } from 'vue-router'
import type { TableColumn } from '@nuxt/ui'
import CategoryMenu from '../inner/categoryMenu.vue'
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
interface Product {
reference: number

View File

@@ -1,57 +1,20 @@
<template>
<UNavigationMenu orientation="vertical" type="single" :items="items" class="data-[orientation=vertical]:w-72" />
</template>
<script setup lang="ts">
import { getMenu } from '@/router/menu'
import type { NavigationMenuItem } from '@nuxt/ui';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
let menu = await getMenu() as NavigationMenuItem[]
const openAll = ref(false)
const route = useRoute()
let categoryId = ref(route.params.category_id)
function findPath(tree: NavigationMenuItem[], id: number, path: Array<number> = []): Array<number> | null {
for (let item of tree) {
let newPath: Array<number> = [...path, item.category_id]
if (item.category_id === id) {
return newPath
}
if (item.children) {
const result: Array<number> | null = findPath(item.children, id, newPath)
if (result) {
return result
}
}
}
return null
}
let path = findPath(menu, Number(categoryId.value))
function adaptMenu(menu: NavigationMenuItem[]) {
for (const item of menu) {
if (item.children && item.children.length > 0) {
item.open = path && path.includes(item.category_id) ? true : openAll.value
console.log(item);
adaptMenu(item.children);
item.children.unshift({
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
name: 'customer-products-category', params: {
category_id: item.params.category_id,
link_rewrite: item.params.link_rewrite
}
}
})
item.open = openAll.value
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
} else {
item.to = {
name: 'customer-products-category', params: {
category_id: item.params.category_id,
link_rewrite: item.params.link_rewrite
}
};
item.to = { name: 'category', params: item.params };
item.icon = 'i-lucide-file-text'
}
}
@@ -59,10 +22,15 @@ function adaptMenu(menu: NavigationMenuItem[]) {
}
menu = adaptMenu(menu)
const items = ref<NavigationMenuItem[][]>([
[
...menu as NavigationMenuItem[]
],
])
</script>
</script>
<template>
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import { getMenu } from '@/router/menu'
import type { NavigationMenuItem } from '@nuxt/ui';
import { ref } from 'vue';
let menu = await getMenu() as NavigationMenuItem[]
const openAll = ref(false)
function adaptMenu(menu: NavigationMenuItem[]) {
for (const item of menu) {
if (item.children && item.children.length > 0) {
adaptMenu(item.children);
item.open = openAll.value
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
} else {
item.to = { name: 'category', params: item.params };
item.icon = 'i-lucide-file-text'
}
}
return menu;
}
menu = adaptMenu(menu)
const items = ref<NavigationMenuItem[][]>([
[
...menu as NavigationMenuItem[]
],
])
</script>
<template>
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
</template>

View File

@@ -59,6 +59,7 @@ async function setRoutes() {
const componentName = item.component
const [, folder] = componentName.split('/')
const componentPath = `/src${componentName}`
console.log(componentPath);
let modules =

View File

@@ -1,15 +1,8 @@
import { useFetchJson } from "@/composable/useFetchJson";
import type { MenuItem, Route } from "@/types/menu";
import { ref } from "vue";
import { settings } from "./settings";
const categoryId = ref()
export const getMenu = async () => {
if(!categoryId.value){
categoryId.value = settings['app'].category_tree_root_id
}
const resp = await useFetchJson<MenuItem>(`/api/v1/restricted/menu/get-category-tree?root_category_id=${categoryId.value}`);
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-category-tree');
return resp.items.children
}

View File

@@ -13,24 +13,24 @@ const products = [
// type CategoryProducts = {}
export const useCategoryStore = defineStore('category', () => {
const idCategory = ref(0)
const id_category = ref(0)
const categoryProducts = ref(products)
function setCategoryID(id: number) {
idCategory.value = id
id_category.value = id
}
async function getCategoryProducts() {
return new Promise<typeof products>((resolve) => {
setTimeout(() => {
// console.log('Fetching products from category id: ', idCategory.value);
console.log('Fetching products from category id: ', id_category.value);
resolve(categoryProducts.value)
}, 2000 * Math.random())
})
}
return {
idCategory,
id_category,
getCategoryProducts,
setCategoryID
}

View File

@@ -36,6 +36,7 @@ export const useProductStore = defineStore('product', () => {
const response = await useFetchJson<ProductDescription>(
`/api/v1/restricted/product-translation/get-product-description?productID=${productID}&productLangID=${langId}`
)
console.log(response, 'dfsfsdf');
productDescription.value = response.items
} catch (e: unknown) {

View File

@@ -1,4 +1,4 @@
export interface ProductDescription {
export interface ProductDescription {
id?: number
name?: string
description: string
@@ -6,12 +6,4 @@ export interface ProductDescription {
meta_description: string
available_now: string
usage: string
}
export interface Product {
reference: number
product_id: number
name: string
image_link: string
link_rewrite: string
}

View File

@@ -7,7 +7,6 @@ export interface Settings {
}
export interface App {
category_tree_root_id: number
name: string
environment: string
base_url: string

View File

@@ -5,13 +5,13 @@ info:
http:
method: GET
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=1
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=4
params:
- name: productID
value: "51"
type: query
- name: productLangID
value: "1"
value: "4"
type: query
auth: inherit