Files
webapp/app/components/LocationsContent.vue
Ruslan Bakiev 2b6cccdead
All checks were successful
Build Docker Image / build (push) Successful in 5m8s
Fix all TypeScript errors and remove Storybook
- Remove all Storybook files and configuration
- Add type declarations for @vueuse/core, @formkit/core, vue3-apexcharts
- Fix TypeScript configuration (typeRoots, include paths)
- Fix Sentry config - move settings to plugin
- Fix nullable prop assignments with ?? operator
- Fix type narrowing issues with explicit type assertions
- Fix Card component linkable computed properties
- Update codegen with operationResultSuffix
- Fix GraphQL operation type definitions
2026-01-26 00:32:36 +07:00

122 lines
3.9 KiB
Vue

<template>
<Stack gap="8">
<!-- My addresses (for authenticated users) -->
<Stack v-if="isAuthenticated && teamAddresses?.length" gap="4">
<PageHeader :title="t('locations.myAddresses')">
<template #actions>
<NuxtLink
:to="localePath('/clientarea/addresses')"
class="btn btn-sm btn-ghost gap-2"
>
<Icon name="lucide:settings" size="16" />
{{ t('locations.manage') }}
</NuxtLink>
</template>
</PageHeader>
<Grid :cols="1" :md="2" :lg="3" :gap="4">
<Card
v-for="addr in teamAddresses"
:key="addr.uuid"
padding="small"
interactive
@click="selectTeamAddress(addr)"
>
<Stack gap="2">
<Stack direction="row" align="center" gap="2">
<Icon name="lucide:map-pin" size="18" class="text-primary" />
<Text size="base" weight="semibold">{{ addr.name }}</Text>
<Pill v-if="addr.isDefault" variant="outline" size="sm">{{ t('locations.default') }}</Pill>
</Stack>
<Text tone="muted" size="sm">{{ addr.address }}</Text>
</Stack>
</Card>
</Grid>
</Stack>
<!-- Terminals and logistics hubs -->
<Stack gap="4">
<PageHeader :title="t('locations.terminalsAndHubs')" />
<div v-if="pending" class="flex items-center justify-center p-8">
<span class="loading loading-spinner loading-lg" />
</div>
<Alert v-else-if="error" variant="error">
<Stack gap="2">
<Heading :level="4" weight="semibold">{{ t('locations.loadError') }}</Heading>
<Button @click="refresh()">{{ t('locations.tryAgain') }}</Button>
</Stack>
</Alert>
<EmptyState
v-else-if="!locationsData?.length"
:title="t('locations.noLocations')"
:description="t('locations.noHubsDescription')"
/>
<Grid v-else :cols="1" :md="2" :lg="3" :gap="4">
<HubCard
v-for="location in locationsData"
:key="location.uuid"
:hub="location"
selectable
@select="selectLocation(location)"
/>
</Grid>
</Stack>
</Stack>
</template>
<script setup lang="ts">
import { GetNodesDocument } from '~/composables/graphql/public/geo-generated'
const { t } = useI18n()
const searchStore = useSearchStore()
const { isAuthenticated } = useAuth()
const localePath = useLocalePath()
const calculateDistance = (lat: number, lng: number) => {
const moscowLat = 55.76
const moscowLng = 37.64
const distance = Math.sqrt(Math.pow(lat - moscowLat, 2) + Math.pow(lng - moscowLng, 2)) * 111
return `${Math.round(distance)} km`
}
// Load logistics hubs
const { data: locationsDataRaw, pending, error, refresh } = await useServerQuery('locations', GetNodesDocument, {}, 'public', 'geo')
const locationsData = computed(() => {
return (locationsDataRaw.value?.nodes || []).map((location: any) => ({
...location,
distance: location?.latitude && location?.longitude
? calculateDistance(location.latitude, location.longitude)
: undefined,
}))
})
// Load team addresses (if authenticated)
const teamAddresses = ref<any[]>([])
if (isAuthenticated.value) {
try {
const { GetTeamAddressesDocument } = await import('~/composables/graphql/team/teams-generated')
const { data: addressData } = await useServerQuery('locations-team-addresses', GetTeamAddressesDocument, {}, 'team', 'teams')
teamAddresses.value = addressData.value?.teamAddresses || []
} catch (e) {
console.log('Team addresses not available')
}
}
const selectLocation = (location: any) => {
searchStore.setLocation(location.name)
searchStore.setLocationUuid(location.uuid)
history.back()
}
const selectTeamAddress = (addr: any) => {
searchStore.setLocation(addr.address)
searchStore.setLocationUuid(addr.uuid)
history.back()
}
</script>