Redesign hub detail page: buttons instead of select, show prices
All checks were successful
Build Docker Image / build (push) Successful in 4m37s
All checks were successful
Build Docker Image / build (push) Successful in 4m37s
This commit is contained in:
@@ -37,26 +37,23 @@
|
|||||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<Heading :level="1">{{ hub.name }}</Heading>
|
<Heading :level="1">{{ hub.name }}</Heading>
|
||||||
<Text tone="muted" size="sm">
|
<Text tone="muted" size="sm">{{ hub.country }}</Text>
|
||||||
{{ hub.country }}
|
|
||||||
<span v-if="hub.latitude && hub.longitude" class="ml-2">
|
|
||||||
{{ hub.latitude.toFixed(2) }}°, {{ hub.longitude.toFixed(2) }}°
|
|
||||||
</span>
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #filters>
|
<template #filters>
|
||||||
<select
|
<div class="flex gap-2 flex-wrap">
|
||||||
v-model="selectedProductUuid"
|
<button
|
||||||
class="select select-bordered w-full"
|
v-for="product in products"
|
||||||
>
|
:key="product.uuid"
|
||||||
<option value="">{{ t('catalogHub.sources.selectProduct') }}</option>
|
class="btn btn-sm"
|
||||||
<option v-for="product in products" :key="product.uuid" :value="product.uuid">
|
:class="selectedProductUuid === product.uuid ? 'btn-primary' : 'btn-outline'"
|
||||||
|
@click="selectedProductUuid = product.uuid"
|
||||||
|
>
|
||||||
{{ product.name }}
|
{{ product.name }}
|
||||||
</option>
|
</button>
|
||||||
</select>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #card="{ item }">
|
<template #card="{ item }">
|
||||||
@@ -68,10 +65,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<Text weight="semibold" class="text-primary">
|
<Text weight="semibold" class="text-primary">
|
||||||
{{ formatDistance(item.distanceKm) }} км
|
{{ getOfferPrice(item.uuid) }}
|
||||||
</Text>
|
</Text>
|
||||||
<Text tone="muted" size="sm">
|
<Text tone="muted" size="sm">
|
||||||
{{ formatDuration(item.durationSeconds) }}
|
{{ formatDistance(item.distanceKm) }} км
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +90,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetNodeConnectionsDocument, FindProductRoutesDocument } from '~/composables/graphql/public/geo-generated'
|
import { GetNodeConnectionsDocument, FindProductRoutesDocument } from '~/composables/graphql/public/geo-generated'
|
||||||
import { GetAvailableProductsDocument } from '~/composables/graphql/public/exchange-generated'
|
import { GetAvailableProductsDocument, GetOfferDocument } from '~/composables/graphql/public/exchange-generated'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav'
|
layout: 'topnav'
|
||||||
@@ -111,6 +108,7 @@ const products = ref<Array<{ uuid: string; name: string }>>([])
|
|||||||
const selectedProductUuid = ref('')
|
const selectedProductUuid = ref('')
|
||||||
const selectedSourceUuid = ref('')
|
const selectedSourceUuid = ref('')
|
||||||
const rawSources = ref<any[]>([])
|
const rawSources = ref<any[]>([])
|
||||||
|
const offersData = ref<Map<string, any>>(new Map())
|
||||||
|
|
||||||
const hubId = computed(() => route.params.id as string)
|
const hubId = computed(() => route.params.id as string)
|
||||||
|
|
||||||
@@ -132,10 +130,39 @@ const sources = computed(() => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get offer price for display
|
||||||
|
const getOfferPrice = (uuid: string) => {
|
||||||
|
const offer = offersData.value.get(uuid)
|
||||||
|
if (!offer) return '-'
|
||||||
|
return `${offer.pricePerUnit} ${offer.currency}/${offer.unit}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load offer details for prices
|
||||||
|
const loadOfferDetails = async () => {
|
||||||
|
if (rawSources.value.length === 0) {
|
||||||
|
offersData.value.clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newOffersData = new Map<string, any>()
|
||||||
|
await Promise.all(rawSources.value.map(async (source) => {
|
||||||
|
try {
|
||||||
|
const data = await execute(GetOfferDocument, { uuid: source.sourceUuid }, 'public', 'exchange')
|
||||||
|
if (data?.getOffer) {
|
||||||
|
newOffersData.set(source.sourceUuid, data.getOffer)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading offer:', source.sourceUuid, error)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
offersData.value = newOffersData
|
||||||
|
}
|
||||||
|
|
||||||
// Load routes when product changes
|
// Load routes when product changes
|
||||||
const loadRoutes = async () => {
|
const loadRoutes = async () => {
|
||||||
if (!selectedProductUuid.value || !hubId.value) {
|
if (!selectedProductUuid.value || !hubId.value) {
|
||||||
rawSources.value = []
|
rawSources.value = []
|
||||||
|
offersData.value.clear()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +182,7 @@ const loadRoutes = async () => {
|
|||||||
'geo'
|
'geo'
|
||||||
)
|
)
|
||||||
rawSources.value = (data?.findProductRoutes || []).filter(Boolean)
|
rawSources.value = (data?.findProductRoutes || []).filter(Boolean)
|
||||||
|
await loadOfferDetails()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading routes:', error)
|
console.error('Error loading routes:', error)
|
||||||
rawSources.value = []
|
rawSources.value = []
|
||||||
|
|||||||
Reference in New Issue
Block a user