Fix Info panel - translations, two-step offers flow, icon, add to filter
All checks were successful
Build Docker Image / build (push) Successful in 3m36s
All checks were successful
Build Docker Image / build (push) Successful in 3m36s
- Add i18n translations for entities, tabs, and info sections (EN/RU) - Refactor offers tab to two-step flow (products → offers) for Hub/Supplier - Replace entity badge with circular icon in header - Fix "Add to filter" button with name fallback and proper cleanup - Update selectItem() to clear info param when adding to filter
This commit is contained in:
@@ -3,9 +3,12 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="badge badge-sm" :style="{ backgroundColor: badgeColor }">
|
<div
|
||||||
{{ badgeLabel }}
|
class="flex items-center justify-center w-6 h-6 rounded-full"
|
||||||
</span>
|
:style="{ backgroundColor: badgeColor }"
|
||||||
|
>
|
||||||
|
<Icon :name="entityIcon" size="14" class="text-white" />
|
||||||
|
</div>
|
||||||
<h3 class="font-semibold text-base text-base-content">{{ entityName }}</h3>
|
<h3 class="font-semibold text-base text-base-content">{{ entityName }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -48,11 +51,11 @@
|
|||||||
|
|
||||||
<!-- Tab content -->
|
<!-- Tab content -->
|
||||||
<div class="flex flex-col gap-2 min-h-[200px]">
|
<div class="flex flex-col gap-2 min-h-[200px]">
|
||||||
<!-- Products tab -->
|
<!-- Products tab (only for Offer entity) -->
|
||||||
<div v-if="currentTab === 'products'">
|
<div v-if="currentTab === 'products' && entityType === 'offer'">
|
||||||
<div v-if="relatedProducts.length === 0" class="text-center py-8 text-white/60">
|
<div v-if="relatedProducts.length === 0" class="text-center py-8 text-white/60">
|
||||||
<Icon name="lucide:package" size="32" class="mb-2 opacity-50" />
|
<Icon name="lucide:package" size="32" class="mb-2 opacity-50" />
|
||||||
<p>{{ $t('catalog.info.noProducts') }}</p>
|
<p>{{ $t('catalog.empty.noProducts') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col gap-2">
|
<div v-else class="flex flex-col gap-2">
|
||||||
<ProductCard
|
<ProductCard
|
||||||
@@ -100,15 +103,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Offers tab -->
|
<!-- Offers tab (two-step for Hub/Supplier) -->
|
||||||
<div v-if="currentTab === 'offers'">
|
<div v-if="currentTab === 'offers'">
|
||||||
<div v-if="!selectedProduct" class="text-center py-8 text-white/60">
|
<!-- Step 1: Select product (for Hub/Supplier) -->
|
||||||
<Icon name="lucide:info" size="32" class="mb-2 opacity-50" />
|
<div v-if="!selectedProduct && (entityType === 'hub' || entityType === 'supplier')">
|
||||||
<p>{{ $t('catalog.info.selectProductFirst') }}</p>
|
<div v-if="relatedProducts.length === 0" class="text-center py-8 text-white/60">
|
||||||
|
<Icon name="lucide:package" size="32" class="mb-2 opacity-50" />
|
||||||
|
<p>{{ $t('catalog.empty.noProducts') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="relatedOffers.length === 0" class="text-center py-8 text-white/60">
|
<div v-else class="flex flex-col gap-2">
|
||||||
|
<ProductCard
|
||||||
|
v-for="product in relatedProducts"
|
||||||
|
:key="product.uuid"
|
||||||
|
:product="product"
|
||||||
|
selectable
|
||||||
|
compact
|
||||||
|
@select="onProductSelect(product)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2: Show offers for selected product -->
|
||||||
|
<div v-else-if="selectedProduct">
|
||||||
|
<!-- Back button to products -->
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-ghost mb-2 text-white/80 hover:text-white"
|
||||||
|
@click="emit('select-product', null)"
|
||||||
|
>
|
||||||
|
<Icon name="lucide:arrow-left" size="16" />
|
||||||
|
{{ $t('common.back') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div v-if="relatedOffers.length === 0" class="text-center py-8 text-white/60">
|
||||||
<Icon name="lucide:shopping-bag" size="32" class="mb-2 opacity-50" />
|
<Icon name="lucide:shopping-bag" size="32" class="mb-2 opacity-50" />
|
||||||
<p>{{ $t('catalog.info.noOffers') }}</p>
|
<p>{{ $t('catalog.empty.noOffers') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col gap-2">
|
<div v-else class="flex flex-col gap-2">
|
||||||
<OfferCard
|
<OfferCard
|
||||||
@@ -121,6 +149,13 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- For Offer entity type - just show message -->
|
||||||
|
<div v-else class="text-center py-8 text-white/60">
|
||||||
|
<Icon name="lucide:info" size="32" class="mb-2 opacity-50" />
|
||||||
|
<p>{{ $t('catalog.info.selectProductFirst') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add to filter button -->
|
<!-- Add to filter button -->
|
||||||
@@ -158,7 +193,7 @@ const { t } = useI18n()
|
|||||||
const { entityColors } = useCatalogSearch()
|
const { entityColors } = useCatalogSearch()
|
||||||
|
|
||||||
// Current active tab
|
// Current active tab
|
||||||
const currentTab = ref<string>('products')
|
const currentTab = ref<string>('offers')
|
||||||
|
|
||||||
// Entity name
|
// Entity name
|
||||||
const entityName = computed(() => {
|
const entityName = computed(() => {
|
||||||
@@ -180,20 +215,24 @@ const badgeLabel = computed(() => {
|
|||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const entityIcon = computed(() => {
|
||||||
|
if (props.entityType === 'hub') return 'lucide:warehouse'
|
||||||
|
if (props.entityType === 'supplier') return 'lucide:factory'
|
||||||
|
if (props.entityType === 'offer') return 'lucide:shopping-bag'
|
||||||
|
return 'lucide:info'
|
||||||
|
})
|
||||||
|
|
||||||
// Available tabs based on entity type and data
|
// Available tabs based on entity type and data
|
||||||
const availableTabs = computed(() => {
|
const availableTabs = computed(() => {
|
||||||
const tabs: Array<{ id: string; label: string; count?: number }> = []
|
const tabs: Array<{ id: string; label: string; count?: number }> = []
|
||||||
|
|
||||||
if (props.entityType === 'hub') {
|
if (props.entityType === 'hub') {
|
||||||
tabs.push({
|
|
||||||
id: 'products',
|
|
||||||
label: t('catalog.tabs.products'),
|
|
||||||
count: props.relatedProducts?.length || 0
|
|
||||||
})
|
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'offers',
|
id: 'offers',
|
||||||
label: t('catalog.tabs.offers'),
|
label: t('catalog.tabs.offers'),
|
||||||
count: props.relatedOffers?.length || 0
|
count: props.selectedProduct
|
||||||
|
? props.relatedOffers?.length || 0
|
||||||
|
: props.relatedProducts?.length || 0
|
||||||
})
|
})
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'suppliers',
|
id: 'suppliers',
|
||||||
@@ -201,15 +240,12 @@ const availableTabs = computed(() => {
|
|||||||
count: props.relatedSuppliers?.length || 0
|
count: props.relatedSuppliers?.length || 0
|
||||||
})
|
})
|
||||||
} else if (props.entityType === 'supplier') {
|
} else if (props.entityType === 'supplier') {
|
||||||
tabs.push({
|
|
||||||
id: 'products',
|
|
||||||
label: t('catalog.tabs.products'),
|
|
||||||
count: props.relatedProducts?.length || 0
|
|
||||||
})
|
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'offers',
|
id: 'offers',
|
||||||
label: t('catalog.tabs.offers'),
|
label: t('catalog.tabs.offers'),
|
||||||
count: props.relatedOffers?.length || 0
|
count: props.selectedProduct
|
||||||
|
? props.relatedOffers?.length || 0
|
||||||
|
: props.relatedProducts?.length || 0
|
||||||
})
|
})
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'hubs',
|
id: 'hubs',
|
||||||
|
|||||||
@@ -210,7 +210,8 @@ export function useCatalogSearch() {
|
|||||||
setLabel(type, id, label)
|
setLabel(type, id, label)
|
||||||
updateQuery({
|
updateQuery({
|
||||||
[type]: id,
|
[type]: id,
|
||||||
select: null // Exit selection mode
|
select: null, // Exit selection mode
|
||||||
|
info: null // Exit info mode
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -395,9 +395,11 @@ const onInfoClose = () => {
|
|||||||
const onInfoAddToFilter = () => {
|
const onInfoAddToFilter = () => {
|
||||||
if (!infoId.value || !entity.value) return
|
if (!infoId.value || !entity.value) return
|
||||||
const { type, uuid } = infoId.value
|
const { type, uuid } = infoId.value
|
||||||
const name = entity.value.name
|
// Fallback for name - offer entities might use productName
|
||||||
|
const name = entity.value.name || entity.value.productName || uuid.slice(0, 8) + '...'
|
||||||
selectItem(type, uuid, name)
|
selectItem(type, uuid, name)
|
||||||
closeInfo()
|
closeInfo()
|
||||||
|
clearInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInfoOpenRelated = (type: 'hub' | 'supplier' | 'offer', uuid: string) => {
|
const onInfoOpenRelated = (type: 'hub' | 'supplier' | 'offer', uuid: string) => {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"catalog": {
|
"catalog": {
|
||||||
|
"entities": {
|
||||||
|
"hub": "Hub",
|
||||||
|
"supplier": "Supplier",
|
||||||
|
"offer": "Offer"
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"product": "Product",
|
"product": "Product",
|
||||||
"supplier": "Supplier",
|
"supplier": "Supplier",
|
||||||
@@ -41,6 +46,18 @@
|
|||||||
"offers": "Offers",
|
"offers": "Offers",
|
||||||
"map": "Map"
|
"map": "Map"
|
||||||
},
|
},
|
||||||
|
"tabs": {
|
||||||
|
"products": "Products",
|
||||||
|
"offers": "Offers",
|
||||||
|
"suppliers": "Suppliers",
|
||||||
|
"hubs": "Hubs",
|
||||||
|
"product": "Product",
|
||||||
|
"supplier": "Supplier"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"selectProductFirst": "Select a product first",
|
||||||
|
"addToFilter": "Add to filter"
|
||||||
|
},
|
||||||
"modes": {
|
"modes": {
|
||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
"quote": "Get Quote"
|
"quote": "Get Quote"
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"catalog": {
|
"catalog": {
|
||||||
|
"entities": {
|
||||||
|
"hub": "Хаб",
|
||||||
|
"supplier": "Поставщик",
|
||||||
|
"offer": "Оффер"
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"product": "Товар",
|
"product": "Товар",
|
||||||
"supplier": "Поставщик",
|
"supplier": "Поставщик",
|
||||||
@@ -41,6 +46,18 @@
|
|||||||
"offers": "Офферы",
|
"offers": "Офферы",
|
||||||
"map": "Карта"
|
"map": "Карта"
|
||||||
},
|
},
|
||||||
|
"tabs": {
|
||||||
|
"products": "Товары",
|
||||||
|
"offers": "Офферы",
|
||||||
|
"suppliers": "Поставщики",
|
||||||
|
"hubs": "Хабы",
|
||||||
|
"product": "Товар",
|
||||||
|
"supplier": "Поставщик"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"selectProductFirst": "Сначала выберите товар",
|
||||||
|
"addToFilter": "Добавить в фильтр"
|
||||||
|
},
|
||||||
"modes": {
|
"modes": {
|
||||||
"explore": "Исследовать",
|
"explore": "Исследовать",
|
||||||
"quote": "Найти офферы"
|
"quote": "Найти офферы"
|
||||||
|
|||||||
Reference in New Issue
Block a user