From 2dbe600d8ae4f7108e9d6147dd3b45a4a10e9b7c Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:34:12 +0700 Subject: [PATCH] refactor: remove all any types, add strict GraphQL scalar typing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add strictScalars: true to codegen.ts with proper scalar mappings (Date, Decimal, JSONString, JSON, UUID, BigInt → string/Record) - Replace all ref with proper GraphQL-derived types - Add type guards for null filtering in arrays - Fix bugs exposed by typing (locationLatitude vs latitude, etc.) - Add interfaces for external components (MapboxSearchBox) This enables end-to-end type safety from GraphQL schema to frontend. --- app/components/BankSearchRussia.vue | 22 ++-- app/components/CalcResultContent.vue | 45 ++++---- app/components/GoodsContent.vue | 6 +- app/components/KYCFormRussia.vue | 40 ++++++- app/components/LocationsContent.vue | 44 +++++--- app/components/MapSidebar.vue | 58 ++++++++-- app/components/RouteMap.vue | 70 ++++++++---- app/components/TeamCreateForm.vue | 5 +- app/components/catalog/CatalogMapPanel.vue | 1 - app/components/catalog/ExplorePanel.vue | 5 +- app/components/catalog/QuotePanel.vue | 3 +- app/components/catalog/SelectionPanel.vue | 2 +- .../graphql/public/exchange-generated.ts | 14 +-- .../graphql/public/geo-generated.ts | 6 +- .../graphql/team/exchange-generated.ts | 10 +- app/composables/graphql/user/kyc-generated.ts | 8 +- app/composables/useCatalogHubs.ts | 2 +- app/composables/useCatalogProducts.ts | 56 ++++----- app/composables/useTeamAddresses.ts | 8 +- app/composables/useTeamOrders.ts | 20 ++-- app/layouts/topnav.vue | 10 +- app/pages/catalog/hubs/[id]/index.vue | 8 +- app/pages/catalog/offers/detail/[offerId].vue | 15 ++- app/pages/catalog/products/[id].vue | 46 ++++++-- app/pages/clientarea/addresses/[uuid].vue | 12 +- app/pages/clientarea/addresses/index.vue | 6 +- app/pages/clientarea/addresses/map.vue | 10 +- app/pages/clientarea/addresses/new.vue | 10 +- app/pages/clientarea/ai/index.vue | 4 +- app/pages/clientarea/billing/index.vue | 14 ++- app/pages/clientarea/company-switch.vue | 4 +- app/pages/clientarea/kyc/index.vue | 29 +++-- app/pages/clientarea/kyc/russia.vue | 39 +++++-- app/pages/clientarea/offers/[uuid].vue | 30 ++--- app/pages/clientarea/offers/index.vue | 18 +-- app/pages/clientarea/offers/new.vue | 20 ++-- app/pages/clientarea/orders/[id].vue | 106 +++++++++++------- app/pages/clientarea/orders/index.vue | 54 ++++++--- app/pages/clientarea/orders/map.vue | 16 +-- app/pages/clientarea/team/index.vue | 51 +++++++-- app/pages/clientarea/team/invite.vue | 4 +- codegen.ts | 7 ++ 42 files changed, 614 insertions(+), 324 deletions(-) diff --git a/app/components/BankSearchRussia.vue b/app/components/BankSearchRussia.vue index e230734..9f2a632 100644 --- a/app/components/BankSearchRussia.vue +++ b/app/components/BankSearchRussia.vue @@ -47,13 +47,22 @@ interface BankData { correspondentAccount: string } +interface BankSuggestion { + value: string + data: { + bic: string + correspondent_account?: string + address?: { value: string } + } +} + interface Props { modelValue?: BankData } interface Emits { (e: 'update:modelValue', value: BankData): void - (e: 'select', bank: any): void + (e: 'select', bank: BankSuggestion): void } const props = withDefaults(defineProps(), { @@ -66,15 +75,6 @@ const props = withDefaults(defineProps(), { const emit = defineEmits() -interface BankSuggestion { - value: string - data: { - bic: string - correspondent_account?: string - address?: { value: string } - } -} - const query = ref('') const suggestions = ref([]) const loading = ref(false) @@ -123,7 +123,7 @@ const onInput = async () => { } } -const selectBank = (bank: any) => { +const selectBank = (bank: BankSuggestion) => { query.value = bank.value showDropdown.value = false diff --git a/app/components/CalcResultContent.vue b/app/components/CalcResultContent.vue index 8295354..121e1ed 100644 --- a/app/components/CalcResultContent.vue +++ b/app/components/CalcResultContent.vue @@ -22,7 +22,7 @@ :key="option.sourceUuid ?? index" :location-name="getOfferData(option.sourceUuid)?.locationName" :product-name="productName" - :price-per-unit="getOfferData(option.sourceUuid)?.pricePerUnit" + :price-per-unit="parseFloat(getOfferData(option.sourceUuid)?.pricePerUnit || '0') || null" :currency="getOfferData(option.sourceUuid)?.currency" :unit="getOfferData(option.sourceUuid)?.unit" :stages="getRouteStages(option)" @@ -81,7 +81,8 @@ interface RoutePathType { totalTimeSeconds?: number | null stages?: (RouteStage | null)[] } -import { GetOfferDocument, GetSupplierProfileByTeamDocument } from '~/composables/graphql/public/exchange-generated' +import { GetOfferDocument, GetSupplierProfileByTeamDocument, type GetOfferQueryResult, type GetSupplierProfileByTeamQueryResult } from '~/composables/graphql/public/exchange-generated' +import type { OfferWithRouteType, RouteStageType } from '~/composables/graphql/public/geo-generated' const route = useRoute() const localePath = useLocalePath() @@ -90,12 +91,14 @@ const { execute } = useGraphQL() const productName = computed(() => searchStore.searchForm.product || (route.query.product as string) || 'Товар') const locationName = computed(() => searchStore.searchForm.location || (route.query.location as string) || 'Назначение') -const quantity = computed(() => (route.query.quantity as string) || (searchStore.searchForm as any)?.quantity) +const quantity = computed(() => (route.query.quantity as string) || searchStore.searchForm.quantity) // Offer data for prices -const offersData = ref>(new Map()) +type OfferData = NonNullable +const offersData = ref>(new Map()) // Supplier data for KYC profile UUID (by team_uuid) -const suppliersData = ref>(new Map()) +type SupplierData = NonNullable +const suppliersData = ref>(new Map()) const summaryTitle = computed(() => `${productName.value} → ${locationName.value}`) const summaryMeta = computed(() => { @@ -149,14 +152,16 @@ const fetchOffersByHub = async () => { const offers = offersResponse?.nearestOffers || [] // Offers already include routes from backend - const offersWithRoutes = offers.map((offer: any) => ({ - sourceUuid: offer.uuid, - sourceName: offer.productName, - sourceLat: offer.latitude, - sourceLon: offer.longitude, - distanceKm: offer.distanceKm, - routes: offer.routes || [] - })) + const offersWithRoutes = offers + .filter((offer): offer is NonNullable => offer !== null) + .map((offer) => ({ + sourceUuid: offer.uuid, + sourceName: offer.productName, + sourceLat: offer.latitude, + sourceLon: offer.longitude, + distanceKm: offer.distanceKm, + routes: offer.routes || [] + })) return { offersByHub: offersWithRoutes } } @@ -198,10 +203,12 @@ const mapRouteStages = (route: RoutePathType): RouteStageItem[] => { const getRouteStages = (option: ProductRouteOption) => { const route = option.routes?.[0] if (!route?.stages) return [] - return route.stages.filter(Boolean).map((stage: any) => ({ - transportType: stage?.transportType, - distanceKm: stage?.distanceKm - })) + return route.stages + .filter((stage): stage is NonNullable => stage !== null) + .map((stage) => ({ + transportType: stage.transportType, + distanceKm: stage.distanceKm + })) } // Get offer data for card @@ -233,8 +240,8 @@ const loadOfferDetails = async (options: ProductRouteOption[]) => { return } - const newOffersData = new Map() - const newSuppliersData = new Map() + const newOffersData = new Map() + const newSuppliersData = new Map() const teamUuidsToLoad = new Set() // First, load all offers diff --git a/app/components/GoodsContent.vue b/app/components/GoodsContent.vue index eef9a8d..78f95a9 100644 --- a/app/components/GoodsContent.vue +++ b/app/components/GoodsContent.vue @@ -35,14 +35,16 @@