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"
|
:currency="getOfferData(option.sourceUuid)?.currency"
|
||||||
:unit="getOfferData(option.sourceUuid)?.unit"
|
:unit="getOfferData(option.sourceUuid)?.unit"
|
||||||
:stages="getRouteStages(option)"
|
:stages="getRouteStages(option)"
|
||||||
|
:total-time-seconds="option.routes?.[0]?.totalTimeSeconds ?? null"
|
||||||
:kyc-profile-uuid="getKycProfileUuid(option.sourceUuid)"
|
:kyc-profile-uuid="getKycProfileUuid(option.sourceUuid)"
|
||||||
@select="navigateToOffer(option.sourceUuid)"
|
@select="navigateToOffer(option.sourceUuid)"
|
||||||
/>
|
/>
|
||||||
@@ -209,7 +210,9 @@ const getRouteStages = (option: ProductRouteOption) => {
|
|||||||
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
|
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
|
||||||
.map((stage) => ({
|
.map((stage) => ({
|
||||||
transportType: stage.transportType,
|
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"
|
v-for="(offer, index) in offersWithPrice"
|
||||||
:key="offer.uuid ?? index"
|
:key="offer.uuid ?? index"
|
||||||
:supplier-name="offer.supplierName"
|
: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"
|
:product-name="offer.productName"
|
||||||
:price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null"
|
:price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null"
|
||||||
:quantity="offer.quantity"
|
:quantity="offer.quantity"
|
||||||
:currency="offer.currency"
|
:currency="offer.currency"
|
||||||
:unit="offer.unit"
|
:unit="offer.unit"
|
||||||
:stages="getOfferStages(offer)"
|
:stages="getOfferStages(offer)"
|
||||||
|
:total-time-seconds="offer.routes?.[0]?.totalTimeSeconds ?? null"
|
||||||
@select="onOfferSelect(offer)"
|
@select="onOfferSelect(offer)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -465,7 +466,9 @@ const getOfferStages = (offer: InfoOfferItem) => {
|
|||||||
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
|
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
|
||||||
.map(stage => ({
|
.map(stage => ({
|
||||||
transportType: stage.transportType,
|
transportType: stage.transportType,
|
||||||
distanceKm: stage.distanceKm
|
distanceKm: stage.distanceKm,
|
||||||
|
travelTimeSeconds: stage.travelTimeSeconds,
|
||||||
|
fromName: stage.fromName
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -26,9 +26,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Text v-if="priceDisplay" weight="semibold" class="text-primary text-lg text-right">
|
<div class="text-right">
|
||||||
{{ priceDisplay }}
|
<Text v-if="priceDisplay" weight="semibold" class="text-primary text-lg">
|
||||||
</Text>
|
{{ priceDisplay }}
|
||||||
|
</Text>
|
||||||
|
<Text v-if="durationDisplay" size="xs" class="text-base-content/60">
|
||||||
|
{{ t('catalogOfferCard.labels.duration_label') }} {{ durationDisplay }}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Supplier info -->
|
<!-- Supplier info -->
|
||||||
@@ -45,7 +50,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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<{
|
const props = withDefaults(defineProps<{
|
||||||
locationName?: string
|
locationName?: string
|
||||||
@@ -56,8 +66,7 @@ const props = withDefaults(defineProps<{
|
|||||||
currency?: string | null
|
currency?: string | null
|
||||||
unit?: string | null
|
unit?: string | null
|
||||||
stages?: RouteStage[]
|
stages?: RouteStage[]
|
||||||
startName?: string
|
totalTimeSeconds?: number | null
|
||||||
endName?: string
|
|
||||||
kycProfileUuid?: string | null
|
kycProfileUuid?: string | null
|
||||||
}>(), {
|
}>(), {
|
||||||
stages: () => []
|
stages: () => []
|
||||||
@@ -74,14 +83,17 @@ const supplierDisplay = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const originDisplay = 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(() => {
|
const priceDisplay = computed(() => {
|
||||||
if (props.pricePerUnit == null) return null
|
if (props.pricePerUnit == null) return null
|
||||||
const currSymbol = getCurrencySymbol(props.currency)
|
const currSymbol = getCurrencySymbol(props.currency)
|
||||||
const unitName = getUnitName(props.unit)
|
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}`
|
return `${currSymbol}${formattedPrice}/${unitName}`
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -128,6 +140,21 @@ const formatDistance = (km?: number | null) => {
|
|||||||
return t('catalogOfferCard.labels.distance_km', { km: formatted })
|
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) => {
|
const getTransportIcon = (type?: string | null) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'rail':
|
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(() =>
|
const routeRows = computed(() =>
|
||||||
(props.stages || [])
|
(props.stages || [])
|
||||||
.filter(stage => stage?.distanceKm != null)
|
.filter(stage => stage?.distanceKm != null)
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
"default_unit": "t",
|
"default_unit": "t",
|
||||||
"unit_kg": "kg",
|
"unit_kg": "kg",
|
||||||
"distance_km": "{km} km",
|
"distance_km": "{km} km",
|
||||||
|
"duration_label": "ETA",
|
||||||
|
"time_days_short": "d",
|
||||||
|
"time_hours_short": "h",
|
||||||
|
"time_minutes_short": "m",
|
||||||
"country_unknown": "Not specified",
|
"country_unknown": "Not specified",
|
||||||
"supplier_unknown": "Supplier",
|
"supplier_unknown": "Supplier",
|
||||||
"origin_label": "From",
|
"origin_label": "From",
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
"default_unit": "т",
|
"default_unit": "т",
|
||||||
"unit_kg": "кг",
|
"unit_kg": "кг",
|
||||||
"distance_km": "{km} км",
|
"distance_km": "{km} км",
|
||||||
|
"duration_label": "Срок",
|
||||||
|
"time_days_short": "д",
|
||||||
|
"time_hours_short": "ч",
|
||||||
|
"time_minutes_short": "м",
|
||||||
"country_unknown": "Не указана",
|
"country_unknown": "Не указана",
|
||||||
"supplier_unknown": "Поставщик",
|
"supplier_unknown": "Поставщик",
|
||||||
"origin_label": "Откуда",
|
"origin_label": "Откуда",
|
||||||
|
|||||||
Reference in New Issue
Block a user