Add offer detail page /catalog/offers/[offerId]
All checks were successful
Build Docker Image / build (push) Successful in 4m28s

- New page shows offer details, supplier info, and full KYC profile
- Updated CalcResultContent to navigate to offer page on card click
This commit is contained in:
Ruslan Bakiev
2026-01-21 14:53:27 +07:00
parent c1ae984fcc
commit 16c0a8112e
2 changed files with 177 additions and 5 deletions

View File

@@ -180,11 +180,7 @@ const getKycProfileUuid = (offerUuid?: string | null) => {
// Navigate to offer detail page
const navigateToOffer = (offerUuid?: string | null) => {
if (!offerUuid) return
const offer = offersData.value.get(offerUuid)
if (!offer?.teamUuid || !productUuid.value || !destinationUuid.value) return
// Navigate to /catalog/suppliers/[supplierId]/[productId]/[hubId]
navigateTo(localePath(`/catalog/suppliers/${offer.teamUuid}/${productUuid.value}/${destinationUuid.value}`))
navigateTo(localePath(`/catalog/offers/${offerUuid}`))
}
// Load offer details for prices

View File

@@ -0,0 +1,176 @@
<template>
<Section variant="plain">
<Stack gap="6">
<!-- Loading -->
<Card v-if="isLoading" padding="lg">
<Stack align="center" gap="3">
<Spinner />
<Text tone="muted">Загрузка оффера...</Text>
</Stack>
</Card>
<!-- Error -->
<Alert v-else-if="!offer" variant="error">
<Text>Оффер не найден</Text>
</Alert>
<!-- Content -->
<template v-else>
<!-- Offer Header -->
<Card padding="lg">
<Stack gap="4">
<!-- Product & Price -->
<div class="flex items-start justify-between">
<div>
<Text weight="semibold" size="xl">{{ offer.productName || 'Товар' }}</Text>
<Text v-if="offer.locationName" tone="muted">{{ offer.locationName }}</Text>
</div>
<Text v-if="priceDisplay" weight="bold" class="text-primary text-2xl">
{{ priceDisplay }}
</Text>
</div>
<!-- Quantity -->
<div v-if="offer.quantity" class="flex items-center gap-2">
<Icon name="lucide:package" size="16" class="text-base-content/60" />
<Text tone="muted">Доступно: {{ offer.quantity }} {{ offer.unit || 'т' }}</Text>
</div>
<!-- Location on map -->
<div v-if="offer.latitude && offer.longitude" class="h-48 rounded-lg overflow-hidden">
<ClientOnly>
<MapboxMap
map-id="offer-location-map"
class="w-full h-full"
:options="{
style: 'mapbox://styles/mapbox/streets-v12',
center: [offer.longitude, offer.latitude],
zoom: 8
}"
>
<MapboxDefaultMarker
:lnglat="[offer.longitude, offer.latitude]"
color="#10b981"
/>
</MapboxMap>
</ClientOnly>
</div>
</Stack>
</Card>
<!-- Supplier Info -->
<Card v-if="supplier" padding="lg">
<Stack gap="3">
<Text weight="semibold" size="lg">Поставщик</Text>
<div class="flex items-center gap-3">
<div v-if="supplier.logoUrl" class="w-12 h-12 rounded-full overflow-hidden bg-base-200">
<img :src="supplier.logoUrl" :alt="supplier.name" class="w-full h-full object-cover" />
</div>
<div v-else class="w-12 h-12 rounded-full bg-base-200 flex items-center justify-center">
<Icon name="lucide:building-2" size="24" class="text-base-content/40" />
</div>
<div>
<Text weight="medium" size="lg">{{ supplier.name }}</Text>
<Text v-if="supplier.country" tone="muted">{{ supplier.country }}</Text>
</div>
<div v-if="supplier.isVerified" class="badge badge-success ml-auto">Верифицирован</div>
</div>
</Stack>
</Card>
<!-- KYC Profile (full company info) -->
<KycProfileCard v-if="supplier?.kycProfileUuid" :kyc-profile-uuid="supplier.kycProfileUuid" />
<!-- Actions -->
<Card padding="lg">
<Stack gap="3">
<Button variant="primary" size="lg" class="w-full">
<Icon name="lucide:message-circle" size="18" />
Связаться с поставщиком
</Button>
<Button variant="outline" size="lg" class="w-full" @click="goBack">
<Icon name="lucide:arrow-left" size="18" />
Назад к результатам
</Button>
</Stack>
</Card>
</template>
</Stack>
</Section>
</template>
<script setup lang="ts">
import { GetOfferDocument, GetSupplierProfileByTeamDocument } from '~/composables/graphql/public/exchange-generated'
definePageMeta({
layout: 'topnav'
})
const route = useRoute()
const router = useRouter()
const { execute } = useGraphQL()
const offerId = computed(() => route.params.offerId as string)
const isLoading = ref(true)
const offer = ref<any>(null)
const supplier = ref<any>(null)
// Load offer data
const loadOffer = async () => {
if (!offerId.value) return
isLoading.value = true
try {
const data = await execute(GetOfferDocument, { uuid: offerId.value }, 'public', 'exchange')
offer.value = data?.getOffer || null
// Load supplier if we have teamUuid
if (offer.value?.teamUuid) {
const supplierData = await execute(
GetSupplierProfileByTeamDocument,
{ teamUuid: offer.value.teamUuid },
'public',
'exchange'
)
supplier.value = supplierData?.getSupplierProfileByTeam || null
}
} catch (error) {
console.error('Error loading offer:', error)
} finally {
isLoading.value = false
}
}
await loadOffer()
// Price display
const priceDisplay = computed(() => {
if (!offer.value?.pricePerUnit) return null
const currSymbol = getCurrencySymbol(offer.value.currency)
const unitName = offer.value.unit || 'т'
return `${currSymbol}${offer.value.pricePerUnit.toLocaleString()}/${unitName}`
})
const getCurrencySymbol = (currency?: string | null) => {
switch (currency?.toUpperCase()) {
case 'USD': return '$'
case 'EUR': return '€'
case 'RUB': return '₽'
case 'CNY': return '¥'
default: return '$'
}
}
const goBack = () => {
router.back()
}
// SEO
useHead(() => ({
title: offer.value?.productName
? `${offer.value.productName} - Оффер`
: 'Оффер'
}))
</script>