refactor: remove all any types, add strict GraphQL scalar typing
All checks were successful
Build Docker Image / build (push) Successful in 4m3s

- Add strictScalars: true to codegen.ts with proper scalar mappings
  (Date, Decimal, JSONString, JSON, UUID, BigInt → string/Record)
- Replace all ref<any[]> 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.
This commit is contained in:
Ruslan Bakiev
2026-01-27 11:34:12 +07:00
parent ff34c564e1
commit 2dbe600d8a
42 changed files with 614 additions and 324 deletions

View File

@@ -13,9 +13,9 @@ export type Scalars = {
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
Date: { input: any; output: any; }
Date: { input: string; output: string; }
DateTime: { input: string; output: string; }
Decimal: { input: any; output: any; }
Decimal: { input: string; output: string; }
};
export type OfferType = {
@@ -181,14 +181,14 @@ export type GetLocationOffersQueryVariables = Exact<{
}>;
export type GetLocationOffersQueryResult = { __typename?: 'PublicQuery', getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: any, unit: string, pricePerUnit?: any | null, currency: string, description: string, validUntil?: any | null, createdAt: string, updatedAt: string } | null> | null };
export type GetLocationOffersQueryResult = { __typename?: 'PublicQuery', getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: string, unit: string, pricePerUnit?: string | null, currency: string, description: string, validUntil?: string | null, createdAt: string, updatedAt: string } | null> | null };
export type GetOfferQueryVariables = Exact<{
uuid: Scalars['String']['input'];
}>;
export type GetOfferQueryResult = { __typename?: 'PublicQuery', getOffer?: { __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: any, unit: string, pricePerUnit?: any | null, currency: string, description: string, validUntil?: any | null, createdAt: string, updatedAt: string } | null };
export type GetOfferQueryResult = { __typename?: 'PublicQuery', getOffer?: { __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: string, unit: string, pricePerUnit?: string | null, currency: string, description: string, validUntil?: string | null, createdAt: string, updatedAt: string } | null };
export type GetOffersQueryVariables = Exact<{
productUuid?: InputMaybe<Scalars['String']['input']>;
@@ -200,7 +200,7 @@ export type GetOffersQueryVariables = Exact<{
}>;
export type GetOffersQueryResult = { __typename?: 'PublicQuery', getOffersCount?: number | null, getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: any, unit: string, pricePerUnit?: any | null, currency: string, description: string, validUntil?: any | null, createdAt: string, updatedAt: string } | null> | null };
export type GetOffersQueryResult = { __typename?: 'PublicQuery', getOffersCount?: number | null, getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: string, unit: string, pricePerUnit?: string | null, currency: string, description: string, validUntil?: string | null, createdAt: string, updatedAt: string } | null> | null };
export type GetProductQueryVariables = Exact<{
uuid: Scalars['String']['input'];
@@ -214,7 +214,7 @@ export type GetProductOffersQueryVariables = Exact<{
}>;
export type GetProductOffersQueryResult = { __typename?: 'PublicQuery', getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: any, unit: string, pricePerUnit?: any | null, currency: string, description: string, validUntil?: any | null, createdAt: string, updatedAt: string } | null> | null };
export type GetProductOffersQueryResult = { __typename?: 'PublicQuery', getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: string, unit: string, pricePerUnit?: string | null, currency: string, description: string, validUntil?: string | null, createdAt: string, updatedAt: string } | null> | null };
export type GetProductsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -226,7 +226,7 @@ export type GetSupplierOffersQueryVariables = Exact<{
}>;
export type GetSupplierOffersQueryResult = { __typename?: 'PublicQuery', getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: any, unit: string, pricePerUnit?: any | null, currency: string, description: string, validUntil?: any | null, createdAt: string, updatedAt: string } | null> | null };
export type GetSupplierOffersQueryResult = { __typename?: 'PublicQuery', getOffers?: Array<{ __typename?: 'OfferType', uuid: string, teamUuid: string, status: OffersOfferStatusChoices, locationUuid: string, locationName: string, locationCountry: string, locationCountryCode: string, locationLatitude?: number | null, locationLongitude?: number | null, productUuid: string, productName: string, categoryName: string, quantity: string, unit: string, pricePerUnit?: string | null, currency: string, description: string, validUntil?: string | null, createdAt: string, updatedAt: string } | null> | null };
export type GetSupplierProfileQueryVariables = Exact<{
uuid: Scalars['String']['input'];

View File

@@ -13,7 +13,7 @@ export type Scalars = {
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
JSONString: { input: any; output: any; }
JSONString: { input: Record<string, unknown>; output: Record<string, unknown>; }
};
/** Cluster or individual point for map display. */
@@ -448,7 +448,7 @@ export type GetAutoRouteQueryVariables = Exact<{
}>;
export type GetAutoRouteQueryResult = { __typename?: 'Query', autoRoute?: { __typename?: 'RouteType', distanceKm?: number | null, geometry?: any | null } | null };
export type GetAutoRouteQueryResult = { __typename?: 'Query', autoRoute?: { __typename?: 'RouteType', distanceKm?: number | null, geometry?: Record<string, unknown> | null } | null };
export type GetClusteredNodesQueryVariables = Exact<{
west: Scalars['Float']['input'];
@@ -483,7 +483,7 @@ export type GetRailRouteQueryVariables = Exact<{
}>;
export type GetRailRouteQueryResult = { __typename?: 'Query', railRoute?: { __typename?: 'RouteType', distanceKm?: number | null, geometry?: any | null } | null };
export type GetRailRouteQueryResult = { __typename?: 'Query', railRoute?: { __typename?: 'RouteType', distanceKm?: number | null, geometry?: Record<string, unknown> | null } | null };
export type HubsListQueryVariables = Exact<{
limit?: InputMaybe<Scalars['Int']['input']>;

View File

@@ -13,10 +13,10 @@ export type Scalars = {
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
Date: { input: any; output: any; }
Date: { input: string; output: string; }
DateTime: { input: string; output: string; }
Decimal: { input: any; output: any; }
JSONString: { input: any; output: any; }
Decimal: { input: string; output: string; }
JSONString: { input: Record<string, unknown>; output: Record<string, unknown>; }
};
export type CreateOffer = {
@@ -205,14 +205,14 @@ export type CreateRequestMutationVariables = Exact<{
}>;
export type CreateRequestMutationResult = { __typename?: 'TeamMutation', createRequest?: { __typename?: 'CreateRequest', request?: { __typename?: 'RequestType', uuid: string, productUuid: string, quantity: any, sourceLocationUuid: string, userId: string } | null } | null };
export type CreateRequestMutationResult = { __typename?: 'TeamMutation', createRequest?: { __typename?: 'CreateRequest', request?: { __typename?: 'RequestType', uuid: string, productUuid: string, quantity: string, sourceLocationUuid: string, userId: string } | null } | null };
export type GetRequestsQueryVariables = Exact<{
userId: Scalars['String']['input'];
}>;
export type GetRequestsQueryResult = { __typename?: 'TeamQuery', getRequests?: Array<{ __typename?: 'RequestType', uuid: string, productUuid: string, quantity: any, sourceLocationUuid: string, userId: string } | null> | null };
export type GetRequestsQueryResult = { __typename?: 'TeamQuery', getRequests?: Array<{ __typename?: 'RequestType', uuid: string, productUuid: string, quantity: string, sourceLocationUuid: string, userId: string } | null> | null };
export const CreateOfferDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOffer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"OfferInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOffer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"workflowId"}},{"kind":"Field","name":{"kind":"Name","value":"offerUuid"}}]}}]}}]} as unknown as DocumentNode<CreateOfferMutationResult, CreateOfferMutationVariables>;

View File

@@ -14,7 +14,7 @@ export type Scalars = {
Int: { input: number; output: number; }
Float: { input: number; output: number; }
DateTime: { input: string; output: string; }
JSONString: { input: any; output: any; }
JSONString: { input: Record<string, unknown>; output: Record<string, unknown>; }
};
/** Create KYC Application for Russian company. */
@@ -110,19 +110,19 @@ export type CreateKycApplicationRussiaMutationVariables = Exact<{
}>;
export type CreateKycApplicationRussiaMutationResult = { __typename?: 'UserMutation', createKycApplicationRussia?: { __typename?: 'CreateKYCApplicationRussia', success?: boolean | null, kycApplication?: { __typename?: 'KYCApplicationType', uuid: string, contactEmail: string, createdAt: string, countryData?: any | null } | null } | null };
export type CreateKycApplicationRussiaMutationResult = { __typename?: 'UserMutation', createKycApplicationRussia?: { __typename?: 'CreateKYCApplicationRussia', success?: boolean | null, kycApplication?: { __typename?: 'KYCApplicationType', uuid: string, contactEmail: string, createdAt: string, countryData?: Record<string, unknown> | null } | null } | null };
export type GetKycRequestRussiaQueryVariables = Exact<{
uuid: Scalars['String']['input'];
}>;
export type GetKycRequestRussiaQueryResult = { __typename?: 'UserQuery', kycRequest?: { __typename?: 'KYCApplicationType', uuid: string, userId: string, teamName: string, countryCode: string, contactPerson: string, contactEmail: string, contactPhone: string, approvedBy?: string | null, approvedAt?: string | null, createdAt: string, updatedAt: string, countryData?: any | null } | null };
export type GetKycRequestRussiaQueryResult = { __typename?: 'UserQuery', kycRequest?: { __typename?: 'KYCApplicationType', uuid: string, userId: string, teamName: string, countryCode: string, contactPerson: string, contactEmail: string, contactPhone: string, approvedBy?: string | null, approvedAt?: string | null, createdAt: string, updatedAt: string, countryData?: Record<string, unknown> | null } | null };
export type GetKycRequestsRussiaQueryVariables = Exact<{ [key: string]: never; }>;
export type GetKycRequestsRussiaQueryResult = { __typename?: 'UserQuery', kycRequests?: Array<{ __typename?: 'KYCApplicationType', uuid: string, userId: string, teamName: string, countryCode: string, contactPerson: string, contactEmail: string, contactPhone: string, approvedBy?: string | null, approvedAt?: string | null, createdAt: string, updatedAt: string, countryData?: any | null } | null> | null };
export type GetKycRequestsRussiaQueryResult = { __typename?: 'UserQuery', kycRequests?: Array<{ __typename?: 'KYCApplicationType', uuid: string, userId: string, teamName: string, countryCode: string, contactPerson: string, contactEmail: string, contactPhone: string, approvedBy?: string | null, approvedAt?: string | null, createdAt: string, updatedAt: string, countryData?: Record<string, unknown> | null } | null> | null };
export const CreateKycApplicationRussiaDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateKYCApplicationRussia"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"KYCApplicationRussiaInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createKycApplicationRussia"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"kycApplication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"contactEmail"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"countryData"}}]}}]}}]}}]} as unknown as DocumentNode<CreateKycApplicationRussiaMutationResult, CreateKycApplicationRussiaMutationVariables>;

