Fix all TypeScript errors and remove Storybook
All checks were successful
Build Docker Image / build (push) Successful in 5m8s

- Remove all Storybook files and configuration
- Add type declarations for @vueuse/core, @formkit/core, vue3-apexcharts
- Fix TypeScript configuration (typeRoots, include paths)
- Fix Sentry config - move settings to plugin
- Fix nullable prop assignments with ?? operator
- Fix type narrowing issues with explicit type assertions
- Fix Card component linkable computed properties
- Update codegen with operationResultSuffix
- Fix GraphQL operation type definitions
This commit is contained in:
Ruslan Bakiev
2026-01-26 00:32:36 +07:00
parent b326d8cd76
commit 2b6cccdead
99 changed files with 419 additions and 1171 deletions

View File

@@ -48,5 +48,5 @@ defineEmits<{
const localePath = useLocalePath()
const { t } = useI18n()
const linkable = computed(() => !props.selectable && props.address.uuid)
const linkable = computed(() => !props.selectable && !!props.address.uuid)
</script>

View File

@@ -17,8 +17,8 @@
<Text weight="semibold" class="mb-3">{{ country.name }}</Text>
<Grid :cols="1" :md="2" :lg="3" :gap="4">
<HubCard
v-for="hub in country.hubs"
:key="hub.uuid"
v-for="(hub, index) in country.hubs"
:key="hub.uuid ?? index"
:hub="hub"
/>
</Grid>

View File

@@ -319,20 +319,22 @@ const initClientClusteringLayers = async (map: MapboxMapType) => {
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] })
if (!features.length) return
const clusterId = features[0].properties?.cluster_id
const feature = features[0]
if (!feature) return
const clusterId = feature.properties?.cluster_id
const source = map.getSource(sourceId.value) as mapboxgl.GeoJSONSource
source.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) return
const geometry = features[0].geometry as GeoJSON.Point
const geometry = feature.geometry as GeoJSON.Point
map.easeTo({ center: geometry.coordinates as [number, number], zoom: zoom || 4 })
})
})
map.on('click', 'unclustered-point', (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ['unclustered-point'] })
if (!features.length) return
emit('select-item', features[0].properties?.uuid)
const feature = features[0]
if (!feature) return
emit('select-item', feature.properties?.uuid)
})
map.on('mouseenter', 'clusters', () => { map.getCanvas().style.cursor = 'pointer' })
@@ -406,8 +408,9 @@ const initClientClusteringLayers = async (map: MapboxMapType) => {
// Click handlers for related points
map.on('click', `${props.mapId}-related-circles`, (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: [`${props.mapId}-related-circles`] })
if (!features.length) return
const props_data = features[0].properties
const feature = features[0]
if (!feature) return
const props_data = feature.properties as Record<string, any> | undefined
emit('select-item', props_data?.uuid, props_data)
})
@@ -500,9 +503,10 @@ const initServerClusteringLayers = async (map: MapboxMapType) => {
// Click on cluster to zoom in
map.on('click', 'server-clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ['server-clusters'] })
if (!features.length) return
const expansionZoom = features[0].properties?.expansionZoom
const geometry = features[0].geometry as GeoJSON.Point
const feature = features[0]
if (!feature) return
const expansionZoom = feature.properties?.expansionZoom
const geometry = feature.geometry as GeoJSON.Point
map.easeTo({
center: geometry.coordinates as [number, number],
zoom: expansionZoom || map.getZoom() + 2
@@ -512,8 +516,9 @@ const initServerClusteringLayers = async (map: MapboxMapType) => {
// Click on individual point - emit full properties
map.on('click', 'server-points', (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ['server-points'] })
if (!features.length) return
const props = features[0].properties || {}
const feature = features[0]
if (!feature) return
const props = feature.properties || {}
emit('select-item', props.id, props)
})
@@ -588,8 +593,9 @@ const initServerClusteringLayers = async (map: MapboxMapType) => {
// Click handlers for related points
map.on('click', `${props.mapId}-related-circles`, (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: [`${props.mapId}-related-circles`] })
if (!features.length) return
const props_data = features[0].properties
const feature = features[0]
if (!feature) return
const props_data = feature.properties as Record<string, any> | undefined
emit('select-item', props_data?.uuid, props_data)
})

View File

@@ -23,8 +23,8 @@
<!-- Hubs Tab -->
<template v-if="activeTab === 'hubs'">
<HubCard
v-for="hub in hubs"
:key="hub.uuid"
v-for="(hub, index) in hubs"
:key="hub.uuid ?? index"
:hub="hub"
selectable
:is-selected="selectedId === hub.uuid"
@@ -38,8 +38,8 @@
<!-- Offers Tab -->
<template v-if="activeTab === 'offers'">
<OfferCard
v-for="offer in offers"
:key="offer.uuid"
v-for="(offer, index) in offers"
:key="offer.uuid ?? index"
:offer="offer"
selectable
compact
@@ -54,8 +54,8 @@
<!-- Suppliers Tab -->
<template v-if="activeTab === 'suppliers'">
<SupplierCard
v-for="supplier in suppliers"
:key="supplier.uuid"
v-for="(supplier, index) in suppliers"
:key="supplier.uuid ?? index"
:supplier="supplier"
selectable
:is-selected="selectedId === supplier.uuid"

View File

@@ -15,7 +15,7 @@
<div v-if="filters && filters.length > 0" class="p-4 border-b border-base-300">
<CatalogFilters
:filters="filters"
:model-value="selectedFilter"
:model-value="selectedFilter ?? 'all'"
@update:model-value="$emit('update:selectedFilter', $event)"
/>
</div>

View File

@@ -14,8 +14,8 @@
<Grid :cols="1" :md="2" :lg="3" :gap="4">
<OfferCard
v-for="offer in offers"
:key="offer.uuid"
v-for="(offer, index) in offers"
:key="offer.uuid ?? index"
:offer="offer"
/>
</Grid>

View File

@@ -14,8 +14,8 @@
<Grid :cols="1" :md="2" :lg="3" :gap="4">
<SupplierCard
v-for="supplier in suppliers"
:key="supplier.uuid"
v-for="(supplier, index) in suppliers"
:key="supplier.uuid ?? index"
:supplier="supplier"
/>
</Grid>

View File

@@ -66,7 +66,7 @@ defineEmits<{
const localePath = useLocalePath()
const { t } = useI18n()
const linkable = computed(() => !props.selectable && (props.linkTo || props.hub.uuid))
const linkable = computed(() => !props.selectable && !!(props.linkTo || props.hub.uuid))
const resolvedLink = computed(() => props.linkTo || localePath(`/catalog/hubs/${props.hub.uuid}`))
// ISO code to emoji flag

View File

@@ -76,7 +76,7 @@ const trend = computed(() => {
if (props.priceHistory.length < 2) return 0
const first = props.priceHistory[0]
const last = props.priceHistory[props.priceHistory.length - 1]
if (!first || first === 0) return 0
if (!first || first === 0 || !last) return 0
return Math.round(((last - first) / first) * 100)
})

View File

@@ -186,12 +186,18 @@ const emit = defineEmits<{
'close': []
'add-to-filter': []
'open-info': [type: InfoEntityType, uuid: string]
'select-product': [uuid: string]
'select-product': [uuid: string | null]
}>()
const { t } = useI18n()
const { entityColors } = useCatalogSearch()
// Safe accessors for optional arrays
const relatedProducts = computed(() => props.relatedProducts ?? [])
const relatedHubs = computed(() => props.relatedHubs ?? [])
const relatedSuppliers = computed(() => props.relatedSuppliers ?? [])
const relatedOffers = computed(() => props.relatedOffers ?? [])
// Current active tab
const currentTab = ref<string>('offers')
@@ -279,8 +285,9 @@ const availableTabs = computed(() => {
watch(
() => props.entityType,
() => {
if (availableTabs.value.length > 0) {
currentTab.value = availableTabs.value[0].id
const firstTab = availableTabs.value[0]
if (firstTab) {
currentTab.value = firstTab.id
}
},
{ immediate: true }

View File

@@ -73,7 +73,7 @@ defineEmits<{
const localePath = useLocalePath()
const { t } = useI18n()
const linkable = computed(() => !props.selectable && props.offer.uuid)
const linkable = computed(() => !props.selectable && !!props.offer.uuid)
const formattedDate = computed(() => {
if (!props.offer.validUntil) return ''

View File

@@ -23,7 +23,9 @@ const props = defineProps<{
const isUptrend = computed(() => {
if (props.data.length < 2) return true
return props.data[props.data.length - 1] >= props.data[0]
const last = props.data[props.data.length - 1]
const first = props.data[0]
return (last ?? 0) >= (first ?? 0)
})
const lineColor = computed(() => isUptrend.value ? '#22c55e' : '#ef4444')

View File

@@ -48,5 +48,5 @@ defineEmits<{
const localePath = useLocalePath()
const { t } = useI18n()
const linkable = computed(() => !props.selectable && props.product.uuid)
const linkable = computed(() => !props.selectable && !!props.product.uuid)
</script>

View File

@@ -29,9 +29,9 @@
<!-- Products -->
<template v-if="selectMode === 'product'">
<div
v-for="item in filteredItems"
:key="item.uuid"
@mouseenter="emit('hover', item.uuid)"
v-for="(item, index) in filteredItems"
:key="item.uuid ?? index"
@mouseenter="emit('hover', item.uuid ?? null)"
@mouseleave="emit('hover', null)"
>
<ProductCard
@@ -47,9 +47,9 @@
<!-- Hubs -->
<template v-else-if="selectMode === 'hub'">
<div
v-for="item in filteredItems"
:key="item.uuid"
@mouseenter="emit('hover', item.uuid)"
v-for="(item, index) in filteredItems"
:key="item.uuid ?? index"
@mouseenter="emit('hover', item.uuid ?? null)"
@mouseleave="emit('hover', null)"
>
<HubCard
@@ -64,9 +64,9 @@
<!-- Suppliers -->
<template v-else-if="selectMode === 'supplier'">
<div
v-for="item in filteredItems"
:key="item.uuid"
@mouseenter="emit('hover', item.uuid)"
v-for="(item, index) in filteredItems"
:key="item.uuid ?? index"
@mouseenter="emit('hover', item.uuid ?? null)"
@mouseleave="emit('hover', null)"
>
<SupplierCard

View File

@@ -71,7 +71,7 @@ defineEmits<{
const localePath = useLocalePath()
const { t } = useI18n()
const linkable = computed(() => !props.selectable && props.supplier.uuid)
const linkable = computed(() => !props.selectable && !!props.supplier.uuid)
const reliabilityLabel = computed(() => {
if (props.supplier.onTimeRate !== undefined && props.supplier.onTimeRate !== null) {