Enrich offer card origin, price, and duration
All checks were successful
Build Docker Image / build (push) Successful in 4m40s

This commit is contained in:
Ruslan Bakiev
2026-02-06 17:37:58 +07:00
parent e4f81dba7c
commit 675f46a75e
5 changed files with 83 additions and 11 deletions

View File

@@ -28,6 +28,7 @@
:currency="getOfferData(option.sourceUuid)?.currency"
:unit="getOfferData(option.sourceUuid)?.unit"
:stages="getRouteStages(option)"
:total-time-seconds="option.routes?.[0]?.totalTimeSeconds ?? null"
:kyc-profile-uuid="getKycProfileUuid(option.sourceUuid)"
@select="navigateToOffer(option.sourceUuid)"
/>
@@ -209,7 +210,9 @@ const getRouteStages = (option: ProductRouteOption) => {
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
.map((stage) => ({
transportType: stage.transportType,
distanceKm: stage.distanceKm
distanceKm: stage.distanceKm,
travelTimeSeconds: stage.travelTimeSeconds,
fromName: stage.fromName
}))
}

View File

@@ -160,13 +160,14 @@
v-for="(offer, index) in offersWithPrice"
:key="offer.uuid ?? index"
:supplier-name="offer.supplierName"
:location-name="offer.locationName || offer.locationCountry || offer.locationName"
:location-name="offer.locationName || offer.locationCountry || offer.country || offer.locationName"
:product-name="offer.productName"
:price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null"
:quantity="offer.quantity"
:currency="offer.currency"
:unit="offer.unit"
:stages="getOfferStages(offer)"
:total-time-seconds="offer.routes?.[0]?.totalTimeSeconds ?? null"
@select="onOfferSelect(offer)"
/>
</div>
@@ -465,7 +466,9 @@ const getOfferStages = (offer: InfoOfferItem) => {
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
.map(stage => ({
transportType: stage.transportType,
distanceKm: stage.distanceKm
distanceKm: stage.distanceKm,
travelTimeSeconds: stage.travelTimeSeconds,
fromName: stage.fromName
}))
}
</script>

View File

@@ -26,9 +26,14 @@
</div>
</div>
<Text v-if="priceDisplay" weight="semibold" class="text-primary text-lg text-right">
<div class="text-right">
<Text v-if="priceDisplay" weight="semibold" class="text-primary text-lg">
{{ priceDisplay }}
</Text>
<Text v-if="durationDisplay" size="xs" class="text-base-content/60">
{{ t('catalogOfferCard.labels.duration_label') }} {{ durationDisplay }}
</Text>
</div>
</div>
<!-- Supplier info -->
@@ -45,7 +50,12 @@
</template>
<script setup lang="ts">
import type { RouteStage } from './RouteStepper.vue'
interface RouteStage {
transportType?: string | null
distanceKm?: number | null
travelTimeSeconds?: number | null
fromName?: string | null
}
const props = withDefaults(defineProps<{
locationName?: string
@@ -56,8 +66,7 @@ const props = withDefaults(defineProps<{
currency?: string | null
unit?: string | null
stages?: RouteStage[]
startName?: string
endName?: string
totalTimeSeconds?: number | null
kycProfileUuid?: string | null
}>(), {
stages: () => []
@@ -74,14 +83,17 @@ const supplierDisplay = computed(() => {
})
const originDisplay = computed(() => {
return props.locationName || t('catalogOfferCard.labels.origin_unknown')
const fromStage = props.stages?.find(stage => stage?.fromName)?.fromName
return props.locationName || fromStage || t('catalogOfferCard.labels.origin_unknown')
})
const priceDisplay = computed(() => {
if (props.pricePerUnit == null) return null
const currSymbol = getCurrencySymbol(props.currency)
const unitName = getUnitName(props.unit)
const formattedPrice = Number(props.pricePerUnit).toLocaleString()
const basePrice = Number(props.pricePerUnit)
const totalPrice = basePrice + (logisticsCost.value ?? 0)
const formattedPrice = totalPrice.toLocaleString()
return `${currSymbol}${formattedPrice}/${unitName}`
})
@@ -128,6 +140,21 @@ const formatDistance = (km?: number | null) => {
return t('catalogOfferCard.labels.distance_km', { km: formatted })
}
const formatDuration = (seconds?: number | null) => {
if (!seconds) return null
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (hours >= 24) {
const days = Math.floor(hours / 24)
const remainingHours = hours % 24
return `${days}${t('catalogOfferCard.labels.time_days_short')} ${remainingHours}${t('catalogOfferCard.labels.time_hours_short')}`
}
if (hours > 0) {
return `${hours}${t('catalogOfferCard.labels.time_hours_short')} ${minutes}${t('catalogOfferCard.labels.time_minutes_short')}`
}
return `${minutes}${t('catalogOfferCard.labels.time_minutes_short')}`
}
const getTransportIcon = (type?: string | null) => {
switch (type) {
case 'rail':
@@ -141,6 +168,37 @@ const getTransportIcon = (type?: string | null) => {
}
}
const getTransportRate = (type?: string | null) => {
switch (type) {
case 'rail':
return 0.18
case 'sea':
return 0.12
case 'road':
case 'auto':
default:
return 0.35
}
}
const logisticsCost = computed(() => {
if (!props.stages?.length) return null
return props.stages.reduce((sum, stage) => {
const km = stage?.distanceKm
if (km == null) return sum
return sum + km * getTransportRate(stage?.transportType)
}, 0)
})
const totalTimeSeconds = computed(() => {
if (props.totalTimeSeconds != null) return props.totalTimeSeconds
if (!props.stages?.length) return null
const sum = props.stages.reduce((acc, stage) => acc + (stage?.travelTimeSeconds || 0), 0)
return sum > 0 ? sum : null
})
const durationDisplay = computed(() => formatDuration(totalTimeSeconds.value))
const routeRows = computed(() =>
(props.stages || [])
.filter(stage => stage?.distanceKm != null)

View File

@@ -5,6 +5,10 @@
"default_unit": "t",
"unit_kg": "kg",
"distance_km": "{km} km",
"duration_label": "ETA",
"time_days_short": "d",
"time_hours_short": "h",
"time_minutes_short": "m",
"country_unknown": "Not specified",
"supplier_unknown": "Supplier",
"origin_label": "From",

View File

@@ -5,6 +5,10 @@
"default_unit": "т",
"unit_kg": "кг",
"distance_km": "{km} км",
"duration_label": "Срок",
"time_days_short": "д",
"time_hours_short": "ч",
"time_minutes_short": "м",
"country_unknown": "Не указана",
"supplier_unknown": "Поставщик",
"origin_label": "Откуда",