View File

@@ -45,7 +45,7 @@ export function useCatalogHubs() {
)
const itemsByCountry = computed(() => {
const grouped = new Map<string, any[]>()
const grouped = new Map<string, Array<HubItem | NearestHubItem>>()
items.value.forEach(hub => {
const country = hub.country || t('catalogMap.labels.country_unknown')
if (!grouped.has(country)) grouped.set(country, [])

View File

@@ -1,4 +1,4 @@
import type { ProductsListQueryResult } from '~/composables/graphql/public/geo-generated'
import type { ProductsListQueryResult, NearestOffersQueryResult } from '~/composables/graphql/public/geo-generated'
import {
ProductsListDocument,
GetNodeDocument,
@@ -10,6 +10,14 @@ import {
// Type from codegen
type ProductItem = NonNullable<NonNullable<ProductsListQueryResult['productsList']>[number]>
type OfferItem = NonNullable<NonNullable<NearestOffersQueryResult['nearestOffers']>[number]>
// Product aggregated from offers
interface AggregatedProduct {
uuid: string
name: string | null | undefined
offersCount: number
}
// Shared state
const items = ref<ProductItem[]>([])
@@ -61,20 +69,19 @@ export function useCatalogProducts() {
)
// Group offers by product
const productsMap = new Map<string, any>()
offersData?.nearestOffers?.forEach((offer: any) => {
if (offer?.productUuid) {
if (!productsMap.has(offer.productUuid)) {
productsMap.set(offer.productUuid, {
uuid: offer.productUuid,
name: offer.productName,
offersCount: 0
})
}
productsMap.get(offer.productUuid)!.offersCount++
const productsMap = new Map<string, AggregatedProduct>()
offersData?.nearestOffers?.forEach((offer) => {
if (!offer?.productUuid) return
if (!productsMap.has(offer.productUuid)) {
productsMap.set(offer.productUuid, {
uuid: offer.productUuid,
name: offer.productName,
offersCount: 0
})
}
productsMap.get(offer.productUuid)!.offersCount++
})
items.value = Array.from(productsMap.values())
items.value = Array.from(productsMap.values()) as ProductItem[]
}
} else if (filterHubUuid.value) {
// Products near hub - get hub coordinates first
@@ -103,20 +110,19 @@ export function useCatalogProducts() {
)
// Group offers by product
const productsMap = new Map<string, any>()
offersData?.nearestOffers?.forEach((offer: any) => {
if (offer?.productUuid) {
if (!productsMap.has(offer.productUuid)) {
productsMap.set(offer.productUuid, {
uuid: offer.productUuid,
name: offer.productName,
offersCount: 0
})
}
productsMap.get(offer.productUuid)!.offersCount++
const productsMap = new Map<string, AggregatedProduct>()
offersData?.nearestOffers?.forEach((offer) => {
if (!offer?.productUuid) return
if (!productsMap.has(offer.productUuid)) {
productsMap.set(offer.productUuid, {
uuid: offer.productUuid,
name: offer.productName,
offersCount: 0
})
}
productsMap.get(offer.productUuid)!.offersCount++
})
items.value = Array.from(productsMap.values())
items.value = Array.from(productsMap.values()) as ProductItem[]
}
} else {
// All products from graph

View File

@@ -1,4 +1,8 @@
const items = ref<any[]>([])
import type { GetTeamAddressesQueryResult } from '~/composables/graphql/team/teams-generated'
type TeamAddress = NonNullable<NonNullable<GetTeamAddressesQueryResult['teamAddresses']>[number]>
const items = ref<TeamAddress[]>([])
const isLoading = ref(false)
const isInitialized = ref(false)
@@ -23,7 +27,7 @@ export function useTeamAddresses() {
try {
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
const data = await execute(GetTeamAddressesDocument, {}, 'team', 'teams')
items.value = data?.teamAddresses || []
items.value = (data?.teamAddresses || []).filter((a): a is TeamAddress => a !== null)
isInitialized.value = true
} catch (e) {
console.error('Failed to load addresses', e)

View File

@@ -1,4 +1,9 @@
const items = ref<any[]>([])
import type { GetTeamOrdersQueryResult } from '~/composables/graphql/team/orders-generated'
type TeamOrder = NonNullable<NonNullable<GetTeamOrdersQueryResult['getTeamOrders']>[number]>
type TeamOrderStage = NonNullable<NonNullable<TeamOrder['stages']>[number]>
const items = ref<TeamOrder[]>([])
const isLoading = ref(false)
const isInitialized = ref(false)
@@ -23,13 +28,14 @@ export function useTeamOrders() {
const routesForMap = computed(() =>
filteredItems.value
.filter(order => order.uuid && order.name)
.map(order => ({
uuid: order.uuid,
name: order.name,
status: order.status,
uuid: order.uuid!,
name: order.name!,
status: order.status ?? undefined,
stages: (order.stages || [])
.filter((s: any) => s.stageType === 'transport' && s.sourceLatitude && s.sourceLongitude && s.destinationLatitude && s.destinationLongitude)
.map((s: any) => ({
.filter((s): s is TeamOrderStage => s !== null && s.stageType === 'transport' && !!s.sourceLatitude && !!s.sourceLongitude && !!s.destinationLatitude && !!s.destinationLongitude)
.map((s) => ({
fromLat: s.sourceLatitude,
fromLon: s.sourceLongitude,
toLat: s.destinationLatitude,
@@ -47,7 +53,7 @@ export function useTeamOrders() {
try {
const { GetTeamOrdersDocument } = await import('~/composables/graphql/team/orders-generated')
const data = await execute(GetTeamOrdersDocument, {}, 'team', 'orders')
items.value = data?.getTeamOrders || []
items.value = (data?.getTeamOrders || []).filter((o): o is TeamOrder => o !== null)
isInitialized.value = true
} catch (e) {
console.error('Failed to load orders', e)