Fix Info panel - translations, two-step offers flow, icon, add to filter
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:
Ruslan Bakiev
2026-01-25 16:44:00 +07:00
parent 908d63062c
commit 39c3d24b3a
5 changed files with 110 additions and 37 deletions

View File

@@ -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',

View File

@@ -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
}) })
} }

View File

@@ -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) => {

View File

@@ -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"

View File

@@ -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": "Найти офферы"