refactor: remove all any types, add strict GraphQL scalar typing
All checks were successful
Build Docker Image / build (push) Successful in 4m3s
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:
@@ -47,13 +47,22 @@ interface BankData {
|
|||||||
correspondentAccount: string
|
correspondentAccount: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BankSuggestion {
|
||||||
|
value: string
|
||||||
|
data: {
|
||||||
|
bic: string
|
||||||
|
correspondent_account?: string
|
||||||
|
address?: { value: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue?: BankData
|
modelValue?: BankData
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'update:modelValue', value: BankData): void
|
(e: 'update:modelValue', value: BankData): void
|
||||||
(e: 'select', bank: any): void
|
(e: 'select', bank: BankSuggestion): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -66,15 +75,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
interface BankSuggestion {
|
|
||||||
value: string
|
|
||||||
data: {
|
|
||||||
bic: string
|
|
||||||
correspondent_account?: string
|
|
||||||
address?: { value: string }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = ref('')
|
const query = ref('')
|
||||||
const suggestions = ref<BankSuggestion[]>([])
|
const suggestions = ref<BankSuggestion[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -123,7 +123,7 @@ const onInput = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectBank = (bank: any) => {
|
const selectBank = (bank: BankSuggestion) => {
|
||||||
query.value = bank.value
|
query.value = bank.value
|
||||||
showDropdown.value = false
|
showDropdown.value = false
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
:key="option.sourceUuid ?? index"
|
:key="option.sourceUuid ?? index"
|
||||||
:location-name="getOfferData(option.sourceUuid)?.locationName"
|
:location-name="getOfferData(option.sourceUuid)?.locationName"
|
||||||
:product-name="productName"
|
: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"
|
:currency="getOfferData(option.sourceUuid)?.currency"
|
||||||
:unit="getOfferData(option.sourceUuid)?.unit"
|
:unit="getOfferData(option.sourceUuid)?.unit"
|
||||||
:stages="getRouteStages(option)"
|
:stages="getRouteStages(option)"
|
||||||
@@ -81,7 +81,8 @@ interface RoutePathType {
|
|||||||
totalTimeSeconds?: number | null
|
totalTimeSeconds?: number | null
|
||||||
stages?: (RouteStage | 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 route = useRoute()
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
@@ -90,12 +91,14 @@ const { execute } = useGraphQL()
|
|||||||
|
|
||||||
const productName = computed(() => searchStore.searchForm.product || (route.query.product as string) || 'Товар')
|
const productName = computed(() => searchStore.searchForm.product || (route.query.product as string) || 'Товар')
|
||||||
const locationName = computed(() => searchStore.searchForm.location || (route.query.location 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
|
// Offer data for prices
|
||||||
const offersData = ref<Map<string, any>>(new Map())
|
type OfferData = NonNullable<GetOfferQueryResult['getOffer']>
|
||||||
|
const offersData = ref<Map<string, OfferData>>(new Map())
|
||||||
// Supplier data for KYC profile UUID (by team_uuid)
|
// Supplier data for KYC profile UUID (by team_uuid)
|
||||||
const suppliersData = ref<Map<string, any>>(new Map())
|
type SupplierData = NonNullable<GetSupplierProfileByTeamQueryResult['getSupplierProfileByTeam']>
|
||||||
|
const suppliersData = ref<Map<string, SupplierData>>(new Map())
|
||||||
|
|
||||||
const summaryTitle = computed(() => `${productName.value} → ${locationName.value}`)
|
const summaryTitle = computed(() => `${productName.value} → ${locationName.value}`)
|
||||||
const summaryMeta = computed(() => {
|
const summaryMeta = computed(() => {
|
||||||
@@ -149,14 +152,16 @@ const fetchOffersByHub = async () => {
|
|||||||
const offers = offersResponse?.nearestOffers || []
|
const offers = offersResponse?.nearestOffers || []
|
||||||
|
|
||||||
// Offers already include routes from backend
|
// Offers already include routes from backend
|
||||||
const offersWithRoutes = offers.map((offer: any) => ({
|
const offersWithRoutes = offers
|
||||||
sourceUuid: offer.uuid,
|
.filter((offer): offer is NonNullable<OfferWithRouteType> => offer !== null)
|
||||||
sourceName: offer.productName,
|
.map((offer) => ({
|
||||||
sourceLat: offer.latitude,
|
sourceUuid: offer.uuid,
|
||||||
sourceLon: offer.longitude,
|
sourceName: offer.productName,
|
||||||
distanceKm: offer.distanceKm,
|
sourceLat: offer.latitude,
|
||||||
routes: offer.routes || []
|
sourceLon: offer.longitude,
|
||||||
}))
|
distanceKm: offer.distanceKm,
|
||||||
|
routes: offer.routes || []
|
||||||
|
}))
|
||||||
|
|
||||||
return { offersByHub: offersWithRoutes }
|
return { offersByHub: offersWithRoutes }
|
||||||
}
|
}
|
||||||
@@ -198,10 +203,12 @@ const mapRouteStages = (route: RoutePathType): RouteStageItem[] => {
|
|||||||
const getRouteStages = (option: ProductRouteOption) => {
|
const getRouteStages = (option: ProductRouteOption) => {
|
||||||
const route = option.routes?.[0]
|
const route = option.routes?.[0]
|
||||||
if (!route?.stages) return []
|
if (!route?.stages) return []
|
||||||
return route.stages.filter(Boolean).map((stage: any) => ({
|
return route.stages
|
||||||
transportType: stage?.transportType,
|
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
|
||||||
distanceKm: stage?.distanceKm
|
.map((stage) => ({
|
||||||
}))
|
transportType: stage.transportType,
|
||||||
|
distanceKm: stage.distanceKm
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get offer data for card
|
// Get offer data for card
|
||||||
@@ -233,8 +240,8 @@ const loadOfferDetails = async (options: ProductRouteOption[]) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const newOffersData = new Map<string, any>()
|
const newOffersData = new Map<string, OfferData>()
|
||||||
const newSuppliersData = new Map<string, any>()
|
const newSuppliersData = new Map<string, SupplierData>()
|
||||||
const teamUuidsToLoad = new Set<string>()
|
const teamUuidsToLoad = new Set<string>()
|
||||||
|
|
||||||
// First, load all offers
|
// First, load all offers
|
||||||
|
|||||||
@@ -35,14 +35,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetProductsDocument } from '~/composables/graphql/public/exchange-generated'
|
import { GetProductsDocument, type GetProductsQueryResult } from '~/composables/graphql/public/exchange-generated'
|
||||||
|
|
||||||
|
type Product = NonNullable<NonNullable<GetProductsQueryResult['getProducts']>[number]>
|
||||||
|
|
||||||
const searchStore = useSearchStore()
|
const searchStore = useSearchStore()
|
||||||
|
|
||||||
const { data, pending, error, refresh } = await useServerQuery('products', GetProductsDocument, {}, 'public', 'exchange')
|
const { data, pending, error, refresh } = await useServerQuery('products', GetProductsDocument, {}, 'public', 'exchange')
|
||||||
const productsData = computed(() => data.value?.getProducts || [])
|
const productsData = computed(() => data.value?.getProducts || [])
|
||||||
|
|
||||||
const selectProduct = (product: any) => {
|
const selectProduct = (product: Product) => {
|
||||||
searchStore.setProduct(product.name)
|
searchStore.setProduct(product.name)
|
||||||
searchStore.setProductUuid(product.uuid)
|
searchStore.setProductUuid(product.uuid)
|
||||||
const locationUuid = searchStore.searchForm.locationUuid
|
const locationUuid = searchStore.searchForm.locationUuid
|
||||||
|
|||||||
@@ -154,10 +154,44 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
interface KycSubmitData {
|
||||||
|
company_name: string
|
||||||
|
company_full_name: string
|
||||||
|
inn: string
|
||||||
|
kpp: string
|
||||||
|
ogrn: string
|
||||||
|
address: string
|
||||||
|
bank_name: string
|
||||||
|
bik: string
|
||||||
|
correspondent_account: string
|
||||||
|
contact_person: string
|
||||||
|
contact_email: string
|
||||||
|
contact_phone: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompanySuggestion {
|
||||||
|
value: string
|
||||||
|
unrestricted_value: string
|
||||||
|
data: {
|
||||||
|
inn: string
|
||||||
|
kpp?: string
|
||||||
|
ogrn?: string
|
||||||
|
address?: { value: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BankSuggestion {
|
||||||
|
value: string
|
||||||
|
data: {
|
||||||
|
bic: string
|
||||||
|
correspondent_account?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [data: any]
|
submit: [data: KycSubmitData]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -195,7 +229,7 @@ const isFormValid = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const onCompanySelect = (company: any) => {
|
const onCompanySelect = (company: CompanySuggestion) => {
|
||||||
formData.value.company = {
|
formData.value.company = {
|
||||||
companyName: company.value,
|
companyName: company.value,
|
||||||
companyFullName: company.unrestricted_value,
|
companyFullName: company.unrestricted_value,
|
||||||
@@ -206,7 +240,7 @@ const onCompanySelect = (company: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBankSelect = (bank: any) => {
|
const onBankSelect = (bank: BankSuggestion) => {
|
||||||
formData.value.bank = {
|
formData.value.bank = {
|
||||||
bankName: bank.value,
|
bankName: bank.value,
|
||||||
bik: bank.data.bic,
|
bik: bank.data.bic,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
<Grid :cols="1" :md="2" :lg="3" :gap="4">
|
<Grid :cols="1" :md="2" :lg="3" :gap="4">
|
||||||
<Card
|
<Card
|
||||||
v-for="addr in teamAddresses"
|
v-for="(addr, index) in teamAddresses"
|
||||||
:key="addr.uuid"
|
:key="addr.uuid ?? index"
|
||||||
padding="small"
|
padding="small"
|
||||||
interactive
|
interactive
|
||||||
@click="selectTeamAddress(addr)"
|
@click="selectTeamAddress(addr)"
|
||||||
@@ -57,8 +57,8 @@
|
|||||||
|
|
||||||
<Grid v-else :cols="1" :md="2" :lg="3" :gap="4">
|
<Grid v-else :cols="1" :md="2" :lg="3" :gap="4">
|
||||||
<HubCard
|
<HubCard
|
||||||
v-for="location in locationsData"
|
v-for="(location, index) in locationsData"
|
||||||
:key="location.uuid"
|
:key="location.uuid ?? index"
|
||||||
:hub="location"
|
:hub="location"
|
||||||
selectable
|
selectable
|
||||||
@select="selectLocation(location)"
|
@select="selectLocation(location)"
|
||||||
@@ -69,7 +69,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { HubsListDocument } from '~/composables/graphql/public/geo-generated'
|
import { HubsListDocument, type HubsListQueryResult } from '~/composables/graphql/public/geo-generated'
|
||||||
|
|
||||||
|
type HubItem = NonNullable<NonNullable<HubsListQueryResult['hubsList']>[number]>
|
||||||
|
type HubWithDistance = HubItem & { distance?: string }
|
||||||
|
|
||||||
|
interface TeamAddress {
|
||||||
|
uuid?: string | null
|
||||||
|
name?: string | null
|
||||||
|
address?: string | null
|
||||||
|
isDefault?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const searchStore = useSearchStore()
|
const searchStore = useSearchStore()
|
||||||
@@ -85,35 +95,37 @@ const calculateDistance = (lat: number, lng: number) => {
|
|||||||
|
|
||||||
// Load logistics hubs
|
// Load logistics hubs
|
||||||
const { data: locationsDataRaw, pending, error, refresh } = await useServerQuery('locations', HubsListDocument, { limit: 100 }, 'public', 'geo')
|
const { data: locationsDataRaw, pending, error, refresh } = await useServerQuery('locations', HubsListDocument, { limit: 100 }, 'public', 'geo')
|
||||||
const locationsData = computed(() => {
|
const locationsData = computed<HubWithDistance[]>(() => {
|
||||||
return (locationsDataRaw.value?.hubsList || []).map((location: any) => ({
|
return (locationsDataRaw.value?.hubsList || [])
|
||||||
...location,
|
.filter((location): location is HubItem => location !== null)
|
||||||
distance: location?.latitude && location?.longitude
|
.map((location) => ({
|
||||||
? calculateDistance(location.latitude, location.longitude)
|
...location,
|
||||||
: undefined,
|
distance: location.latitude && location.longitude
|
||||||
}))
|
? calculateDistance(location.latitude, location.longitude)
|
||||||
|
: undefined,
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Load team addresses (if authenticated)
|
// Load team addresses (if authenticated)
|
||||||
const teamAddresses = ref<any[]>([])
|
const teamAddresses = ref<TeamAddress[]>([])
|
||||||
|
|
||||||
if (isAuthenticated.value) {
|
if (isAuthenticated.value) {
|
||||||
try {
|
try {
|
||||||
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
|
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
|
||||||
const { data: addressData } = await useServerQuery('locations-team-addresses', GetTeamAddressesDocument, {}, 'team', 'teams')
|
const { data: addressData } = await useServerQuery('locations-team-addresses', GetTeamAddressesDocument, {}, 'team', 'teams')
|
||||||
teamAddresses.value = addressData.value?.teamAddresses || []
|
teamAddresses.value = (addressData.value?.teamAddresses || []).filter((a): a is NonNullable<typeof a> => a !== null)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Team addresses not available')
|
console.log('Team addresses not available')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectLocation = (location: any) => {
|
const selectLocation = (location: HubWithDistance) => {
|
||||||
searchStore.setLocation(location.name)
|
searchStore.setLocation(location.name)
|
||||||
searchStore.setLocationUuid(location.uuid)
|
searchStore.setLocationUuid(location.uuid)
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectTeamAddress = (addr: any) => {
|
const selectTeamAddress = (addr: TeamAddress) => {
|
||||||
searchStore.setLocation(addr.address)
|
searchStore.setLocation(addr.address)
|
||||||
searchStore.setLocationUuid(addr.uuid)
|
searchStore.setLocationUuid(addr.uuid)
|
||||||
history.back()
|
history.back()
|
||||||
|
|||||||
@@ -52,8 +52,8 @@
|
|||||||
<!-- Hubs Tab -->
|
<!-- Hubs Tab -->
|
||||||
<div v-else-if="activeTab === 'hubs'" class="space-y-2">
|
<div v-else-if="activeTab === 'hubs'" class="space-y-2">
|
||||||
<HubCard
|
<HubCard
|
||||||
v-for="hub in hubs"
|
v-for="(hub, index) in hubs"
|
||||||
:key="hub.uuid"
|
:key="hub.uuid ?? index"
|
||||||
:hub="hub"
|
:hub="hub"
|
||||||
selectable
|
selectable
|
||||||
:is-selected="selectedItemId === hub.uuid"
|
:is-selected="selectedItemId === hub.uuid"
|
||||||
@@ -67,8 +67,8 @@
|
|||||||
<!-- Suppliers Tab -->
|
<!-- Suppliers Tab -->
|
||||||
<div v-else-if="activeTab === 'suppliers'" class="space-y-2">
|
<div v-else-if="activeTab === 'suppliers'" class="space-y-2">
|
||||||
<SupplierCard
|
<SupplierCard
|
||||||
v-for="supplier in suppliers"
|
v-for="(supplier, index) in suppliers"
|
||||||
:key="supplier.uuid"
|
:key="supplier.uuid ?? index"
|
||||||
:supplier="supplier"
|
:supplier="supplier"
|
||||||
selectable
|
selectable
|
||||||
:is-selected="selectedItemId === supplier.uuid"
|
:is-selected="selectedItemId === supplier.uuid"
|
||||||
@@ -82,8 +82,8 @@
|
|||||||
<!-- Offers Tab -->
|
<!-- Offers Tab -->
|
||||||
<div v-else-if="activeTab === 'offers'" class="space-y-2">
|
<div v-else-if="activeTab === 'offers'" class="space-y-2">
|
||||||
<OfferCard
|
<OfferCard
|
||||||
v-for="offer in offers"
|
v-for="(offer, index) in offers"
|
||||||
:key="offer.uuid"
|
:key="offer.uuid ?? index"
|
||||||
:offer="offer"
|
:offer="offer"
|
||||||
selectable
|
selectable
|
||||||
:is-selected="selectedItemId === offer.uuid"
|
:is-selected="selectedItemId === offer.uuid"
|
||||||
@@ -98,18 +98,56 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
interface Hub {
|
||||||
|
uuid?: string | null
|
||||||
|
name?: string | null
|
||||||
|
country?: string | null
|
||||||
|
countryCode?: string | null
|
||||||
|
distance?: string
|
||||||
|
transportTypes?: (string | null)[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Supplier {
|
||||||
|
uuid?: string | null
|
||||||
|
teamUuid?: string | null
|
||||||
|
name?: string | null
|
||||||
|
country?: string | null
|
||||||
|
countryCode?: string | null
|
||||||
|
logo?: string | null
|
||||||
|
onTimeRate?: number | null
|
||||||
|
offersCount?: number | null
|
||||||
|
isVerified?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Offer {
|
||||||
|
uuid?: string | null
|
||||||
|
productUuid?: string | null
|
||||||
|
productName?: string | null
|
||||||
|
categoryName?: string | null
|
||||||
|
locationUuid?: string | null
|
||||||
|
locationName?: string | null
|
||||||
|
locationCountry?: string | null
|
||||||
|
locationCountryCode?: string | null
|
||||||
|
quantity?: number | string | null
|
||||||
|
unit?: string | null
|
||||||
|
pricePerUnit?: number | string | null
|
||||||
|
currency?: string | null
|
||||||
|
status?: string | null
|
||||||
|
validUntil?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
activeTab: 'hubs' | 'suppliers' | 'offers'
|
activeTab: 'hubs' | 'suppliers' | 'offers'
|
||||||
hubs: any[]
|
hubs: Hub[]
|
||||||
suppliers: any[]
|
suppliers: Supplier[]
|
||||||
offers: any[]
|
offers: Offer[]
|
||||||
selectedItemId: string | null
|
selectedItemId: string | null
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
'update:activeTab': [tab: 'hubs' | 'suppliers' | 'offers']
|
'update:activeTab': [tab: 'hubs' | 'suppliers' | 'offers']
|
||||||
'select': [item: any, type: string]
|
'select': [item: Hub | Supplier | Offer, type: 'hub' | 'supplier' | 'offer']
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<MapboxMap
|
<MapboxMap
|
||||||
:key="mapId"
|
:key="mapId"
|
||||||
:map-id="mapId"
|
:map-id="mapId"
|
||||||
:style="`height: ${height}px; width: 100%;`"
|
:style="`height: ${heightValue}px; width: 100%;`"
|
||||||
class="rounded-lg border border-base-300"
|
class="rounded-lg border border-base-300"
|
||||||
:options="mapOptions"
|
:options="mapOptions"
|
||||||
@load="onMapCreated"
|
@load="onMapCreated"
|
||||||
@@ -26,16 +26,46 @@ import type { Map as MapboxMapType } from 'mapbox-gl'
|
|||||||
import { LngLatBounds, Popup } from 'mapbox-gl'
|
import { LngLatBounds, Popup } from 'mapbox-gl'
|
||||||
import { getCurrentInstance } from 'vue'
|
import { getCurrentInstance } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
interface StageCompany {
|
||||||
stages: {
|
uuid?: string | null
|
||||||
type: Array,
|
name?: string | null
|
||||||
default: () => []
|
}
|
||||||
},
|
|
||||||
height: {
|
interface StageTrip {
|
||||||
type: Number,
|
uuid?: string | null
|
||||||
default: 400
|
company?: StageCompany | null
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
interface RouteStage {
|
||||||
|
uuid?: string | null
|
||||||
|
stageType?: string | null
|
||||||
|
sourceLatitude?: number | null
|
||||||
|
sourceLongitude?: number | null
|
||||||
|
sourceLocationName?: string | null
|
||||||
|
destinationLatitude?: number | null
|
||||||
|
destinationLongitude?: number | null
|
||||||
|
destinationLocationName?: string | null
|
||||||
|
locationLatitude?: number | null
|
||||||
|
locationLongitude?: number | null
|
||||||
|
locationName?: string | null
|
||||||
|
selectedCompany?: StageCompany | null
|
||||||
|
trips?: StageTrip[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RoutePoint {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
lat: number
|
||||||
|
lng: number
|
||||||
|
companies: StageCompany[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
stages?: RouteStage[]
|
||||||
|
height?: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const defaultHeight = 400
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const mapRef = ref<MapboxMapType | null>(null)
|
const mapRef = ref<MapboxMapType | null>(null)
|
||||||
@@ -44,10 +74,12 @@ const didFitBounds = ref(false)
|
|||||||
const instanceId = getCurrentInstance()?.uid || Math.floor(Math.random() * 100000)
|
const instanceId = getCurrentInstance()?.uid || Math.floor(Math.random() * 100000)
|
||||||
const mapId = computed(() => `route-map-${instanceId}`)
|
const mapId = computed(() => `route-map-${instanceId}`)
|
||||||
|
|
||||||
const routePoints = computed(() => {
|
const heightValue = computed(() => props.height ?? defaultHeight)
|
||||||
const points: Array<{ id: string; name: string; lat: number; lng: number; companies: any[] }> = []
|
|
||||||
|
|
||||||
props.stages.forEach((stage: any) => {
|
const routePoints = computed(() => {
|
||||||
|
const points: RoutePoint[] = []
|
||||||
|
|
||||||
|
props.stages?.forEach((stage: RouteStage) => {
|
||||||
if (stage.stageType === 'transport') {
|
if (stage.stageType === 'transport') {
|
||||||
if (stage.sourceLatitude && stage.sourceLongitude) {
|
if (stage.sourceLatitude && stage.sourceLongitude) {
|
||||||
const existingPoint = points.find(p => p.lat === stage.sourceLatitude && p.lng === stage.sourceLongitude)
|
const existingPoint = points.find(p => p.lat === stage.sourceLatitude && p.lng === stage.sourceLongitude)
|
||||||
@@ -263,16 +295,16 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const getStageCompanies = (stage: any) => {
|
const getStageCompanies = (stage: RouteStage): StageCompany[] => {
|
||||||
const companies: any[] = []
|
const companies: StageCompany[] = []
|
||||||
|
|
||||||
if (stage.selectedCompany) {
|
if (stage.selectedCompany) {
|
||||||
companies.push(stage.selectedCompany)
|
companies.push(stage.selectedCompany)
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueCompanies = new Set()
|
const uniqueCompanies = new Set<string>()
|
||||||
stage.trips?.forEach((trip: any) => {
|
stage.trips?.forEach((trip: StageTrip) => {
|
||||||
if (trip.company && !uniqueCompanies.has(trip.company.uuid)) {
|
if (trip.company && trip.company.uuid && !uniqueCompanies.has(trip.company.uuid)) {
|
||||||
uniqueCompanies.add(trip.company.uuid)
|
uniqueCompanies.add(trip.company.uuid)
|
||||||
companies.push(trip.company)
|
companies.push(trip.company)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CreateTeamDocument } from '~/composables/graphql/user/teams-generated'
|
import { CreateTeamDocument } from '~/composables/graphql/user/teams-generated'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const emit = defineEmits(['teamCreated', 'cancel'])
|
const emit = defineEmits(['teamCreated', 'cancel'])
|
||||||
|
|
||||||
const teamName = ref('')
|
const teamName = ref('')
|
||||||
@@ -93,9 +94,9 @@ const handleSubmit = async () => {
|
|||||||
emit('teamCreated', result.createTeam?.team)
|
emit('teamCreated', result.createTeam?.team)
|
||||||
teamName.value = ''
|
teamName.value = ''
|
||||||
teamType.value = 'BUYER'
|
teamType.value = 'BUYER'
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
error.value = err?.message || $t('teams.errors.create_failed')
|
error.value = err instanceof Error ? err.message : t('teams.errors.create_failed')
|
||||||
console.error('Error creating team:', err)
|
console.error('Error creating team:', err)
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ interface Offer {
|
|||||||
status?: string | null
|
status?: string | null
|
||||||
latitude?: number | null
|
latitude?: number | null
|
||||||
longitude?: number | null
|
longitude?: number | null
|
||||||
lines?: any[] | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Supplier {
|
interface Supplier {
|
||||||
|
|||||||
@@ -61,11 +61,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
interface SelectedItem {
|
interface SelectedItem {
|
||||||
uuid: string
|
uuid: string
|
||||||
name?: string
|
name?: string | null
|
||||||
country?: string
|
country?: string | null
|
||||||
latitude?: number | null
|
latitude?: number | null
|
||||||
longitude?: number | null
|
longitude?: number | null
|
||||||
[key: string]: any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -35,7 +35,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
interface Offer {
|
interface Offer {
|
||||||
uuid: string
|
uuid: string
|
||||||
[key: string]: any
|
name?: string | null
|
||||||
|
productUuid?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ import type { SelectMode } from '~/composables/useCatalogSearch'
|
|||||||
interface Item {
|
interface Item {
|
||||||
uuid?: string | null
|
uuid?: string | null
|
||||||
name?: string | null
|
name?: string | null
|
||||||
[key: string]: any
|
country?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export type Scalars = {
|
|||||||
Boolean: { input: boolean; output: boolean; }
|
Boolean: { input: boolean; output: boolean; }
|
||||||
Int: { input: number; output: number; }
|
Int: { input: number; output: number; }
|
||||||
Float: { input: number; output: number; }
|
Float: { input: number; output: number; }
|
||||||
Date: { input: any; output: any; }
|
Date: { input: string; output: string; }
|
||||||
DateTime: { input: string; output: string; }
|
DateTime: { input: string; output: string; }
|
||||||
Decimal: { input: any; output: any; }
|
Decimal: { input: string; output: string; }
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OfferType = {
|
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<{
|
export type GetOfferQueryVariables = Exact<{
|
||||||
uuid: Scalars['String']['input'];
|
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<{
|
export type GetOffersQueryVariables = Exact<{
|
||||||
productUuid?: InputMaybe<Scalars['String']['input']>;
|
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<{
|
export type GetProductQueryVariables = Exact<{
|
||||||
uuid: Scalars['String']['input'];
|
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; }>;
|
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<{
|
export type GetSupplierProfileQueryVariables = Exact<{
|
||||||
uuid: Scalars['String']['input'];
|
uuid: Scalars['String']['input'];
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type Scalars = {
|
|||||||
Boolean: { input: boolean; output: boolean; }
|
Boolean: { input: boolean; output: boolean; }
|
||||||
Int: { input: number; output: number; }
|
Int: { input: number; output: number; }
|
||||||
Float: { 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. */
|
/** 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<{
|
export type GetClusteredNodesQueryVariables = Exact<{
|
||||||
west: Scalars['Float']['input'];
|
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<{
|
export type HubsListQueryVariables = Exact<{
|
||||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ export type Scalars = {
|
|||||||
Boolean: { input: boolean; output: boolean; }
|
Boolean: { input: boolean; output: boolean; }
|
||||||
Int: { input: number; output: number; }
|
Int: { input: number; output: number; }
|
||||||
Float: { input: number; output: number; }
|
Float: { input: number; output: number; }
|
||||||
Date: { input: any; output: any; }
|
Date: { input: string; output: string; }
|
||||||
DateTime: { input: string; output: string; }
|
DateTime: { input: string; output: string; }
|
||||||
Decimal: { input: any; output: any; }
|
Decimal: { input: string; output: string; }
|
||||||
JSONString: { input: any; output: any; }
|
JSONString: { input: Record<string, unknown>; output: Record<string, unknown>; }
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateOffer = {
|
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<{
|
export type GetRequestsQueryVariables = Exact<{
|
||||||
userId: Scalars['String']['input'];
|
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>;
|
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>;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export type Scalars = {
|
|||||||
Int: { input: number; output: number; }
|
Int: { input: number; output: number; }
|
||||||
Float: { input: number; output: number; }
|
Float: { input: number; output: number; }
|
||||||
DateTime: { input: string; output: string; }
|
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. */
|
/** 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<{
|
export type GetKycRequestRussiaQueryVariables = Exact<{
|
||||||
uuid: Scalars['String']['input'];
|
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 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>;
|
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>;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function useCatalogHubs() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const itemsByCountry = computed(() => {
|
const itemsByCountry = computed(() => {
|
||||||
const grouped = new Map<string, any[]>()
|
const grouped = new Map<string, Array<HubItem | NearestHubItem>>()
|
||||||
items.value.forEach(hub => {
|
items.value.forEach(hub => {
|
||||||
const country = hub.country || t('catalogMap.labels.country_unknown')
|
const country = hub.country || t('catalogMap.labels.country_unknown')
|
||||||
if (!grouped.has(country)) grouped.set(country, [])
|
if (!grouped.has(country)) grouped.set(country, [])
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ProductsListQueryResult } from '~/composables/graphql/public/geo-generated'
|
import type { ProductsListQueryResult, NearestOffersQueryResult } from '~/composables/graphql/public/geo-generated'
|
||||||
import {
|
import {
|
||||||
ProductsListDocument,
|
ProductsListDocument,
|
||||||
GetNodeDocument,
|
GetNodeDocument,
|
||||||
@@ -10,6 +10,14 @@ import {
|
|||||||
|
|
||||||
// Type from codegen
|
// Type from codegen
|
||||||
type ProductItem = NonNullable<NonNullable<ProductsListQueryResult['productsList']>[number]>
|
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
|
// Shared state
|
||||||
const items = ref<ProductItem[]>([])
|
const items = ref<ProductItem[]>([])
|
||||||
@@ -61,20 +69,19 @@ export function useCatalogProducts() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Group offers by product
|
// Group offers by product
|
||||||
const productsMap = new Map<string, any>()
|
const productsMap = new Map<string, AggregatedProduct>()
|
||||||
offersData?.nearestOffers?.forEach((offer: any) => {
|
offersData?.nearestOffers?.forEach((offer) => {
|
||||||
if (offer?.productUuid) {
|
if (!offer?.productUuid) return
|
||||||
if (!productsMap.has(offer.productUuid)) {
|
if (!productsMap.has(offer.productUuid)) {
|
||||||
productsMap.set(offer.productUuid, {
|
productsMap.set(offer.productUuid, {
|
||||||
uuid: offer.productUuid,
|
uuid: offer.productUuid,
|
||||||
name: offer.productName,
|
name: offer.productName,
|
||||||
offersCount: 0
|
offersCount: 0
|
||||||
})
|
})
|
||||||
}
|
|
||||||
productsMap.get(offer.productUuid)!.offersCount++
|
|
||||||
}
|
}
|
||||||
|
productsMap.get(offer.productUuid)!.offersCount++
|
||||||
})
|
})
|
||||||
items.value = Array.from(productsMap.values())
|
items.value = Array.from(productsMap.values()) as ProductItem[]
|
||||||
}
|
}
|
||||||
} else if (filterHubUuid.value) {
|
} else if (filterHubUuid.value) {
|
||||||
// Products near hub - get hub coordinates first
|
// Products near hub - get hub coordinates first
|
||||||
@@ -103,20 +110,19 @@ export function useCatalogProducts() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Group offers by product
|
// Group offers by product
|
||||||
const productsMap = new Map<string, any>()
|
const productsMap = new Map<string, AggregatedProduct>()
|
||||||
offersData?.nearestOffers?.forEach((offer: any) => {
|
offersData?.nearestOffers?.forEach((offer) => {
|
||||||
if (offer?.productUuid) {
|
if (!offer?.productUuid) return
|
||||||
if (!productsMap.has(offer.productUuid)) {
|
if (!productsMap.has(offer.productUuid)) {
|
||||||
productsMap.set(offer.productUuid, {
|
productsMap.set(offer.productUuid, {
|
||||||
uuid: offer.productUuid,
|
uuid: offer.productUuid,
|
||||||
name: offer.productName,
|
name: offer.productName,
|
||||||
offersCount: 0
|
offersCount: 0
|
||||||
})
|
})
|
||||||
}
|
|
||||||
productsMap.get(offer.productUuid)!.offersCount++
|
|
||||||
}
|
}
|
||||||
|
productsMap.get(offer.productUuid)!.offersCount++
|
||||||
})
|
})
|
||||||
items.value = Array.from(productsMap.values())
|
items.value = Array.from(productsMap.values()) as ProductItem[]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// All products from graph
|
// All products from graph
|
||||||
|
|||||||
@@ -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 isLoading = ref(false)
|
||||||
const isInitialized = ref(false)
|
const isInitialized = ref(false)
|
||||||
|
|
||||||
@@ -23,7 +27,7 @@ export function useTeamAddresses() {
|
|||||||
try {
|
try {
|
||||||
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
|
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
|
||||||
const data = await execute(GetTeamAddressesDocument, {}, 'team', 'teams')
|
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
|
isInitialized.value = true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load addresses', e)
|
console.error('Failed to load addresses', e)
|
||||||
|
|||||||
@@ -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 isLoading = ref(false)
|
||||||
const isInitialized = ref(false)
|
const isInitialized = ref(false)
|
||||||
|
|
||||||
@@ -23,13 +28,14 @@ export function useTeamOrders() {
|
|||||||
|
|
||||||
const routesForMap = computed(() =>
|
const routesForMap = computed(() =>
|
||||||
filteredItems.value
|
filteredItems.value
|
||||||
|
.filter(order => order.uuid && order.name)
|
||||||
.map(order => ({
|
.map(order => ({
|
||||||
uuid: order.uuid,
|
uuid: order.uuid!,
|
||||||
name: order.name,
|
name: order.name!,
|
||||||
status: order.status,
|
status: order.status ?? undefined,
|
||||||
stages: (order.stages || [])
|
stages: (order.stages || [])
|
||||||
.filter((s: any) => s.stageType === 'transport' && s.sourceLatitude && s.sourceLongitude && s.destinationLatitude && s.destinationLongitude)
|
.filter((s): s is TeamOrderStage => s !== null && s.stageType === 'transport' && !!s.sourceLatitude && !!s.sourceLongitude && !!s.destinationLatitude && !!s.destinationLongitude)
|
||||||
.map((s: any) => ({
|
.map((s) => ({
|
||||||
fromLat: s.sourceLatitude,
|
fromLat: s.sourceLatitude,
|
||||||
fromLon: s.sourceLongitude,
|
fromLon: s.sourceLongitude,
|
||||||
toLat: s.destinationLatitude,
|
toLat: s.destinationLatitude,
|
||||||
@@ -47,7 +53,7 @@ export function useTeamOrders() {
|
|||||||
try {
|
try {
|
||||||
const { GetTeamOrdersDocument } = await import('~/composables/graphql/team/orders-generated')
|
const { GetTeamOrdersDocument } = await import('~/composables/graphql/team/orders-generated')
|
||||||
const data = await execute(GetTeamOrdersDocument, {}, 'team', 'orders')
|
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
|
isInitialized.value = true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load orders', e)
|
console.error('Failed to load orders', e)
|
||||||
|
|||||||
@@ -113,12 +113,20 @@ const {
|
|||||||
const theme = useState<'cupcake' | 'night'>('theme', () => 'cupcake')
|
const theme = useState<'cupcake' | 'night'>('theme', () => 'cupcake')
|
||||||
|
|
||||||
// User data state (shared across layouts)
|
// User data state (shared across layouts)
|
||||||
|
interface SelectedLocation {
|
||||||
|
type: string
|
||||||
|
uuid: string
|
||||||
|
name: string
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
}
|
||||||
|
|
||||||
const userData = useState<{
|
const userData = useState<{
|
||||||
id?: string
|
id?: string
|
||||||
firstName?: string
|
firstName?: string
|
||||||
lastName?: string
|
lastName?: string
|
||||||
avatarId?: string
|
avatarId?: string
|
||||||
activeTeam?: { name?: string; teamType?: string; logtoOrgId?: string; selectedLocation?: any }
|
activeTeam?: { name?: string; teamType?: string; logtoOrgId?: string; selectedLocation?: SelectedLocation | null }
|
||||||
activeTeamId?: string
|
activeTeamId?: string
|
||||||
teams?: Array<{ id?: string; name?: string; logtoOrgId?: string }>
|
teams?: Array<{ id?: string; name?: string; logtoOrgId?: string }>
|
||||||
} | null>('me', () => null)
|
} | null>('me', () => null)
|
||||||
|
|||||||
@@ -44,7 +44,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetNodeDocument, NearestOffersDocument } from '~/composables/graphql/public/geo-generated'
|
import { GetNodeDocument, NearestOffersDocument, type OfferWithRouteType, type GetNodeQueryResult } from '~/composables/graphql/public/geo-generated'
|
||||||
|
|
||||||
|
type Hub = NonNullable<GetNodeQueryResult['node']>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav'
|
layout: 'topnav'
|
||||||
@@ -56,7 +58,7 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const hoveredId = ref<string>()
|
const hoveredId = ref<string>()
|
||||||
const hub = ref<any>(null)
|
const hub = ref<Hub | null>(null)
|
||||||
const products = ref<Array<{ uuid: string; name: string }>>([])
|
const products = ref<Array<{ uuid: string; name: string }>>([])
|
||||||
|
|
||||||
const hubId = computed(() => route.params.id as string)
|
const hubId = computed(() => route.params.id as string)
|
||||||
@@ -150,7 +152,7 @@ try {
|
|||||||
|
|
||||||
// Group offers by product
|
// Group offers by product
|
||||||
const productsMap = new Map<string, { uuid: string; name: string }>()
|
const productsMap = new Map<string, { uuid: string; name: string }>()
|
||||||
offersData.value?.nearestOffers?.forEach((offer: any) => {
|
offersData.value?.nearestOffers?.forEach((offer) => {
|
||||||
if (offer?.productUuid) {
|
if (offer?.productUuid) {
|
||||||
if (!productsMap.has(offer.productUuid)) {
|
if (!productsMap.has(offer.productUuid)) {
|
||||||
productsMap.set(offer.productUuid, {
|
productsMap.set(offer.productUuid, {
|
||||||
|
|||||||
@@ -37,19 +37,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Location on map -->
|
<!-- Location on map -->
|
||||||
<div v-if="offer.latitude && offer.longitude" class="h-48 rounded-lg overflow-hidden">
|
<div v-if="offer.locationLatitude && offer.locationLongitude" class="h-48 rounded-lg overflow-hidden">
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<MapboxMap
|
<MapboxMap
|
||||||
map-id="offer-location-map"
|
map-id="offer-location-map"
|
||||||
class="w-full h-full"
|
class="w-full h-full"
|
||||||
:options="{
|
:options="{
|
||||||
style: 'mapbox://styles/mapbox/streets-v12',
|
style: 'mapbox://styles/mapbox/streets-v12',
|
||||||
center: [offer.longitude, offer.latitude],
|
center: [offer.locationLongitude, offer.locationLatitude],
|
||||||
zoom: 8
|
zoom: 8
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<MapboxDefaultMarker
|
<MapboxDefaultMarker
|
||||||
:lnglat="[offer.longitude, offer.latitude]"
|
:lnglat="[offer.locationLongitude, offer.locationLatitude]"
|
||||||
color="#10b981"
|
color="#10b981"
|
||||||
/>
|
/>
|
||||||
</MapboxMap>
|
</MapboxMap>
|
||||||
@@ -101,7 +101,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetOfferDocument, GetSupplierProfileByTeamDocument } from '~/composables/graphql/public/exchange-generated'
|
import { GetOfferDocument, GetSupplierProfileByTeamDocument, type GetOfferQueryResult, type GetSupplierProfileByTeamQueryResult } from '~/composables/graphql/public/exchange-generated'
|
||||||
|
|
||||||
|
type Offer = NonNullable<GetOfferQueryResult['getOffer']>
|
||||||
|
type SupplierProfile = NonNullable<GetSupplierProfileByTeamQueryResult['getSupplierProfileByTeam']>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav'
|
layout: 'topnav'
|
||||||
@@ -114,8 +117,8 @@ const { execute } = useGraphQL()
|
|||||||
const offerId = computed(() => route.params.offerId as string)
|
const offerId = computed(() => route.params.offerId as string)
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const offer = ref<any>(null)
|
const offer = ref<Offer | null>(null)
|
||||||
const supplier = ref<any>(null)
|
const supplier = ref<SupplierProfile | null>(null)
|
||||||
|
|
||||||
// Load offer data
|
// Load offer data
|
||||||
const loadOffer = async () => {
|
const loadOffer = async () => {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<!-- Line with this product -->
|
<!-- Line with this product -->
|
||||||
<template v-for="line in getProductLines(offer)" :key="line?.uuid">
|
<template v-for="(line, lineIndex) in getProductLines(offer)" :key="line?.uuid ?? lineIndex">
|
||||||
<Card padding="sm" class="bg-base-200">
|
<Card padding="sm" class="bg-base-200">
|
||||||
<Stack direction="row" align="center" justify="between">
|
<Stack direction="row" align="center" justify="between">
|
||||||
<Stack gap="0">
|
<Stack gap="0">
|
||||||
@@ -204,8 +204,14 @@ import {
|
|||||||
GetProductsDocument,
|
GetProductsDocument,
|
||||||
GetProductOffersDocument,
|
GetProductOffersDocument,
|
||||||
GetSupplierProfilesDocument,
|
GetSupplierProfilesDocument,
|
||||||
|
type GetProductsQueryResult,
|
||||||
|
type GetProductOffersQueryResult
|
||||||
} from '~/composables/graphql/public/exchange-generated'
|
} from '~/composables/graphql/public/exchange-generated'
|
||||||
|
|
||||||
|
// Types from GraphQL
|
||||||
|
type Product = NonNullable<NonNullable<GetProductsQueryResult['getProducts']>[number]>
|
||||||
|
type ProductOffer = NonNullable<NonNullable<GetProductOffersQueryResult['getOffers']>[number]>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav'
|
layout: 'topnav'
|
||||||
})
|
})
|
||||||
@@ -237,7 +243,7 @@ const allSuppliers = computed(() => suppliersData.value?.getSupplierProfiles ||
|
|||||||
const productId = computed(() => route.params.id as string)
|
const productId = computed(() => route.params.id as string)
|
||||||
|
|
||||||
// Find product by uuid from list
|
// Find product by uuid from list
|
||||||
const findProduct = (products: any[]) => {
|
const findProduct = (products: (Product | null)[]) => {
|
||||||
return products.find(p => p?.uuid === productId.value)
|
return products.find(p => p?.uuid === productId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,11 +301,13 @@ const mapLocations = computed(() => {
|
|||||||
const priceRange = computed(() => {
|
const priceRange = computed(() => {
|
||||||
const prices: number[] = []
|
const prices: number[] = []
|
||||||
offers.value.forEach(offer => {
|
offers.value.forEach(offer => {
|
||||||
(offer as any).lines?.forEach((line: any) => {
|
// Offers for this product already filtered by productUuid
|
||||||
if (line?.productUuid === productId.value && line?.pricePerUnit) {
|
if (offer.pricePerUnit) {
|
||||||
prices.push(Number(line.pricePerUnit))
|
const price = typeof offer.pricePerUnit === 'string' ? parseFloat(offer.pricePerUnit) : Number(offer.pricePerUnit)
|
||||||
|
if (!isNaN(price)) {
|
||||||
|
prices.push(price)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
if (prices.length === 0) return t('common.values.not_available')
|
if (prices.length === 0) return t('common.values.not_available')
|
||||||
const min = Math.min(...prices)
|
const min = Math.min(...prices)
|
||||||
@@ -308,9 +316,24 @@ const priceRange = computed(() => {
|
|||||||
return t('catalogProduct.labels.price_range', { min: min.toLocaleString(), max: max.toLocaleString() })
|
return t('catalogProduct.labels.price_range', { min: min.toLocaleString(), max: max.toLocaleString() })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get lines with this product
|
// Get offer as "line" - offers already have quantity/unit/price directly
|
||||||
const getProductLines = (offer: any) => {
|
interface OfferLine {
|
||||||
return (offer.lines || []).filter((line: any) => line?.productUuid === productId.value)
|
uuid?: string | null
|
||||||
|
quantity?: string | number | null
|
||||||
|
unit?: string | null
|
||||||
|
pricePerUnit?: string | number | null
|
||||||
|
currency?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProductLines = (offer: ProductOffer): OfferLine[] => {
|
||||||
|
// Each offer is a single "line" with quantity, unit, and price
|
||||||
|
return [{
|
||||||
|
uuid: offer.uuid,
|
||||||
|
quantity: offer.quantity,
|
||||||
|
unit: offer.unit,
|
||||||
|
pricePerUnit: offer.pricePerUnit,
|
||||||
|
currency: offer.currency
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCategoryIcon = (categoryName: string | null | undefined) => {
|
const getCategoryIcon = (categoryName: string | null | undefined) => {
|
||||||
@@ -355,9 +378,10 @@ const formatDate = (dateStr: string | null | undefined) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatPrice = (price: any, currency: string | null | undefined) => {
|
const formatPrice = (price: string | number | null | undefined, currency: string | null | undefined) => {
|
||||||
if (!price) return '—'
|
if (!price) return '—'
|
||||||
const num = Number(price)
|
const num = typeof price === 'string' ? parseFloat(price) : Number(price)
|
||||||
|
if (isNaN(num)) return '—'
|
||||||
const curr = currency || 'USD'
|
const curr = currency || 'USD'
|
||||||
try {
|
try {
|
||||||
return new Intl.NumberFormat('ru', {
|
return new Intl.NumberFormat('ru', {
|
||||||
|
|||||||
@@ -94,6 +94,10 @@
|
|||||||
import { NuxtLink } from '#components'
|
import { NuxtLink } from '#components'
|
||||||
import type { MapMouseEvent, Map as MapboxMapType } from 'mapbox-gl'
|
import type { MapMouseEvent, Map as MapboxMapType } from 'mapbox-gl'
|
||||||
|
|
||||||
|
interface MapboxSearchBox {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
middleware: ['auth-oidc']
|
middleware: ['auth-oidc']
|
||||||
@@ -112,7 +116,7 @@ const isSaving = ref(false)
|
|||||||
const isDeleting = ref(false)
|
const isDeleting = ref(false)
|
||||||
const searchBoxContainer = ref<HTMLElement | null>(null)
|
const searchBoxContainer = ref<HTMLElement | null>(null)
|
||||||
const mapInstance = ref<MapboxMapType | null>(null)
|
const mapInstance = ref<MapboxMapType | null>(null)
|
||||||
const searchBoxRef = ref<any>(null)
|
const searchBoxRef = ref<MapboxSearchBox | null>(null)
|
||||||
|
|
||||||
const addressData = ref<{
|
const addressData = ref<{
|
||||||
uuid: string
|
uuid: string
|
||||||
@@ -130,7 +134,7 @@ const loadAddress = async () => {
|
|||||||
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
|
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
|
||||||
const data = await execute(GetTeamAddressesDocument, {}, 'team', 'teams')
|
const data = await execute(GetTeamAddressesDocument, {}, 'team', 'teams')
|
||||||
const addresses = data?.teamAddresses || []
|
const addresses = data?.teamAddresses || []
|
||||||
const found = addresses.find((a: any) => a.uuid === uuid.value)
|
const found = addresses.find((a) => a?.uuid === uuid.value)
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
addressData.value = {
|
addressData.value = {
|
||||||
@@ -167,7 +171,7 @@ const reverseGeocode = async (lat: number, lng: number): Promise<{ address: stri
|
|||||||
if (!feature) return { address: null, countryCode: null }
|
if (!feature) return { address: null, countryCode: null }
|
||||||
|
|
||||||
// Extract country code from context
|
// Extract country code from context
|
||||||
const countryContext = feature.context?.find((c: any) => c.id?.startsWith('country.'))
|
const countryContext = feature.context?.find((c: { id?: string }) => c.id?.startsWith('country.'))
|
||||||
const countryCode = countryContext?.short_code?.toUpperCase() || null
|
const countryCode = countryContext?.short_code?.toUpperCase() || null
|
||||||
|
|
||||||
return { address: feature.place_name, countryCode }
|
return { address: feature.place_name, countryCode }
|
||||||
@@ -215,7 +219,7 @@ onMounted(async () => {
|
|||||||
searchBox.value = addressData.value.address
|
searchBox.value = addressData.value.address
|
||||||
}
|
}
|
||||||
|
|
||||||
searchBox.addEventListener('retrieve', (event: any) => {
|
searchBox.addEventListener('retrieve', (event: CustomEvent) => {
|
||||||
if (!addressData.value) return
|
if (!addressData.value) return
|
||||||
|
|
||||||
const feature = event.detail.features?.[0]
|
const feature = event.detail.features?.[0]
|
||||||
|
|||||||
@@ -118,8 +118,10 @@ const onSearch = () => {
|
|||||||
// TODO: Implement search
|
// TODO: Implement search
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSelectAddress = (item: any) => {
|
const onSelectAddress = (item: { uuid?: string | null }) => {
|
||||||
selectedAddressId.value = item.uuid
|
if (item.uuid) {
|
||||||
|
selectedAddressId.value = item.uuid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await init()
|
await init()
|
||||||
|
|||||||
@@ -62,10 +62,12 @@ await init()
|
|||||||
const mapRef = ref<{ flyTo: (lat: number, lng: number, zoom?: number) => void } | null>(null)
|
const mapRef = ref<{ flyTo: (lat: number, lng: number, zoom?: number) => void } | null>(null)
|
||||||
const selectedItemId = ref<string | null>(null)
|
const selectedItemId = ref<string | null>(null)
|
||||||
|
|
||||||
const selectItem = (item: any) => {
|
const selectItem = (item: { uuid?: string | null; latitude?: number | null; longitude?: number | null }) => {
|
||||||
selectedItemId.value = item.uuid
|
if (item.uuid) {
|
||||||
if (item.latitude && item.longitude) {
|
selectedItemId.value = item.uuid
|
||||||
mapRef.value?.flyTo(item.latitude, item.longitude, 8)
|
if (item.latitude && item.longitude) {
|
||||||
|
mapRef.value?.flyTo(item.latitude, item.longitude, 8)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,10 @@
|
|||||||
import { NuxtLink } from '#components'
|
import { NuxtLink } from '#components'
|
||||||
import type { MapMouseEvent, Map as MapboxMapType } from 'mapbox-gl'
|
import type { MapMouseEvent, Map as MapboxMapType } from 'mapbox-gl'
|
||||||
|
|
||||||
|
interface MapboxSearchBox {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
middleware: ['auth-oidc']
|
middleware: ['auth-oidc']
|
||||||
@@ -84,7 +88,7 @@ const config = useRuntimeConfig()
|
|||||||
const isCreating = ref(false)
|
const isCreating = ref(false)
|
||||||
const searchBoxContainer = ref<HTMLElement | null>(null)
|
const searchBoxContainer = ref<HTMLElement | null>(null)
|
||||||
const mapInstance = ref<MapboxMapType | null>(null)
|
const mapInstance = ref<MapboxMapType | null>(null)
|
||||||
const searchBoxRef = ref<any>(null)
|
const searchBoxRef = ref<MapboxSearchBox | null>(null)
|
||||||
|
|
||||||
const newAddress = reactive({
|
const newAddress = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -110,7 +114,7 @@ const reverseGeocode = async (lat: number, lng: number): Promise<{ address: stri
|
|||||||
if (!feature) return { address: null, countryCode: null }
|
if (!feature) return { address: null, countryCode: null }
|
||||||
|
|
||||||
// Extract country code from context
|
// Extract country code from context
|
||||||
const countryContext = feature.context?.find((c: any) => c.id?.startsWith('country.'))
|
const countryContext = feature.context?.find((c: { id?: string }) => c.id?.startsWith('country.'))
|
||||||
const countryCode = countryContext?.short_code?.toUpperCase() || null
|
const countryCode = countryContext?.short_code?.toUpperCase() || null
|
||||||
|
|
||||||
return { address: feature.place_name, countryCode }
|
return { address: feature.place_name, countryCode }
|
||||||
@@ -151,7 +155,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
searchBox.placeholder = t('profileAddresses.form.address.placeholder')
|
searchBox.placeholder = t('profileAddresses.form.address.placeholder')
|
||||||
|
|
||||||
searchBox.addEventListener('retrieve', (event: any) => {
|
searchBox.addEventListener('retrieve', (event: CustomEvent) => {
|
||||||
const feature = event.detail.features?.[0]
|
const feature = event.detail.features?.[0]
|
||||||
if (feature) {
|
if (feature) {
|
||||||
const [lng, lat] = feature.geometry.coordinates
|
const [lng, lat] = feature.geometry.coordinates
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ const handleSend = async () => {
|
|||||||
const content = last?.content?.[0]?.text || last?.content || t('aiAssistants.view.emptyResponse')
|
const content = last?.content?.[0]?.text || last?.content || t('aiAssistants.view.emptyResponse')
|
||||||
chat.value.push({ role: 'assistant', content })
|
chat.value.push({ role: 'assistant', content })
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
console.error('Agent error', e)
|
console.error('Agent error', e)
|
||||||
error.value = e?.message || t('aiAssistants.view.error')
|
error.value = e instanceof Error ? e.message : t('aiAssistants.view.error')
|
||||||
chat.value.push({ role: 'assistant', content: t('aiAssistants.view.error') })
|
chat.value.push({ role: 'assistant', content: t('aiAssistants.view.error') })
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -95,6 +95,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { GetTeamTransactionsQueryResult } from '~/composables/graphql/team/billing-generated'
|
||||||
|
|
||||||
|
type Transaction = NonNullable<NonNullable<GetTeamTransactionsQueryResult['teamTransactions']>[number]>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
middleware: ['auth-oidc']
|
middleware: ['auth-oidc']
|
||||||
@@ -112,7 +116,7 @@ const balance = ref({
|
|||||||
exists: false
|
exists: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const transactions = ref<any[]>([])
|
const transactions = ref<Transaction[]>([])
|
||||||
|
|
||||||
const formatCurrency = (amount: number) => {
|
const formatCurrency = (amount: number) => {
|
||||||
// Amount is in kopecks, convert to base units
|
// Amount is in kopecks, convert to base units
|
||||||
@@ -130,7 +134,7 @@ const formatAmount = (amount: number) => {
|
|||||||
}).format(amount / 100)
|
}).format(amount / 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatTimestamp = (timestamp: number) => {
|
const formatTimestamp = (timestamp: number | null | undefined) => {
|
||||||
if (!timestamp) return '—'
|
if (!timestamp) return '—'
|
||||||
// TigerBeetle timestamp is in nanoseconds since epoch
|
// TigerBeetle timestamp is in nanoseconds since epoch
|
||||||
const date = new Date(timestamp / 1000000)
|
const date = new Date(timestamp / 1000000)
|
||||||
@@ -157,8 +161,8 @@ const loadBalance = async () => {
|
|||||||
if (data.value?.teamBalance) {
|
if (data.value?.teamBalance) {
|
||||||
balance.value = data.value.teamBalance
|
balance.value = data.value.teamBalance
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
error.value = e.message || t('billing.errors.load_failed')
|
error.value = e instanceof Error ? e.message : t('billing.errors.load_failed')
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -171,7 +175,7 @@ const loadTransactions = async () => {
|
|||||||
|
|
||||||
if (txError.value) throw txError.value
|
if (txError.value) throw txError.value
|
||||||
|
|
||||||
transactions.value = data.value?.teamTransactions || []
|
transactions.value = (data.value?.teamTransactions || []).filter((tx): tx is Transaction => tx !== null)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load transactions', e)
|
console.error('Failed to load transactions', e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ const switchToTeam = async (teamId: string) => {
|
|||||||
markActiveTeam(newActiveId)
|
markActiveTeam(newActiveId)
|
||||||
navigateTo(localePath('/clientarea/team'))
|
navigateTo(localePath('/clientarea/team'))
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
error.value = err.message || t('clientTeamSwitch.error.switch')
|
error.value = err instanceof Error ? err.message : t('clientTeamSwitch.error.switch')
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<Card v-for="request in kycRequests" :key="request.uuid" padding="lg">
|
<Card v-for="request in kycRequests" :key="request.uuid" padding="lg">
|
||||||
<Stack gap="3">
|
<Stack gap="3">
|
||||||
<Stack direction="row" gap="2" align="center" justify="between">
|
<Stack direction="row" gap="2" align="center" justify="between">
|
||||||
<Heading :level="4" weight="semibold">{{ request.companyName || t('kycOverview.list.unnamed') }}</Heading>
|
<Heading :level="4" weight="semibold">{{ request.teamName || t('kycOverview.list.unnamed') }}</Heading>
|
||||||
<Pill :variant="getStatusVariant(request)" :tone="getStatusTone(request)">
|
<Pill :variant="getStatusVariant(request)" :tone="getStatusTone(request)">
|
||||||
{{ getStatusText(request) }}
|
{{ getStatusText(request) }}
|
||||||
</Pill>
|
</Pill>
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
<Text tone="muted" size="base">
|
<Text tone="muted" size="base">
|
||||||
{{ t('kycOverview.list.submitted') }}: {{ formatDate(request.createdAt) }}
|
{{ t('kycOverview.list.submitted') }}: {{ formatDate(request.createdAt) }}
|
||||||
</Text>
|
</Text>
|
||||||
<Text v-if="request.inn" tone="muted" size="base">
|
<Text tone="muted" size="base">
|
||||||
{{ t('kycOverview.list.inn') }}: {{ request.inn }}
|
{{ t('kycOverview.list.country') }}: {{ request.countryCode }}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -91,7 +91,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetKycRequestsRussiaDocument } from '~/composables/graphql/user/kyc-generated'
|
import { GetKycRequestsRussiaDocument, type GetKycRequestsRussiaQueryResult } from '~/composables/graphql/user/kyc-generated'
|
||||||
|
|
||||||
|
type KycRequest = NonNullable<NonNullable<GetKycRequestsRussiaQueryResult['kycRequests']>[number]>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
@@ -102,7 +104,7 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
const kycRequests = ref<any[]>([])
|
const kycRequests = ref<KycRequest[]>([])
|
||||||
|
|
||||||
const selectCountry = (country: string) => {
|
const selectCountry = (country: string) => {
|
||||||
if (country === 'russia') {
|
if (country === 'russia') {
|
||||||
@@ -110,21 +112,18 @@ const selectCountry = (country: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusVariant = (request: any) => {
|
const getStatusVariant = (request: KycRequest) => {
|
||||||
if (request.approvedAt) return 'primary'
|
if (request.approvedAt) return 'primary'
|
||||||
if (request.rejectedAt) return 'outline'
|
|
||||||
return 'outline'
|
return 'outline'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusTone = (request: any) => {
|
const getStatusTone = (request: KycRequest) => {
|
||||||
if (request.approvedAt) return 'success'
|
if (request.approvedAt) return 'success'
|
||||||
if (request.rejectedAt) return 'error'
|
|
||||||
return 'warning'
|
return 'warning'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusText = (request: any) => {
|
const getStatusText = (request: KycRequest) => {
|
||||||
if (request.approvedAt) return t('kycOverview.list.status.approved')
|
if (request.approvedAt) return t('kycOverview.list.status.approved')
|
||||||
if (request.rejectedAt) return t('kycOverview.list.status.rejected')
|
|
||||||
return t('kycOverview.list.status.pending')
|
return t('kycOverview.list.status.pending')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,10 +142,10 @@ const loadKYCStatus = async () => {
|
|||||||
if (kycError.value) throw kycError.value
|
if (kycError.value) throw kycError.value
|
||||||
const requests = data.value?.kycRequests || []
|
const requests = data.value?.kycRequests || []
|
||||||
// Сортируем по дате создания (новые первые)
|
// Сортируем по дате создания (новые первые)
|
||||||
kycRequests.value = [...requests].sort((a: any, b: any) =>
|
kycRequests.value = [...requests]
|
||||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
.filter((r): r is KycRequest => r !== null)
|
||||||
)
|
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
error.value = t('kycOverview.errors.load_failed')
|
error.value = t('kycOverview.errors.load_failed')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|||||||
@@ -57,24 +57,39 @@ const submitting = ref(false)
|
|||||||
const submitError = ref<string | null>(null)
|
const submitError = ref<string | null>(null)
|
||||||
const submitSuccess = ref(false)
|
const submitSuccess = ref(false)
|
||||||
|
|
||||||
const handleSubmit = async (formData: any) => {
|
interface KycFormData {
|
||||||
|
company_name?: string
|
||||||
|
company_full_name?: string
|
||||||
|
inn?: string
|
||||||
|
kpp?: string
|
||||||
|
ogrn?: string
|
||||||
|
address?: string
|
||||||
|
bank_name?: string
|
||||||
|
bik?: string
|
||||||
|
correspondent_account?: string
|
||||||
|
contact_person?: string
|
||||||
|
contact_email?: string
|
||||||
|
contact_phone?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (formData: KycFormData) => {
|
||||||
try {
|
try {
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
submitError.value = null
|
submitError.value = null
|
||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
companyName: formData.company_name,
|
companyName: formData.company_name || '',
|
||||||
companyFullName: formData.company_full_name,
|
companyFullName: formData.company_full_name || '',
|
||||||
inn: formData.inn,
|
inn: formData.inn || '',
|
||||||
kpp: formData.kpp || '',
|
kpp: formData.kpp || '',
|
||||||
ogrn: formData.ogrn || '',
|
ogrn: formData.ogrn || '',
|
||||||
address: formData.address,
|
address: formData.address || '',
|
||||||
bankName: formData.bank_name,
|
bankName: formData.bank_name || '',
|
||||||
bik: formData.bik,
|
bik: formData.bik || '',
|
||||||
correspondentAccount: formData.correspondent_account || '',
|
correspondentAccount: formData.correspondent_account || '',
|
||||||
contactPerson: formData.contact_person,
|
contactPerson: formData.contact_person || '',
|
||||||
contactEmail: formData.contact_email,
|
contactEmail: formData.contact_email || '',
|
||||||
contactPhone: formData.contact_phone,
|
contactPhone: formData.contact_phone || '',
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await mutate(CreateKycApplicationRussiaDocument, { input: submitData }, 'user', 'kyc')
|
const result = await mutate(CreateKycApplicationRussiaDocument, { input: submitData }, 'user', 'kyc')
|
||||||
@@ -85,8 +100,8 @@ const handleSubmit = async (formData: any) => {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(t('kycRussia.errors.create_failed'))
|
throw new Error(t('kycRussia.errors.create_failed'))
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
submitError.value = err.message || t('kycRussia.errors.submit_failed')
|
submitError.value = err instanceof Error ? err.message : t('kycRussia.errors.submit_failed')
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@ import { FormKitSchema } from '@formkit/vue'
|
|||||||
import type { FormKitSchemaNode } from '@formkit/core'
|
import type { FormKitSchemaNode } from '@formkit/core'
|
||||||
import { GetProductsDocument } from '~/composables/graphql/public/exchange-generated'
|
import { GetProductsDocument } from '~/composables/graphql/public/exchange-generated'
|
||||||
import { CreateOfferDocument } from '~/composables/graphql/team/exchange-generated'
|
import { CreateOfferDocument } from '~/composables/graphql/team/exchange-generated'
|
||||||
import { GetTeamAddressesDocument } from '~/composables/graphql/team/teams-generated'
|
import { GetTeamAddressesDocument, type GetTeamAddressesQueryResult } from '~/composables/graphql/team/teams-generated'
|
||||||
|
|
||||||
|
type TeamAddress = NonNullable<NonNullable<GetTeamAddressesQueryResult['teamAddresses']>[number]>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
@@ -147,7 +149,7 @@ const productName = ref<string>('')
|
|||||||
const schemaId = ref<string | null>(null)
|
const schemaId = ref<string | null>(null)
|
||||||
const schemaDescription = ref<string | null>(null)
|
const schemaDescription = ref<string | null>(null)
|
||||||
const formkitSchema = ref<FormKitSchemaNode[]>([])
|
const formkitSchema = ref<FormKitSchemaNode[]>([])
|
||||||
const addresses = ref<any[]>([])
|
const addresses = ref<TeamAddress[]>([])
|
||||||
const selectedAddressUuid = ref<string | null>(null)
|
const selectedAddressUuid = ref<string | null>(null)
|
||||||
const formKitConfig = {
|
const formKitConfig = {
|
||||||
classes: {
|
classes: {
|
||||||
@@ -169,8 +171,8 @@ const loadAddresses = async () => {
|
|||||||
try {
|
try {
|
||||||
const { data, error: addressesError } = await useServerQuery('offer-form-addresses', GetTeamAddressesDocument, {}, 'team', 'teams')
|
const { data, error: addressesError } = await useServerQuery('offer-form-addresses', GetTeamAddressesDocument, {}, 'team', 'teams')
|
||||||
if (addressesError.value) throw addressesError.value
|
if (addressesError.value) throw addressesError.value
|
||||||
addresses.value = data.value?.teamAddresses || []
|
addresses.value = (data.value?.teamAddresses || []).filter((a): a is TeamAddress => a !== null)
|
||||||
const defaultAddress = addresses.value.find((address: any) => address.isDefault)
|
const defaultAddress = addresses.value.find((address) => address.isDefault)
|
||||||
selectedAddressUuid.value = defaultAddress?.uuid || addresses.value[0]?.uuid || null
|
selectedAddressUuid.value = defaultAddress?.uuid || addresses.value[0]?.uuid || null
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load addresses:', err)
|
console.error('Failed to load addresses:', err)
|
||||||
@@ -189,7 +191,7 @@ const loadData = async () => {
|
|||||||
const { data: productsData, error: productsError } = await useServerQuery('offer-form-products', GetProductsDocument, {}, 'public', 'exchange')
|
const { data: productsData, error: productsError } = await useServerQuery('offer-form-products', GetProductsDocument, {}, 'public', 'exchange')
|
||||||
if (productsError.value) throw productsError.value
|
if (productsError.value) throw productsError.value
|
||||||
const products = productsData.value?.getProducts || []
|
const products = productsData.value?.getProducts || []
|
||||||
const product = products.find((p: any) => p.uuid === productUuid.value)
|
const product = products.find((p) => p?.uuid === productUuid.value)
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error(t('clientOfferForm.errors.productNotFound', { uuid: productUuid.value }))
|
throw new Error(t('clientOfferForm.errors.productNotFound', { uuid: productUuid.value }))
|
||||||
@@ -219,9 +221,9 @@ const loadData = async () => {
|
|||||||
formkitSchema.value = schemaToFormKit(terminusClass, enums)
|
formkitSchema.value = schemaToFormKit(terminusClass, enums)
|
||||||
await loadAddresses()
|
await loadAddresses()
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
error.value = err.message || t('clientOfferForm.error.load')
|
error.value = err instanceof Error ? err.message : t('clientOfferForm.error.load')
|
||||||
console.error('Load error:', err)
|
console.error('Load error:', err)
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@@ -237,7 +239,7 @@ const handleSubmit = async (data: Record<string, unknown>) => {
|
|||||||
throw new Error(t('clientOfferForm.error.load'))
|
throw new Error(t('clientOfferForm.error.load'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedAddress = addresses.value.find((address: any) => address.uuid === selectedAddressUuid.value)
|
const selectedAddress = addresses.value.find((address) => address?.uuid === selectedAddressUuid.value)
|
||||||
if (!selectedAddress) {
|
if (!selectedAddress) {
|
||||||
throw new Error(t('clientOfferForm.error.save'))
|
throw new Error(t('clientOfferForm.error.save'))
|
||||||
}
|
}
|
||||||
@@ -253,14 +255,14 @@ const handleSubmit = async (data: Record<string, unknown>) => {
|
|||||||
locationCountryCode: selectedAddress.countryCode || '',
|
locationCountryCode: selectedAddress.countryCode || '',
|
||||||
locationLatitude: selectedAddress.latitude,
|
locationLatitude: selectedAddress.latitude,
|
||||||
locationLongitude: selectedAddress.longitude,
|
locationLongitude: selectedAddress.longitude,
|
||||||
quantity: data.quantity || 0,
|
quantity: String(data.quantity || '0'),
|
||||||
unit: String(data.unit || 'ton'),
|
unit: String(data.unit || 'ton'),
|
||||||
pricePerUnit: data.price_per_unit || data.pricePerUnit || null,
|
pricePerUnit: String(data.price_per_unit || data.pricePerUnit || ''),
|
||||||
currency: String(data.currency || 'USD'),
|
currency: String(data.currency || 'USD'),
|
||||||
description: String(data.description || ''),
|
description: String(data.description || ''),
|
||||||
validUntil: data.valid_until || data.validUntil || null,
|
validUntil: (data.valid_until as string | undefined) ?? (data.validUntil as string | undefined) ?? undefined,
|
||||||
terminusSchemaId: schemaId.value,
|
terminusSchemaId: schemaId.value,
|
||||||
terminusPayload: JSON.stringify(data),
|
terminusPayload: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await mutate(CreateOfferDocument, { input }, 'team', 'exchange')
|
const result = await mutate(CreateOfferDocument, { input }, 'team', 'exchange')
|
||||||
@@ -270,8 +272,8 @@ const handleSubmit = async (data: Record<string, unknown>) => {
|
|||||||
|
|
||||||
await navigateTo(localePath('/clientarea/offers'))
|
await navigateTo(localePath('/clientarea/offers'))
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
error.value = err.message || t('clientOfferForm.error.save')
|
error.value = err instanceof Error ? err.message : t('clientOfferForm.error.save')
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
} finally {
|
} finally {
|
||||||
isSubmitting.value = false
|
isSubmitting.value = false
|
||||||
|
|||||||
@@ -122,7 +122,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MapBounds } from '~/components/catalog/CatalogMap.vue'
|
import type { MapBounds } from '~/components/catalog/CatalogMap.vue'
|
||||||
import { GetOffersDocument } from '~/composables/graphql/public/exchange-generated'
|
import { GetOffersDocument, type GetOffersQueryResult } from '~/composables/graphql/public/exchange-generated'
|
||||||
|
|
||||||
|
type Offer = NonNullable<NonNullable<GetOffersQueryResult['getOffers']>[number]>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
@@ -135,7 +137,7 @@ const { activeTeamId } = useActiveTeam()
|
|||||||
|
|
||||||
const { execute } = useGraphQL()
|
const { execute } = useGraphQL()
|
||||||
const PAGE_SIZE = 24
|
const PAGE_SIZE = 24
|
||||||
const offers = ref<any[]>([])
|
const offers = ref<Offer[]>([])
|
||||||
const totalOffers = ref(0)
|
const totalOffers = ref(0)
|
||||||
const isLoadingMore = ref(false)
|
const isLoadingMore = ref(false)
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@ const {
|
|||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (offersData.value?.getOffers) {
|
if (offersData.value?.getOffers) {
|
||||||
offers.value = offersData.value.getOffers
|
offers.value = offersData.value.getOffers.filter((o): o is Offer => o !== null)
|
||||||
totalOffers.value = offersData.value.getOffersCount ?? offersData.value.getOffers.length
|
totalOffers.value = offersData.value.getOffersCount ?? offersData.value.getOffers.length
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -231,9 +233,11 @@ const onSearch = () => {
|
|||||||
// TODO: Implement search
|
// TODO: Implement search
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSelectOffer = (offer: any) => {
|
const onSelectOffer = (offer: { uuid?: string | null }) => {
|
||||||
selectedOfferId.value = offer.uuid
|
if (offer.uuid) {
|
||||||
navigateTo(localePath(`/clientarea/offers/${offer.uuid}`))
|
selectedOfferId.value = offer.uuid
|
||||||
|
navigateTo(localePath(`/clientarea/offers/${offer.uuid}`))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusVariant = (status: string) => {
|
const getStatusVariant = (status: string) => {
|
||||||
@@ -293,7 +297,7 @@ const fetchOffers = async (offset = 0, replace = false) => {
|
|||||||
'public',
|
'public',
|
||||||
'exchange'
|
'exchange'
|
||||||
)
|
)
|
||||||
const next = data?.getOffers || []
|
const next = (data?.getOffers || []).filter((o): o is Offer => o !== null)
|
||||||
offers.value = replace ? next : offers.value.concat(next)
|
offers.value = replace ? next : offers.value.concat(next)
|
||||||
totalOffers.value = data?.getOffersCount ?? totalOffers.value
|
totalOffers.value = data?.getOffersCount ?? totalOffers.value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<Grid v-if="products.length" :cols="1" :md="2" :lg="3" :gap="4">
|
<Grid v-if="products.length" :cols="1" :md="2" :lg="3" :gap="4">
|
||||||
<Card
|
<Card
|
||||||
v-for="product in products"
|
v-for="(product, index) in products"
|
||||||
:key="product.uuid"
|
:key="product.uuid ?? index"
|
||||||
padding="lg"
|
padding="lg"
|
||||||
class="cursor-pointer hover:shadow-md transition-shadow"
|
class="cursor-pointer hover:shadow-md transition-shadow"
|
||||||
@click="selectProduct(product)"
|
@click="selectProduct(product)"
|
||||||
@@ -51,7 +51,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetProductsDocument } from '~/composables/graphql/public/exchange-generated'
|
import { GetProductsDocument, type GetProductsQueryResult } from '~/composables/graphql/public/exchange-generated'
|
||||||
|
|
||||||
|
type Product = NonNullable<NonNullable<GetProductsQueryResult['getProducts']>[number]>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
@@ -62,7 +64,7 @@ const localePath = useLocalePath()
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { execute } = useGraphQL()
|
const { execute } = useGraphQL()
|
||||||
|
|
||||||
const products = ref<any[]>([])
|
const products = ref<Product[]>([])
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const hasError = ref(false)
|
const hasError = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
@@ -73,19 +75,19 @@ const loadProducts = async () => {
|
|||||||
hasError.value = false
|
hasError.value = false
|
||||||
const { data, error: productsError } = await useServerQuery('offers-new-products', GetProductsDocument, {}, 'public', 'exchange')
|
const { data, error: productsError } = await useServerQuery('offers-new-products', GetProductsDocument, {}, 'public', 'exchange')
|
||||||
if (productsError.value) throw productsError.value
|
if (productsError.value) throw productsError.value
|
||||||
products.value = data.value?.getProducts || []
|
products.value = (data.value?.getProducts || []).filter((p): p is Product => p !== null)
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
error.value = err.message || t('offersNew.errors.load_failed')
|
error.value = err instanceof Error ? err.message : t('offersNew.errors.load_failed')
|
||||||
products.value = []
|
products.value = []
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectProduct = (product: any) => {
|
const selectProduct = (product: { uuid?: string | null }) => {
|
||||||
// Navigate to product details page
|
// Navigate to product details page
|
||||||
navigateTo(localePath(`/clientarea/offers/${product.uuid}`))
|
if (product.uuid) navigateTo(localePath(`/clientarea/offers/${product.uuid}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadProducts()
|
await loadProducts()
|
||||||
|
|||||||
@@ -46,9 +46,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetOrderDocument } from '~/composables/graphql/team/orders-generated'
|
import { GetOrderDocument, type GetOrderQueryResult } from '~/composables/graphql/team/orders-generated'
|
||||||
import type { RouteStageItem } from '~/components/RouteStagesList.vue'
|
import type { RouteStageItem } from '~/components/RouteStagesList.vue'
|
||||||
|
|
||||||
|
// Types from GraphQL
|
||||||
|
type OrderType = NonNullable<GetOrderQueryResult['getOrder']>
|
||||||
|
type StageType = NonNullable<NonNullable<OrderType['stages']>[number]>
|
||||||
|
type TripType = NonNullable<NonNullable<StageType['trips']>[number]>
|
||||||
|
type CompanyType = NonNullable<StageType['selectedCompany']>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
middleware: ['auth-oidc']
|
middleware: ['auth-oidc']
|
||||||
@@ -57,7 +63,7 @@ definePageMeta({
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const order = ref<any>(null)
|
const order = ref<OrderType | null>(null)
|
||||||
const isLoadingOrder = ref(true)
|
const isLoadingOrder = ref(true)
|
||||||
const hasOrderError = ref(false)
|
const hasOrderError = ref(false)
|
||||||
const orderError = ref('')
|
const orderError = ref('')
|
||||||
@@ -96,8 +102,8 @@ const orderMeta = computed(() => {
|
|||||||
|
|
||||||
const orderRoutesForMap = computed(() => {
|
const orderRoutesForMap = computed(() => {
|
||||||
const stages = (order.value?.stages || [])
|
const stages = (order.value?.stages || [])
|
||||||
.filter(Boolean)
|
.filter((stage): stage is StageType => stage !== null)
|
||||||
.map((stage: any) => {
|
.map((stage) => {
|
||||||
if (stage.stageType === 'transport') {
|
if (stage.stageType === 'transport') {
|
||||||
if (!stage.sourceLatitude || !stage.sourceLongitude || !stage.destinationLatitude || !stage.destinationLongitude) return null
|
if (!stage.sourceLatitude || !stage.sourceLongitude || !stage.destinationLatitude || !stage.destinationLongitude) return null
|
||||||
return {
|
return {
|
||||||
@@ -118,33 +124,43 @@ const orderRoutesForMap = computed(() => {
|
|||||||
return [{ stages }]
|
return [{ stages }]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Company summary type
|
||||||
|
interface CompanySummary {
|
||||||
|
name: string | null | undefined
|
||||||
|
totalWeight: number
|
||||||
|
tripsCount: number
|
||||||
|
company: CompanyType | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
const orderStageItems = computed<RouteStageItem[]>(() => {
|
const orderStageItems = computed<RouteStageItem[]>(() => {
|
||||||
return (order.value?.stages || []).map((stage: any) => {
|
return (order.value?.stages || [])
|
||||||
const isTransport = stage.stageType === 'transport'
|
.filter((stage): stage is StageType => stage !== null)
|
||||||
const from = isTransport ? stage.sourceLocationName : stage.locationName
|
.map((stage) => {
|
||||||
const to = isTransport ? stage.destinationLocationName : stage.locationName
|
const isTransport = stage.stageType === 'transport'
|
||||||
|
const from = isTransport ? stage.sourceLocationName : stage.locationName
|
||||||
|
const to = isTransport ? stage.destinationLocationName : stage.locationName
|
||||||
|
|
||||||
const meta: string[] = []
|
const meta: string[] = []
|
||||||
const dateRange = getStageDateRange(stage)
|
const dateRange = getStageDateRange(stage)
|
||||||
if (dateRange) {
|
if (dateRange) {
|
||||||
meta.push(dateRange)
|
meta.push(dateRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
const companies = getCompaniesSummary(stage)
|
const companies = getCompaniesSummary(stage)
|
||||||
companies.forEach((company: any) => {
|
companies.forEach((company: CompanySummary) => {
|
||||||
meta.push(
|
meta.push(
|
||||||
`${company.name} · ${company.totalWeight || 0}${t('ordersDetail.labels.weight_unit')} · ${company.tripsCount || 0} ${t('ordersDetail.labels.trips')}`
|
`${company.name} · ${company.totalWeight || 0}${t('ordersDetail.labels.weight_unit')} · ${company.tripsCount || 0} ${t('ordersDetail.labels.trips')}`
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: stage.uuid ?? undefined,
|
||||||
|
from: from ?? undefined,
|
||||||
|
to: to ?? undefined,
|
||||||
|
label: stage.name ?? undefined,
|
||||||
|
meta
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
|
||||||
key: stage.uuid,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
label: stage.name,
|
|
||||||
meta
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadOrder = async () => {
|
const loadOrder = async () => {
|
||||||
@@ -154,10 +170,10 @@ const loadOrder = async () => {
|
|||||||
const orderUuid = route.params.id as string
|
const orderUuid = route.params.id as string
|
||||||
const { data, error: orderErrorResp } = await useServerQuery('order-detail', GetOrderDocument, { orderUuid }, 'team', 'orders')
|
const { data, error: orderErrorResp } = await useServerQuery('order-detail', GetOrderDocument, { orderUuid }, 'team', 'orders')
|
||||||
if (orderErrorResp.value) throw orderErrorResp.value
|
if (orderErrorResp.value) throw orderErrorResp.value
|
||||||
order.value = data.value?.getOrder
|
order.value = data.value?.getOrder ?? null
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
hasOrderError.value = true
|
hasOrderError.value = true
|
||||||
orderError.value = err.message || t('ordersDetail.errors.load_failed')
|
orderError.value = err instanceof Error ? err.message : t('ordersDetail.errors.load_failed')
|
||||||
} finally {
|
} finally {
|
||||||
isLoadingOrder.value = false
|
isLoadingOrder.value = false
|
||||||
}
|
}
|
||||||
@@ -172,8 +188,8 @@ const formatPrice = (price: number, currency?: string | null) => {
|
|||||||
}).format(price)
|
}).format(price)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCompaniesSummary = (stage: any) => {
|
const getCompaniesSummary = (stage: StageType): CompanySummary[] => {
|
||||||
const companies = []
|
const companies: CompanySummary[] = []
|
||||||
if (stage.stageType === 'service' && stage.selectedCompany) {
|
if (stage.stageType === 'service' && stage.selectedCompany) {
|
||||||
companies.push({
|
companies.push({
|
||||||
name: stage.selectedCompany.name,
|
name: stage.selectedCompany.name,
|
||||||
@@ -185,12 +201,13 @@ const getCompaniesSummary = (stage: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stage.stageType === 'transport' && stage.trips?.length) {
|
if (stage.stageType === 'transport' && stage.trips?.length) {
|
||||||
const companiesMap = new Map()
|
const companiesMap = new Map<string, CompanySummary>()
|
||||||
stage.trips.forEach((trip: any) => {
|
stage.trips.forEach((trip) => {
|
||||||
|
if (!trip) return
|
||||||
const companyName = trip.company?.name || t('ordersDetail.labels.company_unknown')
|
const companyName = trip.company?.name || t('ordersDetail.labels.company_unknown')
|
||||||
const weight = trip.plannedWeight || 0
|
const weight = trip.plannedWeight || 0
|
||||||
if (companiesMap.has(companyName)) {
|
if (companiesMap.has(companyName)) {
|
||||||
const existing = companiesMap.get(companyName)
|
const existing = companiesMap.get(companyName)!
|
||||||
existing.totalWeight += weight
|
existing.totalWeight += weight
|
||||||
existing.tripsCount += 1
|
existing.tripsCount += 1
|
||||||
} else {
|
} else {
|
||||||
@@ -211,10 +228,12 @@ const getOrderDuration = () => {
|
|||||||
if (!order.value?.stages?.length) return 0
|
if (!order.value?.stages?.length) return 0
|
||||||
let minDate: Date | null = null
|
let minDate: Date | null = null
|
||||||
let maxDate: Date | null = null
|
let maxDate: Date | null = null
|
||||||
order.value.stages.forEach((stage: any) => {
|
order.value.stages.forEach((stage) => {
|
||||||
stage.trips?.forEach((trip: any) => {
|
if (!stage) return
|
||||||
const startDate = new Date(trip.plannedLoadingDate || trip.actualLoadingDate)
|
stage.trips?.forEach((trip) => {
|
||||||
const endDate = new Date(trip.plannedUnloadingDate || trip.actualUnloadingDate)
|
if (!trip) return
|
||||||
|
const startDate = new Date(trip.plannedLoadingDate || trip.actualLoadingDate || '')
|
||||||
|
const endDate = new Date(trip.plannedUnloadingDate || trip.actualUnloadingDate || '')
|
||||||
if (!minDate || startDate < minDate) minDate = startDate
|
if (!minDate || startDate < minDate) minDate = startDate
|
||||||
if (!maxDate || endDate > maxDate) maxDate = endDate
|
if (!maxDate || endDate > maxDate) maxDate = endDate
|
||||||
})
|
})
|
||||||
@@ -224,13 +243,14 @@ const getOrderDuration = () => {
|
|||||||
return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStageDateRange = (stage: any) => {
|
const getStageDateRange = (stage: StageType) => {
|
||||||
if (!stage.trips?.length) return t('ordersDetail.labels.dates_undefined')
|
if (!stage.trips?.length) return t('ordersDetail.labels.dates_undefined')
|
||||||
let minDate: Date | null = null
|
let minDate: Date | null = null
|
||||||
let maxDate: Date | null = null
|
let maxDate: Date | null = null
|
||||||
stage.trips.forEach((trip: any) => {
|
stage.trips.forEach((trip) => {
|
||||||
const startDate = new Date(trip.plannedLoadingDate || trip.actualLoadingDate)
|
if (!trip) return
|
||||||
const endDate = new Date(trip.plannedUnloadingDate || trip.actualUnloadingDate)
|
const startDate = new Date(trip.plannedLoadingDate || trip.actualLoadingDate || '')
|
||||||
|
const endDate = new Date(trip.plannedUnloadingDate || trip.actualUnloadingDate || '')
|
||||||
if (!minDate || startDate < minDate) minDate = startDate
|
if (!minDate || startDate < minDate) minDate = startDate
|
||||||
if (!maxDate || endDate > maxDate) maxDate = endDate
|
if (!maxDate || endDate > maxDate) maxDate = endDate
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -100,6 +100,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MapBounds } from '~/components/catalog/CatalogMap.vue'
|
import type { MapBounds } from '~/components/catalog/CatalogMap.vue'
|
||||||
|
import type { GetTeamOrdersQueryResult } from '~/composables/graphql/team/orders-generated'
|
||||||
|
|
||||||
|
type TeamOrder = NonNullable<NonNullable<GetTeamOrdersQueryResult['getTeamOrders']>[number]>
|
||||||
|
type TeamOrderStage = NonNullable<NonNullable<TeamOrder['stages']>[number]>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'topnav',
|
layout: 'topnav',
|
||||||
@@ -131,19 +135,28 @@ const currentBounds = ref<MapBounds | null>(null)
|
|||||||
|
|
||||||
// List items - one per order
|
// List items - one per order
|
||||||
const listItems = computed(() => {
|
const listItems = computed(() => {
|
||||||
return filteredItems.value.map(order => ({
|
return filteredItems.value
|
||||||
...order,
|
.filter(order => order.uuid)
|
||||||
uuid: order.uuid,
|
.map(order => ({
|
||||||
name: order.name || `#${order.uuid.slice(0, 8)}`,
|
...order,
|
||||||
latitude: order.sourceLatitude,
|
uuid: order.uuid,
|
||||||
longitude: order.sourceLongitude,
|
name: order.name || `#${order.uuid!.slice(0, 8)}`,
|
||||||
country: order.sourceLocationName
|
latitude: order.sourceLatitude,
|
||||||
}))
|
longitude: order.sourceLongitude,
|
||||||
|
country: order.sourceLocationName
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Map points - two per order (source + destination)
|
// Map points - two per order (source + destination)
|
||||||
|
interface MapPoint {
|
||||||
|
uuid: string
|
||||||
|
name: string
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
}
|
||||||
|
|
||||||
const mapPoints = computed(() => {
|
const mapPoints = computed(() => {
|
||||||
const result: any[] = []
|
const result: MapPoint[] = []
|
||||||
filteredItems.value.forEach(order => {
|
filteredItems.value.forEach(order => {
|
||||||
// Source point
|
// Source point
|
||||||
if (order.sourceLatitude && order.sourceLongitude) {
|
if (order.sourceLatitude && order.sourceLongitude) {
|
||||||
@@ -202,22 +215,26 @@ const onSearch = () => {
|
|||||||
// TODO: Implement search
|
// TODO: Implement search
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSelectOrder = (item: any) => {
|
const onSelectOrder = (item: { uuid?: string | null }) => {
|
||||||
selectedOrderId.value = item.uuid
|
if (item.uuid) {
|
||||||
navigateTo(localePath(`/clientarea/orders/${item.uuid}`))
|
selectedOrderId.value = item.uuid
|
||||||
|
navigateTo(localePath(`/clientarea/orders/${item.uuid}`))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await init()
|
await init()
|
||||||
|
|
||||||
const getOrderStartDate = (order: any) => {
|
const getOrderStartDate = (order: TeamOrder) => {
|
||||||
if (!order.createdAt) return t('ordersDetail.labels.dates_undefined')
|
if (!order.createdAt) return t('ordersDetail.labels.dates_undefined')
|
||||||
return formatDate(order.createdAt)
|
return formatDate(order.createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOrderEndDate = (order: any) => {
|
const getOrderEndDate = (order: TeamOrder) => {
|
||||||
let latestDate: Date | null = null
|
let latestDate: Date | null = null
|
||||||
order.stages?.forEach((stage: any) => {
|
order.stages?.forEach((stage) => {
|
||||||
stage.trips?.forEach((trip: any) => {
|
if (!stage) return
|
||||||
|
stage.trips?.forEach((trip) => {
|
||||||
|
if (!trip) return
|
||||||
const endDate = trip.actualUnloadingDate || trip.plannedUnloadingDate
|
const endDate = trip.actualUnloadingDate || trip.plannedUnloadingDate
|
||||||
if (endDate) {
|
if (endDate) {
|
||||||
const date = new Date(endDate)
|
const date = new Date(endDate)
|
||||||
@@ -236,9 +253,10 @@ const getOrderEndDate = (order: any) => {
|
|||||||
return t('ordersDetail.labels.dates_undefined')
|
return t('ordersDetail.labels.dates_undefined')
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCompletedStages = (order: any) => {
|
const getCompletedStages = (order: TeamOrder) => {
|
||||||
if (!order.stages?.length) return 0
|
if (!order.stages?.length) return 0
|
||||||
return order.stages.filter((stage: any) => stage.status === 'completed').length
|
// Note: StageType doesn't have a status field, count all stages for now
|
||||||
|
return order.stages.filter((stage): stage is TeamOrderStage => stage !== null).length
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (date: string) => {
|
const formatDate = (date: string) => {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
>
|
>
|
||||||
<template #cards>
|
<template #cards>
|
||||||
<Card
|
<Card
|
||||||
v-for="order in filteredItems"
|
v-for="(order, index) in filteredItems"
|
||||||
:key="order.uuid"
|
:key="order.uuid ?? index"
|
||||||
padding="small"
|
padding="small"
|
||||||
interactive
|
interactive
|
||||||
:class="{ 'ring-2 ring-primary': selectedOrderId === order.uuid }"
|
:class="{ 'ring-2 ring-primary': selectedOrderId === order.uuid }"
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
<Stack gap="2">
|
<Stack gap="2">
|
||||||
<Stack direction="row" justify="between" align="center">
|
<Stack direction="row" justify="between" align="center">
|
||||||
<Text weight="semibold">#{{ order.name }}</Text>
|
<Text weight="semibold">#{{ order.name }}</Text>
|
||||||
<Badge :variant="getStatusVariant(order.status)" size="sm">
|
<Badge :variant="getStatusVariant(order.status || '')" size="sm">
|
||||||
{{ getStatusText(order.status) }}
|
{{ getStatusText(order.status || '') }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Text tone="muted" size="sm" class="truncate">
|
<Text tone="muted" size="sm" class="truncate">
|
||||||
@@ -74,9 +74,11 @@ await init()
|
|||||||
const mapRef = ref<{ flyTo: (orderId: string) => void } | null>(null)
|
const mapRef = ref<{ flyTo: (orderId: string) => void } | null>(null)
|
||||||
const selectedOrderId = ref<string | null>(null)
|
const selectedOrderId = ref<string | null>(null)
|
||||||
|
|
||||||
const selectOrder = (order: any) => {
|
const selectOrder = (order: { uuid?: string | null }) => {
|
||||||
selectedOrderId.value = order.uuid
|
if (order.uuid) {
|
||||||
mapRef.value?.flyTo(order.uuid)
|
selectedOrderId.value = order.uuid
|
||||||
|
mapRef.value?.flyTo(order.uuid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMapSelectOrder = (uuid: string) => {
|
const onMapSelectOrder = (uuid: string) => {
|
||||||
|
|||||||
@@ -48,8 +48,8 @@
|
|||||||
<Heading :level="2">{{ t('clientTeam.members.title') }}</Heading>
|
<Heading :level="2">{{ t('clientTeam.members.title') }}</Heading>
|
||||||
<Grid :cols="1" :md="2" :lg="3" :gap="4">
|
<Grid :cols="1" :md="2" :lg="3" :gap="4">
|
||||||
<Card
|
<Card
|
||||||
v-for="member in currentTeam?.members || []"
|
v-for="(member, index) in currentTeamMembers"
|
||||||
:key="member.user?.id"
|
:key="member.user?.id ?? `member-${index}`"
|
||||||
padding="lg"
|
padding="lg"
|
||||||
>
|
>
|
||||||
<Stack gap="3">
|
<Stack gap="3">
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
<!-- Pending invitations -->
|
<!-- Pending invitations -->
|
||||||
<Card
|
<Card
|
||||||
v-for="invitation in currentTeam?.invitations || []"
|
v-for="invitation in currentTeamInvitations"
|
||||||
:key="invitation.uuid"
|
:key="invitation.uuid"
|
||||||
padding="lg"
|
padding="lg"
|
||||||
class="border-dashed border-warning"
|
class="border-dashed border-warning"
|
||||||
@@ -111,7 +111,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetTeamDocument } from '~/composables/graphql/user/teams-generated'
|
import { GetTeamDocument, type GetTeamQueryResult } from '~/composables/graphql/user/teams-generated'
|
||||||
|
|
||||||
|
interface UserTeam {
|
||||||
|
id?: string | null
|
||||||
|
name: string
|
||||||
|
logtoOrgId?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type TeamWithMembers = NonNullable<GetTeamQueryResult['getTeam']>
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -129,8 +137,8 @@ const me = useState<{
|
|||||||
} | null>('me', () => null)
|
} | null>('me', () => null)
|
||||||
const { setActiveTeam } = useActiveTeam()
|
const { setActiveTeam } = useActiveTeam()
|
||||||
|
|
||||||
const userTeams = ref<any[]>([])
|
const userTeams = ref<UserTeam[]>([])
|
||||||
const currentTeam = ref<any>(null)
|
const currentTeam = ref<TeamWithMembers | UserTeam | null>(null)
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const hasError = ref(false)
|
const hasError = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
@@ -143,7 +151,7 @@ const teamHeaderActions = computed(() => {
|
|||||||
}
|
}
|
||||||
return actions
|
return actions
|
||||||
})
|
})
|
||||||
const roleText = (role?: string) => {
|
const roleText = (role?: string | null) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
OWNER: t('clientTeam.roles.owner'),
|
OWNER: t('clientTeam.roles.owner'),
|
||||||
ADMIN: t('clientTeam.roles.admin'),
|
ADMIN: t('clientTeam.roles.admin'),
|
||||||
@@ -153,13 +161,30 @@ const roleText = (role?: string) => {
|
|||||||
return map[role || ''] || role || t('clientTeam.roles.member')
|
return map[role || ''] || role || t('clientTeam.roles.member')
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMemberInitials = (user?: any) => {
|
interface TeamMember {
|
||||||
|
id?: string | null
|
||||||
|
firstName?: string | null
|
||||||
|
lastName?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMemberInitials = (user?: TeamMember | null) => {
|
||||||
if (!user) return '??'
|
if (!user) return '??'
|
||||||
const first = user.firstName?.charAt(0) || ''
|
const first = user.firstName?.charAt(0) || ''
|
||||||
const last = user.lastName?.charAt(0) || ''
|
const last = user.lastName?.charAt(0) || ''
|
||||||
return (first + last).toUpperCase() || user.id?.charAt(0).toUpperCase() || '??'
|
return (first + last).toUpperCase() || user.id?.charAt(0).toUpperCase() || '??'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type-safe accessors for TeamWithMembers properties
|
||||||
|
const currentTeamMembers = computed(() => {
|
||||||
|
const team = currentTeam.value
|
||||||
|
return team && 'members' in team ? (team.members || []).filter((m): m is NonNullable<typeof m> => m !== null) : []
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentTeamInvitations = computed(() => {
|
||||||
|
const team = currentTeam.value
|
||||||
|
return team && 'invitations' in team ? (team.invitations || []).filter((i): i is NonNullable<typeof i> => i !== null) : []
|
||||||
|
})
|
||||||
|
|
||||||
const loadUserTeams = async () => {
|
const loadUserTeams = async () => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
@@ -177,13 +202,15 @@ const loadUserTeams = async () => {
|
|||||||
currentTeam.value = teamData.value?.getTeam || null
|
currentTeam.value = teamData.value?.getTeam || null
|
||||||
} else if (userTeams.value.length > 0) {
|
} else if (userTeams.value.length > 0) {
|
||||||
const firstTeam = userTeams.value[0]
|
const firstTeam = userTeams.value[0]
|
||||||
setActiveTeam(firstTeam?.id || null, firstTeam?.logtoOrgId)
|
if (firstTeam) {
|
||||||
currentTeam.value = firstTeam
|
setActiveTeam(firstTeam.id || null, firstTeam.logtoOrgId)
|
||||||
|
currentTeam.value = firstTeam
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Если нет команды - currentTeam остаётся null, показываем EmptyState
|
// Если нет команды - currentTeam остаётся null, показываем EmptyState
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
error.value = err.message || t('clientTeam.error.load')
|
error.value = err instanceof Error ? err.message : t('clientTeam.error.load')
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ const submitInvite = async () => {
|
|||||||
} else {
|
} else {
|
||||||
inviteError.value = result?.inviteMember?.message || t('clientTeam.invite.error')
|
inviteError.value = result?.inviteMember?.message || t('clientTeam.invite.error')
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
inviteError.value = err.message || t('clientTeam.invite.error')
|
inviteError.value = err instanceof Error ? err.message : t('clientTeam.invite.error')
|
||||||
} finally {
|
} finally {
|
||||||
inviteLoading.value = false
|
inviteLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,15 @@ const plugins = [
|
|||||||
const pluginConfig = {
|
const pluginConfig = {
|
||||||
scalars: {
|
scalars: {
|
||||||
DateTime: 'string',
|
DateTime: 'string',
|
||||||
|
Date: 'string',
|
||||||
|
Decimal: 'string',
|
||||||
|
JSONString: 'Record<string, unknown>',
|
||||||
|
JSON: 'Record<string, unknown>',
|
||||||
|
UUID: 'string',
|
||||||
|
BigInt: 'string',
|
||||||
},
|
},
|
||||||
useTypeImports: true,
|
useTypeImports: true,
|
||||||
|
strictScalars: true,
|
||||||
// Add suffix to operation result types to avoid conflicts with schema types
|
// Add suffix to operation result types to avoid conflicts with schema types
|
||||||
operationResultSuffix: 'Result',
|
operationResultSuffix: 'Result',
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user