Added progress bar to loading process
This commit is contained in:
@@ -12,25 +12,39 @@ import { API_BASE_URL, API_LIMIT, MAP_TILELAYER_URL, SpotAccess, SpotType } from
|
|||||||
import { useQuery } from '@pinia/colada'
|
import { useQuery } from '@pinia/colada'
|
||||||
import { LControlZoom, LMap, LTileLayer } from '@vue-leaflet/vue-leaflet'
|
import { LControlZoom, LMap, LTileLayer } from '@vue-leaflet/vue-leaflet'
|
||||||
import BikeFilters from './BikeFilters.vue'
|
import BikeFilters from './BikeFilters.vue'
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import BikeClusterLayer from './BikeClusterLayer.vue'
|
import BikeClusterLayer from './BikeClusterLayer.vue'
|
||||||
|
import { PhCircleNotch } from '@phosphor-icons/vue'
|
||||||
|
|
||||||
|
const fetchedPages = ref(0)
|
||||||
|
const totalPages = ref(1)
|
||||||
|
const progress = computed(() => Math.round((fetchedPages.value / totalPages.value) * 100))
|
||||||
|
|
||||||
async function fetchAllBikeParkings(): Promise<BikeParking[]> {
|
async function fetchAllBikeParkings(): Promise<BikeParking[]> {
|
||||||
|
fetchedPages.value = 0
|
||||||
|
|
||||||
const firstPage = await fetch(`${API_BASE_URL}?limit=${API_LIMIT}`).then(r => r.json()) as ApiResponse
|
const firstPage = await fetch(`${API_BASE_URL}?limit=${API_LIMIT}`).then(r => r.json()) as ApiResponse
|
||||||
const { total_count } = firstPage
|
const { total_count } = firstPage
|
||||||
|
|
||||||
const remainingPages = await Promise.all(
|
const remainingCount = Math.ceil((total_count - API_LIMIT) / API_LIMIT)
|
||||||
Array.from(
|
totalPages.value = remainingCount + 1
|
||||||
{ length: Math.ceil((total_count - API_LIMIT) / API_LIMIT) },
|
fetchedPages.value = 1
|
||||||
(_, i) => fetch(`${API_BASE_URL}?limit=${API_LIMIT}&offset=${(i + 1) * API_LIMIT}`).then(r => r.json()) as Promise<ApiResponse>
|
|
||||||
|
const results: BikeParking[] = [...(firstPage.results ?? [])]
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from({ length: remainingCount }, (_, i) =>
|
||||||
|
fetch(`${API_BASE_URL}?limit=${API_LIMIT}&offset=${(i + 1) * API_LIMIT}`)
|
||||||
|
.then(r => r.json() as Promise<ApiResponse>)
|
||||||
|
.then(page => {
|
||||||
|
results.push(...(page.results ?? []))
|
||||||
|
fetchedPages.value++
|
||||||
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return results
|
||||||
...(firstPage.results ?? []),
|
|
||||||
...remainingPages.flatMap(page => page.results ?? [])
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { state } = useQuery({
|
const { state } = useQuery({
|
||||||
@@ -42,25 +56,25 @@ const { state } = useQuery({
|
|||||||
const nonCoveredParkings = computed(() =>
|
const nonCoveredParkings = computed(() =>
|
||||||
state.value.data?.filter(p => p.type === SpotType.Uncovered) ?? []
|
state.value.data?.filter(p => p.type === SpotType.Uncovered) ?? []
|
||||||
)
|
)
|
||||||
const nonCoveredParkingsSpots = computed(() => {
|
const nonCoveredParkingsSpots = computed(() =>
|
||||||
return nonCoveredParkings.value.reduce((acc, val) => acc + val.nb_total_place, 0)
|
nonCoveredParkings.value.reduce((acc, val) => acc + val.nb_total_place, 0)
|
||||||
})
|
)
|
||||||
|
|
||||||
const coveredTypes = new Set([SpotType.Covered, SpotType.Boxed])
|
const coveredTypes = new Set([SpotType.Covered, SpotType.Boxed])
|
||||||
|
|
||||||
const coveredParkings = computed(() =>
|
const coveredParkings = computed(() =>
|
||||||
state.value.data?.filter(p => coveredTypes.has(p.type as SpotType)) ?? []
|
state.value.data?.filter(p => coveredTypes.has(p.type as SpotType)) ?? []
|
||||||
)
|
)
|
||||||
const coveredParkingsSpots = computed(() => {
|
const coveredParkingsSpots = computed(() =>
|
||||||
return coveredParkings.value.reduce((acc, val) => acc + val.nb_total_place, 0)
|
coveredParkings.value.reduce((acc, val) => acc + val.nb_total_place, 0)
|
||||||
})
|
)
|
||||||
|
|
||||||
const premiumParkings = computed(() =>
|
const premiumParkings = computed(() =>
|
||||||
state.value.data?.filter(p => p.condition_acces === SpotAccess.Korrigo) ?? []
|
state.value.data?.filter(p => p.condition_acces === SpotAccess.Korrigo) ?? []
|
||||||
)
|
)
|
||||||
const premiumParkingsSpots = computed(() => {
|
const premiumParkingsSpots = computed(() =>
|
||||||
return premiumParkings.value.reduce((acc, val) => acc + val.nb_total_place, 0)
|
premiumParkings.value.reduce((acc, val) => acc + val.nb_total_place, 0)
|
||||||
})
|
)
|
||||||
|
|
||||||
const { zoom, minZoom, center, maxBounds, maxBoundsViscosity, maxClusterRadius, disableClusteringAtZoom, extraOptions } = useMap()
|
const { zoom, minZoom, center, maxBounds, maxBoundsViscosity, maxClusterRadius, disableClusteringAtZoom, extraOptions } = useMap()
|
||||||
const { filterUncovered, filterCovered, filterKorrigo } = storeToRefs(useMap())
|
const { filterUncovered, filterCovered, filterKorrigo } = storeToRefs(useMap())
|
||||||
@@ -74,21 +88,25 @@ const { filterUncovered, filterCovered, filterKorrigo } = storeToRefs(useMap())
|
|||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<main class="relative z-0 h-screen w-screen grid place-items-center">
|
<main class="relative z-0 h-screen w-screen grid place-items-center">
|
||||||
<div v-if="state.status === 'pending'">Loading...</div>
|
<div v-if="state.status === 'pending'" class="flex flex-col items-center gap-3 text-sm">
|
||||||
|
<span>Chargement des parkings… {{ progress }}%</span>
|
||||||
|
<progress :value="progress" max="100"
|
||||||
|
class="w-48 h-1.5 rounded-full bg-muted [&::-webkit-progress-bar]:bg-muted-foreground [&::-webkit-progress-value]:bg-primary [&::-moz-progress-bar]:bg-primary" />
|
||||||
|
<PhCircleNotch :size="24" class="animate-spin text-primary" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else-if="state.status === 'error'">Error: {{ state.error.message }}</div>
|
<div v-else-if="state.status === 'error'">Error: {{ state.error.message }}</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<LMap ref="map" :min-zoom v-model:zoom="zoom" :center="center" :max-bounds :max-bounds-viscosity
|
<LMap ref="map" :min-zoom v-model:zoom="zoom" :center="center" :max-bounds :max-bounds-viscosity
|
||||||
:options="extraOptions" :useGlobalLeaflet="true">
|
:options="extraOptions" :useGlobalLeaflet="true">
|
||||||
<LControlZoom position="bottomright" />
|
<LControlZoom position="bottomright" />
|
||||||
|
|
||||||
<LTileLayer v-once :url="MAP_TILELAYER_URL" layer-type="base" />
|
<LTileLayer v-once :url="MAP_TILELAYER_URL" layer-type="base" />
|
||||||
|
|
||||||
<BikeClusterLayer :parkings="coveredParkings" group="covered" :visible="filterCovered" :max-cluster-radius
|
<BikeClusterLayer :parkings="coveredParkings" group="covered" :visible="filterCovered" :max-cluster-radius
|
||||||
:disable-clustering-at-zoom />
|
:disable-clustering-at-zoom />
|
||||||
|
|
||||||
<BikeClusterLayer :parkings="nonCoveredParkings" group="non-covered" :visible="filterUncovered"
|
<BikeClusterLayer :parkings="nonCoveredParkings" group="non-covered" :visible="filterUncovered"
|
||||||
:max-cluster-radius :disable-clustering-at-zoom />
|
:max-cluster-radius :disable-clustering-at-zoom />
|
||||||
|
|
||||||
<BikeClusterLayer :parkings="premiumParkings" group="premium" :visible="filterKorrigo" :max-cluster-radius
|
<BikeClusterLayer :parkings="premiumParkings" group="premium" :visible="filterKorrigo" :max-cluster-radius
|
||||||
:disable-clustering-at-zoom />
|
:disable-clustering-at-zoom />
|
||||||
</LMap>
|
</LMap>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const useMap = defineStore('map', () => {
|
|||||||
zoomControl: false,
|
zoomControl: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxClusterRadius = 28
|
const maxClusterRadius = 40
|
||||||
const disableClusteringAtZoom = 17
|
const disableClusteringAtZoom = 17
|
||||||
|
|
||||||
const filterUncovered = ref(true)
|
const filterUncovered = ref(true)
|
||||||
|
|||||||
Reference in New Issue
Block a user