diff --git a/app/components/CalcResultContent.vue b/app/components/CalcResultContent.vue index f7497cd..e9e652e 100644 --- a/app/components/CalcResultContent.vue +++ b/app/components/CalcResultContent.vue @@ -24,6 +24,7 @@ :location-name="getOfferData(option.sourceUuid)?.locationName" :product-name="productName" :price-per-unit="parseFloat(getOfferData(option.sourceUuid)?.pricePerUnit || '0') || null" + :quantity="getOfferData(option.sourceUuid)?.quantity" :currency="getOfferData(option.sourceUuid)?.currency" :unit="getOfferData(option.sourceUuid)?.unit" :stages="getRouteStages(option)" diff --git a/app/components/MapSidebar.vue b/app/components/MapSidebar.vue index 53e043d..f5a1598 100644 --- a/app/components/MapSidebar.vue +++ b/app/components/MapSidebar.vue @@ -88,6 +88,7 @@ :location-name="offer.locationName || offer.locationCountry" :product-name="offer.productName" :price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null" + :quantity="offer.quantity" :currency="offer.currency" :unit="offer.unit" :stages="[]" diff --git a/app/components/catalog/CatalogMapPanel.vue b/app/components/catalog/CatalogMapPanel.vue index 26bb311..7135334 100644 --- a/app/components/catalog/CatalogMapPanel.vue +++ b/app/components/catalog/CatalogMapPanel.vue @@ -44,6 +44,7 @@ :location-name="offer.locationName" :product-name="offer.productName || offer.title || undefined" :price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null" + :quantity="offer.quantity" :currency="offer.currency" :unit="offer.unit" :stages="[]" @@ -91,6 +92,7 @@ interface Offer { status?: string | null latitude?: number | null longitude?: number | null + quantity?: number | string | null pricePerUnit?: number | string | null currency?: string | null unit?: string | null diff --git a/app/components/catalog/CatalogOffersSection.vue b/app/components/catalog/CatalogOffersSection.vue index fbfba7d..3576c25 100644 --- a/app/components/catalog/CatalogOffersSection.vue +++ b/app/components/catalog/CatalogOffersSection.vue @@ -20,6 +20,7 @@ :location-name="offer.locationName" :product-name="offer.title || undefined" :price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null" + :quantity="offer.quantity" :currency="offer.currency" :unit="offer.unit" :stages="[]" @@ -56,6 +57,7 @@ interface Offer { status?: string | null validUntil?: string | null lines?: (OfferLine | null)[] | null + quantity?: number | string | null pricePerUnit?: number | string | null currency?: string | null unit?: string | null diff --git a/app/components/catalog/InfoPanel.vue b/app/components/catalog/InfoPanel.vue index bf69bad..1122d1c 100644 --- a/app/components/catalog/InfoPanel.vue +++ b/app/components/catalog/InfoPanel.vue @@ -142,9 +142,13 @@ ({{ offersWithPrice.length }}) - @@ -159,6 +163,7 @@ :location-name="offer.locationName || offer.locationCountry || 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)" @@ -341,6 +346,12 @@ const offersWithPrice = computed(() => relatedOffers.value.filter(o => o?.pricePerUnit != null) ) +const selectedProductName = computed(() => { + if (!props.selectedProduct) return '' + const match = relatedProducts.value.find(p => p.uuid === props.selectedProduct) + return match?.name || props.selectedProduct.slice(0, 8) + '...' +}) + // Entity name const entityName = computed(() => { return props.entity?.name || props.entity?.productName || props.entityId.slice(0, 8) + '...' diff --git a/app/components/catalog/OfferResultCard.vue b/app/components/catalog/OfferResultCard.vue index 7006c6f..4280df4 100644 --- a/app/components/catalog/OfferResultCard.vue +++ b/app/components/catalog/OfferResultCard.vue @@ -1,15 +1,32 @@ @@ -35,6 +52,7 @@ const props = withDefaults(defineProps<{ supplierName?: string productName?: string pricePerUnit?: number | null + quantity?: number | string | null currency?: string | null unit?: string | null stages?: RouteStage[] @@ -60,13 +78,25 @@ const originDisplay = computed(() => { }) const priceDisplay = computed(() => { - if (!props.pricePerUnit) return null + if (props.pricePerUnit == null) return null const currSymbol = getCurrencySymbol(props.currency) const unitName = getUnitName(props.unit) - const formattedPrice = props.pricePerUnit.toLocaleString() + 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 '$' @@ -80,14 +110,44 @@ const getCurrencySymbol = (currency?: string | null) => { const getUnitName = (unit?: string | null) => { switch (unit?.toLowerCase()) { case 'т': + case 't': case 'ton': case 'tonne': - return 'тонна' + return t('catalogOfferCard.labels.default_unit') case 'кг': case 'kg': - return 'кг' + return t('catalogOfferCard.labels.unit_kg') default: - return 'тонна' + 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) +) diff --git a/app/components/catalog/QuotePanel.vue b/app/components/catalog/QuotePanel.vue index 6dfe02b..50d16f5 100644 --- a/app/components/catalog/QuotePanel.vue +++ b/app/components/catalog/QuotePanel.vue @@ -31,6 +31,7 @@ :location-name="offer.locationName || offer.locationCountry" :product-name="offer.productName" :price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null" + :quantity="offer.quantity" :currency="offer.currency" :unit="offer.unit" :stages="[]" diff --git a/i18n/locales/en/catalogOfferCard.json b/i18n/locales/en/catalogOfferCard.json index fe669aa..57b8f61 100644 --- a/i18n/locales/en/catalogOfferCard.json +++ b/i18n/locales/en/catalogOfferCard.json @@ -3,6 +3,8 @@ "labels": { "quantity_with_unit": "{quantity} {unit}", "default_unit": "t", + "unit_kg": "kg", + "distance_km": "{km} km", "country_unknown": "Not specified", "supplier_unknown": "Supplier", "origin_label": "From", diff --git a/i18n/locales/ru/catalogOfferCard.json b/i18n/locales/ru/catalogOfferCard.json index 310d74f..b645558 100644 --- a/i18n/locales/ru/catalogOfferCard.json +++ b/i18n/locales/ru/catalogOfferCard.json @@ -3,6 +3,8 @@ "labels": { "quantity_with_unit": "{quantity} {unit}", "default_unit": "т", + "unit_kg": "кг", + "distance_km": "{km} км", "country_unknown": "Не указана", "supplier_unknown": "Поставщик", "origin_label": "Откуда",