From 20e0e73c58cdf10b8f5b8a8a6a1a96dd08ee3c16 Mon Sep 17 00:00:00 2001
From: Ruslan Bakiev <572431+veikab@users.noreply.github.com>
Date: Tue, 27 Jan 2026 10:35:14 +0700
Subject: [PATCH] refactor: remove any types and fix TypeScript errors
- Export InfoProductItem, InfoHubItem, InfoSupplierItem, InfoOfferItem types
- Update InfoEntity interface to have explicit fields (no index signature)
- Export CatalogHubItem, CatalogNearestHubItem from useCatalogHubs
- Fix MapItem interfaces to accept nullable GraphQL types
- Fix v-for :key bindings to handle null uuid
- Add null guards in select-location pages
- Update HubCard to accept nullable transportTypes
- Add shims.d.ts for missing module declarations
---
app/components/catalog/CatalogMap.vue | 26 ++++++++++-------
app/components/catalog/HubCard.vue | 4 +--
app/components/catalog/InfoPanel.vue | 42 +++++++++++++++------------
app/components/page/CatalogPage.vue | 7 ++---
app/composables/useCatalogHubs.ts | 10 +++++--
app/composables/useCatalogInfo.ts | 42 +++++++++++++++++----------
app/pages/select-location/index.vue | 21 ++++++++------
app/pages/select-location/map.vue | 12 ++++----
app/shims.d.ts | 38 ++++++++++++++++++++++++
9 files changed, 133 insertions(+), 69 deletions(-)
create mode 100644 app/shims.d.ts
diff --git a/app/components/catalog/CatalogMap.vue b/app/components/catalog/CatalogMap.vue
index e1194e6..f98a1d0 100644
--- a/app/components/catalog/CatalogMap.vue
+++ b/app/components/catalog/CatalogMap.vue
@@ -20,11 +20,11 @@ import { LngLatBounds } from 'mapbox-gl'
import type { ClusterPointType } from '~/composables/graphql/public/geo-generated'
interface MapItem {
- uuid: string
- name: string
- latitude: number
- longitude: number
- country?: string
+ uuid?: string | null
+ name?: string | null
+ latitude?: number | null
+ longitude?: number | null
+ country?: string | null
}
export interface MapBounds {
@@ -186,11 +186,13 @@ const mapOptions = computed(() => ({
// Client-side clustering GeoJSON (when not using server clustering)
const geoJsonData = computed(() => ({
type: 'FeatureCollection' as const,
- features: props.items.map(item => ({
- type: 'Feature' as const,
- properties: { uuid: item.uuid, name: item.name, country: item.country },
- geometry: { type: 'Point' as const, coordinates: [item.longitude, item.latitude] }
- }))
+ features: props.items
+ .filter(item => item.latitude != null && item.longitude != null)
+ .map(item => ({
+ type: 'Feature' as const,
+ properties: { uuid: item.uuid, name: item.name, country: item.country },
+ geometry: { type: 'Point' as const, coordinates: [item.longitude!, item.latitude!] }
+ }))
}))
// Server-side clustering GeoJSON
@@ -481,7 +483,9 @@ const initClientClusteringLayers = async (map: MapboxMapType) => {
if (!didFitBounds.value && props.items.length > 0) {
const bounds = new LngLatBounds()
props.items.forEach(item => {
- bounds.extend([item.longitude, item.latitude])
+ if (item.longitude != null && item.latitude != null) {
+ bounds.extend([item.longitude, item.latitude])
+ }
})
map.fitBounds(bounds, { padding: 50, maxZoom: 10 })
didFitBounds.value = true
diff --git a/app/components/catalog/HubCard.vue b/app/components/catalog/HubCard.vue
index fbde52e..46514b0 100644
--- a/app/components/catalog/HubCard.vue
+++ b/app/components/catalog/HubCard.vue
@@ -48,7 +48,7 @@ interface Hub {
country?: string | null
countryCode?: string | null
distance?: string
- transportTypes?: string[] | null
+ transportTypes?: (string | null)[] | null
}
const props = defineProps<{
@@ -81,5 +81,5 @@ const countryFlag = computed(() => {
return '🌍'
})
-const hasTransport = (type: string) => props.hub.transportTypes?.includes(type)
+const hasTransport = (type: string) => props.hub.transportTypes?.some(t => t === type)
diff --git a/app/components/catalog/InfoPanel.vue b/app/components/catalog/InfoPanel.vue
index e8ce47f..bfbac05 100644
--- a/app/components/catalog/InfoPanel.vue
+++ b/app/components/catalog/InfoPanel.vue
@@ -48,7 +48,7 @@
@click="emit('open-info', 'supplier', entity.teamUuid)"
>
- {{ entity.teamName || $t('catalog.info.viewSupplier') }}
+ {{ entity.supplierName || entity.teamName || $t('catalog.info.viewSupplier') }}
@@ -66,8 +66,8 @@
import type { InfoEntityType } from '~/composables/useCatalogSearch'
+import type {
+ InfoEntity,
+ InfoProductItem,
+ InfoHubItem,
+ InfoSupplierItem,
+ InfoOfferItem
+} from '~/composables/useCatalogInfo'
const props = defineProps<{
entityType: InfoEntityType
entityId: string
- entity: any
- relatedProducts?: any[]
- relatedHubs?: any[]
- relatedSuppliers?: any[]
- relatedOffers?: any[]
+ entity: InfoEntity | null
+ relatedProducts?: InfoProductItem[]
+ relatedHubs?: InfoHubItem[]
+ relatedSuppliers?: InfoSupplierItem[]
+ relatedOffers?: InfoOfferItem[]
selectedProduct?: string | null
currentTab?: string
loading?: boolean
@@ -209,20 +216,17 @@ const formatPrice = (price: number | string) => {
}
// Handlers for selecting related items
-const onProductSelect = (product: any) => {
- if (product.uuid) {
- // Navigate to offer info for this product
- emit('select-product', product.uuid)
- }
+const onProductSelect = (product: InfoProductItem) => {
+ emit('select-product', product.uuid)
}
-const onHubSelect = (hub: any) => {
+const onHubSelect = (hub: InfoHubItem) => {
if (hub.uuid) {
emit('open-info', 'hub', hub.uuid)
}
}
-const onSupplierSelect = (supplier: any) => {
+const onSupplierSelect = (supplier: InfoSupplierItem) => {
if (supplier.uuid) {
emit('open-info', 'supplier', supplier.uuid)
}
diff --git a/app/components/page/CatalogPage.vue b/app/components/page/CatalogPage.vue
index 13a5723..b6dc557 100644
--- a/app/components/page/CatalogPage.vue
+++ b/app/components/page/CatalogPage.vue
@@ -233,12 +233,11 @@ const activeClusterNodeType = computed(() => VIEW_MODE_NODE_TYPES[mapViewMode.va
const currentBounds = ref(null)
interface MapItem {
- uuid: string
+ uuid?: string | null
latitude?: number | null
longitude?: number | null
- name?: string
- country?: string
- [key: string]: any
+ name?: string | null
+ country?: string | null
}
const props = withDefaults(defineProps<{
diff --git a/app/composables/useCatalogHubs.ts b/app/composables/useCatalogHubs.ts
index c9306ac..3e9fdce 100644
--- a/app/composables/useCatalogHubs.ts
+++ b/app/composables/useCatalogHubs.ts
@@ -3,9 +3,13 @@ import { HubsListDocument, GetHubCountriesDocument, NearestHubsDocument } from '
const PAGE_SIZE = 24
-// Type from codegen
-type HubItem = NonNullable[number]>
-type NearestHubItem = NonNullable[number]>
+// Type from codegen - exported for use in pages
+export type CatalogHubItem = NonNullable[number]>
+export type CatalogNearestHubItem = NonNullable[number]>
+
+// Internal aliases
+type HubItem = CatalogHubItem
+type NearestHubItem = CatalogNearestHubItem
// Shared state across list and map views
const items = ref>([])
diff --git a/app/composables/useCatalogInfo.ts b/app/composables/useCatalogInfo.ts
index 70fd96a..f6e3bbc 100644
--- a/app/composables/useCatalogInfo.ts
+++ b/app/composables/useCatalogInfo.ts
@@ -26,33 +26,43 @@ type HubItem = NonNullable[nu
type OfferItem = NonNullable[number]>
// Product type (aggregated from offers)
-interface ProductItem {
+export interface InfoProductItem {
uuid: string
name: string
offersCount?: number
}
-// Extended entity type with optional supplierName
-// Using intersection to allow both coordinate patterns (node vs offer)
-interface InfoEntity {
+// Re-export types for InfoPanel
+export type InfoHubItem = HubItem
+export type InfoSupplierItem = SupplierProfile
+export type InfoOfferItem = OfferItem
+
+// Extended entity type with all known fields (NO index signature!)
+export interface InfoEntity {
uuid?: string | null
name?: string | null
// Node coordinates
latitude?: number | null
longitude?: number | null
+ // Location fields
+ address?: string | null
+ city?: string | null
+ country?: string | null
// Offer coordinates (different field names)
locationLatitude?: number | null
locationLongitude?: number | null
- locationUuid?: string
- locationName?: string
+ locationUuid?: string | null
+ locationName?: string | null
// Offer fields
- productUuid?: string
- productName?: string
- teamUuid?: string
- // Enriched field
- supplierName?: string
- // Allow any other fields
- [key: string]: unknown
+ productUuid?: string | null
+ productName?: string | null
+ teamUuid?: string | null
+ teamName?: string | null
+ pricePerUnit?: number | string | null
+ currency?: string | null
+ unit?: string | null
+ // Enriched field from supplier profile
+ supplierName?: string | null
}
// Helper to get coordinates from entity (handles both node and offer patterns)
@@ -73,7 +83,7 @@ export function useCatalogInfo() {
// State with proper types
const entity = ref(null)
const entityType = ref(null)
- const relatedProducts = ref([])
+ const relatedProducts = ref([])
const relatedHubs = ref([])
const relatedSuppliers = ref([])
const relatedOffers = ref([])
@@ -119,7 +129,7 @@ export function useCatalogInfo() {
'geo'
).then(offersData => {
// Group offers by product
- const productsMap = new Map()
+ const productsMap = new Map()
const suppliersMap = new Map()
offersData?.nearestOffers?.forEach(offer => {
@@ -224,7 +234,7 @@ export function useCatalogInfo() {
'geo'
).then(offersData => {
// Group offers by product
- const productsMap = new Map()
+ const productsMap = new Map()
offersData?.nearestOffers?.forEach(offer => {
if (!offer || !offer.productUuid || !offer.productName) return
const existing = productsMap.get(offer.productUuid)
diff --git a/app/pages/select-location/index.vue b/app/pages/select-location/index.vue
index 1ee5fc5..e790a06 100644
--- a/app/pages/select-location/index.vue
+++ b/app/pages/select-location/index.vue
@@ -78,6 +78,8 @@