Files
webapp/app/components/catalog/OfferResultCard.vue
Ruslan Bakiev e4f81dba7c
All checks were successful
Build Docker Image / build (push) Successful in 4m53s
Redesign offer result card layout
2026-02-06 17:07:38 +07:00

154 lines
4.6 KiB
Vue

<template>
<Card padding="md" interactive @click="$emit('select')">
<!-- Header: Supplier + Price -->
<div class="flex items-start justify-between gap-4">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-full flex items-center justify-center" style="background-color: #3b82f6">
<Icon name="lucide:factory" size="14" class="text-white" />
</div>
<Text weight="semibold">{{ supplierDisplay }}</Text>
</div>
<div class="flex items-center gap-2 text-sm text-base-content/70">
<Icon name="lucide:map-pin" size="14" class="text-base-content/60" />
<span>{{ originDisplay }}</span>
</div>
<div v-if="productName" class="flex items-center gap-2 text-sm text-base-content/70">
<Icon name="lucide:package" size="14" class="text-base-content/60" />
<span>{{ productName }}</span>
</div>
<div v-if="quantityDisplay" class="flex items-center gap-2 text-sm text-base-content/70">
<Icon name="lucide:scale" size="14" class="text-base-content/60" />
<span>{{ quantityDisplay }}</span>
</div>
</div>
<Text v-if="priceDisplay" weight="semibold" class="text-primary text-lg text-right">
{{ priceDisplay }}
</Text>
</div>
<!-- Supplier info -->
<SupplierInfoBlock v-if="kycProfileUuid" :kyc-profile-uuid="kycProfileUuid" class="mb-3" />
<!-- Route lines -->
<div v-if="routeRows.length" class="mt-3 pt-2 border-t border-base-200/60">
<div v-for="(row, index) in routeRows" :key="index" class="flex items-center gap-2 text-sm text-base-content/70">
<Icon :name="row.icon" size="14" class="text-base-content/60" />
<span>{{ row.distanceLabel }}</span>
</div>
</div>
</Card>
</template>
<script setup lang="ts">
import type { RouteStage } from './RouteStepper.vue'
const props = withDefaults(defineProps<{
locationName?: string
supplierName?: string
productName?: string
pricePerUnit?: number | null
quantity?: number | string | null
currency?: string | null
unit?: string | null
stages?: RouteStage[]
startName?: string
endName?: string
kycProfileUuid?: string | null
}>(), {
stages: () => []
})
defineEmits<{
select: []
}>()
const { t } = useI18n()
const supplierDisplay = computed(() => {
return props.supplierName || t('catalogOfferCard.labels.supplier_unknown')
})
const originDisplay = computed(() => {
return props.locationName || 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()
return `${currSymbol}${formattedPrice}/${unitName}`
})
const quantityDisplay = computed(() => {
if (props.quantity == null || props.quantity === '') return null
const quantityValue = Number(props.quantity)
if (Number.isNaN(quantityValue)) return null
const formattedQuantity = quantityValue.toLocaleString()
const unitName = getUnitName(props.unit)
return t('catalogOfferCard.labels.quantity_with_unit', {
quantity: formattedQuantity,
unit: unitName
})
})
const getCurrencySymbol = (currency?: string | null) => {
switch (currency?.toUpperCase()) {
case 'USD': return '$'
case 'EUR': return '€'
case 'RUB': return '₽'
case 'CNY': return '¥'
default: return '$'
}
}
const getUnitName = (unit?: string | null) => {
switch (unit?.toLowerCase()) {
case 'т':
case 't':
case 'ton':
case 'tonne':
return t('catalogOfferCard.labels.default_unit')
case 'кг':
case 'kg':
return t('catalogOfferCard.labels.unit_kg')
default:
return t('catalogOfferCard.labels.default_unit')
}
}
const formatDistance = (km?: number | null) => {
if (km == null) return null
const formatted = Math.round(km).toLocaleString()
return t('catalogOfferCard.labels.distance_km', { km: formatted })
}
const getTransportIcon = (type?: string | null) => {
switch (type) {
case 'rail':
return 'lucide:train-front'
case 'sea':
return 'lucide:ship'
case 'road':
case 'auto':
default:
return 'lucide:truck'
}
}
const routeRows = computed(() =>
(props.stages || [])
.filter(stage => stage?.distanceKm != null)
.map(stage => ({
icon: getTransportIcon(stage?.transportType),
distanceLabel: formatDistance(stage?.distanceKm)
}))
.filter(row => !!row.distanceLabel)
)
</script>