add location logging, display last location on map

Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
This commit is contained in:
Nicolas Froger 2024-07-26 15:36:19 +02:00
commit 130581a411
No known key found for this signature in database
13 changed files with 386 additions and 3 deletions

View file

@ -6,6 +6,7 @@ const CreatePostView = () => import('@/views/CreatePostView.vue')
const AdminView = () => import('@/views/AdminView.vue')
const LoginView = () => import('@/views/LoginView.vue')
const MapView = () => import('@/views/MapView.vue')
const SendLocationView = () => import('@/views/SendLocationView.vue')
function authenticatedRoute(to) {
const authStore = useAuthStore()
@ -46,6 +47,12 @@ const router = createRouter({
name: 'admin_create_post',
component: CreatePostView,
beforeEnter: [authenticatedRoute]
},
{
path: '/admin/send-location',
name: 'admin_send_location',
component: SendLocationView,
beforeEnter: [authenticatedRoute]
}
]
})

View file

@ -0,0 +1,32 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { formatRelative } from 'date-fns'
import { fr } from 'date-fns/locale'
import { API_BASE_URL } from '@/config.js'
import { fromLonLat } from 'ol/proj.js'
export const useLocationStore = defineStore('location', () => {
const locationApiPath = API_BASE_URL + '/location/last'
const lastLocation = ref(null)
function fetchLocation() {
return fetch(locationApiPath)
.then((response) => {
return response.json()
})
.then(async (loc) => {
const locDate = new Date(loc.timestamp)
loc.formatedDate = formatRelative(locDate, new Date(), { locale: fr })
loc.projectedCoordinates = fromLonLat([loc.longitude, loc.latitude])
lastLocation.value = loc
return location.value
})
.catch((error) => {
console.log('fetch last location failed with: ' + error)
})
}
return { lastLocation, fetchLocation }
})

View file

