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 @@