Enrich offer card origin, price, and duration
All checks were successful
Build Docker Image / build (push) Successful in 4m40s
All checks were successful
Build Docker Image / build (push) Successful in 4m40s
This commit is contained in:
@@ -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
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Откуда",
|
||||
|
||||
Reference in New Issue
Block a user