@ -1,7 +1,7 @@
<script setup>
import { Button } from '@/components/ui/button/index.js'
import { CirclePlus, Pencil, Trash } from 'lucide-vue-next'
import { CirclePlus, Pencil, Trash, MapPin } from 'lucide-vue-next'
import { useAdminPostsStore } from '@/stores/adminPosts.js'
import { onMounted } from 'vue'
import {
@ -27,6 +27,12 @@ onMounted(() => {
<h1 class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl mb-6">
Admin
</h1>
<RouterLink to="/admin/send-location">
<Button class="mb-6 mr-2">
<MapPin class="mr-2" />
Envoyer localisation
</Button>
</RouterLink>
<RouterLink to="/admin/post/create">
<Button class="mb-6">
<CirclePlus class="mr-2" />

View file

@ -2,13 +2,18 @@
import { usePostsStore } from '@/stores/posts.js'
import { computed, onMounted } from 'vue'
import { Camera } from 'lucide-vue-next'
import { Camera, MapPin } from 'lucide-vue-next'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip/index.js'
import { useLocationStore } from '@/stores/location.js'
const postStore = usePostsStore()
const locationStore = useLocationStore()
const postsLinesCoordinates = computed(() => {
const coords = []
if (locationStore.lastLocation != null) {
coords.push(locationStore.lastLocation.projectedCoordinates)
}
for (const post of postStore.posts) {
coords.push(post.projectedCoordinates)
}
@ -17,6 +22,7 @@ const postsLinesCoordinates = computed(() => {
onMounted(() => {
postStore.fetchPosts()
locationStore.fetchLocation()
})
</script>
@ -59,8 +65,27 @@ onMounted(() => {
</Tooltip>
</TooltipProvider>
</ol-overlay>
<ol-overlay
v-if="locationStore.lastLocation != null"
:position="locationStore.lastLocation.projectedCoordinates"
:autoPan="true"
>
<TooltipProvider>
<Tooltip :default-open="true">
<TooltipTrigger as-child>
<div
class="-translate-x-1/2 -translate-y-1/2 p-1 rounded-lg bg-rose-950 hover:bg-rose-900 transition-colors duration-200">
<MapPin size="16" />
</div>
</TooltipTrigger>
<TooltipContent>
<p>Dernière position, {{ locationStore.lastLocation.formatedDate }}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</ol-overlay>
<ol-vector-layer v-if="postStore.posts.length > 1">
<ol-vector-layer v-if="postsLinesCoordinates.length > 1">
<ol-source-vector>
<ol-feature ref="profileFeatureRef">
<ol-geom-line-string

View file

@ -0,0 +1,137 @@
<script setup>
import { Button } from '@/components/ui/button/index.js'
import { useForm } from 'vee-validate'
import { FormControl, FormField, FormItem, FormLabel } from '@/components/ui/form/index.js'
import { Input } from '@/components/ui/input/index.js'
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'
import { onMounted, onUnmounted, ref } from 'vue'
import { AlertCircle, ArrowLeft, CircleCheckBig, Send } from 'lucide-vue-next'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert/index.js'
import { useAuthStore } from '@/stores/auth.js'
import { API_BASE_URL } from '@/config.js'
const authStore = useAuthStore()
const formSchema = toTypedSchema(z.object({
latitude: z.number(),
longitude: z.number()
}))
const form = useForm({
validationSchema: formSchema
})
const formContainer = ref(null)
const latitudeInput = ref(null)
const longitudeInput = ref(null)
const formStatus = ref({
sending: false,
sent: false,
error: false,
errorMsg: ''
})
const onSubmit = form.handleSubmit(async (values) => {
if (!authStore.isAuth)
return
console.log('Envoi de la localisation...')
formContainer.value.classList.add('hidden')
formStatus.value.sending = true
const response = await fetch(API_BASE_URL + '/admin/location', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-admin-token': authStore.adminToken
},
body: JSON.stringify({
latitude: values.latitude,
longitude: values.longitude
})
})
if (!response.ok) {
console.log('POST post API failed: ' + response.statusText + '\n\n' + response.body)
formStatus.value.sending = false
formStatus.value.error = true
formStatus.value.errorMsg = 'Une erreur est survenue lors de l\'envoi du poste : ' + response.statusText
formContainer.value.classList.remove('hidden')
return
}
formStatus.value.sending = false
formStatus.value.sent = true
})
let geoWatchId = null
onMounted(() => {
geoWatchId = navigator.geolocation.watchPosition((position) => {
console.log(position)
form.setFieldValue('latitude', position.coords.latitude)
form.setFieldValue('longitude', position.coords.longitude)
})
})
onUnmounted(() => {
if (geoWatchId != null) {
navigator.geolocation.clearWatch(geoWatchId)
}
})
</script>
<template>
<div class="grid grid-cols-3 grid-rows-1 mt-28 sm:mt-20 w-full justify-center items-center gap-4">
<div class="col-span-3 col-start-1 lg:col-span-1 lg:col-start-2 mx-5 lg:mx-0">
<h1 class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl mb-6">
Envoyer la localisation
</h1>
<RouterLink to="/admin">
<Button class="mb-6">
<ArrowLeft class="mr-2" />
Retour
</Button>
</RouterLink>
<Alert variant="destructive" class="mb-6" v-if="formStatus.error">
<AlertCircle class="w-4 h-4"></AlertCircle>
<AlertTitle>Erreur...</AlertTitle>
<AlertDescription>{{ formStatus.errorMsg }}</AlertDescription>
</Alert>
<Alert class="mb-6" v-if="formStatus.sent">
<CircleCheckBig class="w-4 h-4"></CircleCheckBig>
<AlertTitle>Localisation envoyée !</AlertTitle>
</Alert>
<p v-if="formStatus.sending">Sending...</p>
<div id="form-container" ref="formContainer">
<form id="post-form" class="space-y-6" @submit="onSubmit">
<div class="grid grid-cols-2 gap-4">
<FormField ref="latitudeInput" v-slot="{ componentField }" name="latitude">
<FormItem>
<FormLabel>Latitude</FormLabel>
<FormControl>
<Input disabled v-bind="componentField"></Input>
</FormControl>
</FormItem>
</FormField>
<FormField ref="longitudeInput" v-slot="{ componentField }" name="longitude">
<FormItem>
<FormLabel>Longitude</FormLabel>
<FormControl>
<Input disabled v-bind="componentField"></Input>
</FormControl>
</FormItem>
</FormField>
</div>
<Button class="mt-6 w-full" type="submit">
<Send />
</Button>
</form>
</div>
</div>
</div>
</template>
<style scoped>
</style>