- Add viewMode toggle (Список/Карта) to catalog page - Create CatalogHubsSection with country grouping - Create CatalogOffersSection with status badges - Create CatalogSuppliersSection with verification badges - Create CatalogMapView with left panel tabs (Хабы/Офферы/Компании) - Create CatalogMapItem for clickable list items with flyTo animation - Integrate with existing MapboxGlobe component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
114 lines
3.2 KiB
Vue
114 lines
3.2 KiB
Vue
<template>
|
|
<Section variant="plain" paddingY="md">
|
|
<Card>
|
|
<Stack gap="4">
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Heading :level="2">Предложения</Heading>
|
|
<NuxtLink :to="localePath('/catalog/offers')">
|
|
<Button variant="secondary" size="small">Смотреть все</Button>
|
|
</NuxtLink>
|
|
</Stack>
|
|
|
|
<Grid :cols="1" :md="2" :lg="3" :gap="4">
|
|
<Card
|
|
v-for="offer in displayedOffers"
|
|
:key="offer.uuid"
|
|
padding="sm"
|
|
tone="muted"
|
|
interactive
|
|
>
|
|
<Stack gap="3">
|
|
<Stack gap="1">
|
|
<Text size="base" weight="semibold">{{ offer.title }}</Text>
|
|
<Text tone="muted">{{ offer.locationName || 'Локация не указана' }}</Text>
|
|
</Stack>
|
|
|
|
<Stack direction="row" gap="2" wrap>
|
|
<Pill
|
|
v-for="line in (offer.lines || []).slice(0, 3)"
|
|
:key="line?.uuid"
|
|
variant="outline"
|
|
>
|
|
{{ line?.productName }}
|
|
</Pill>
|
|
<Pill v-if="(offer.lines?.length || 0) > 3" variant="outline">
|
|
+{{ (offer.lines?.length || 0) - 3 }}
|
|
</Pill>
|
|
</Stack>
|
|
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Badge :variant="getStatusVariant(offer.status)">
|
|
{{ getStatusLabel(offer.status) }}
|
|
</Badge>
|
|
<Text v-if="offer.validUntil" tone="muted" size="sm">
|
|
до {{ formatDate(offer.validUntil) }}
|
|
</Text>
|
|
</Stack>
|
|
</Stack>
|
|
</Card>
|
|
</Grid>
|
|
|
|
<Stack v-if="offers.length === 0" align="center" gap="2">
|
|
<Text tone="muted">Нет активных предложений</Text>
|
|
</Stack>
|
|
</Stack>
|
|
</Card>
|
|
</Section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface OfferLine {
|
|
uuid?: string | null
|
|
productName?: string | null
|
|
}
|
|
|
|
interface Offer {
|
|
uuid?: string | null
|
|
title?: string | null
|
|
locationName?: string | null
|
|
status?: string | null
|
|
validUntil?: string | null
|
|
lines?: (OfferLine | null)[] | null
|
|
}
|
|
|
|
const props = defineProps<{
|
|
offers: Offer[]
|
|
}>()
|
|
|
|
const localePath = useLocalePath()
|
|
|
|
const displayedOffers = computed(() => props.offers.slice(0, 6))
|
|
|
|
const getStatusVariant = (status: string | null | undefined) => {
|
|
const variants: Record<string, string> = {
|
|
ACTIVE: 'success',
|
|
DRAFT: 'warning',
|
|
CANCELLED: 'error',
|
|
CLOSED: 'muted'
|
|
}
|
|
return variants[status || ''] || 'muted'
|
|
}
|
|
|
|
const getStatusLabel = (status: string | null | undefined) => {
|
|
const labels: Record<string, string> = {
|
|
ACTIVE: 'Активно',
|
|
DRAFT: 'Черновик',
|
|
CANCELLED: 'Отменено',
|
|
CLOSED: 'Закрыто'
|
|
}
|
|
return labels[status || ''] || status || 'Неизвестно'
|
|
}
|
|
|
|
const formatDate = (dateStr: string | null | undefined) => {
|
|
if (!dateStr) return ''
|
|
try {
|
|
return new Intl.DateTimeFormat('ru', {
|
|
day: 'numeric',
|
|
month: 'short'
|
|
}).format(new Date(dateStr))
|
|
} catch {
|
|
return dateStr
|
|
}
|
|
}
|
|
</script>
|