MapLeaflet.client.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <template>
  2. <div class="map-container">
  3. <v-skeleton-loader v-if="pending" type="image"></v-skeleton-loader>
  4. <LMap
  5. v-show="!pending"
  6. style="height: 350px"
  7. :zoom="zoom"
  8. :center="position"
  9. :use-global-leaflet="false"
  10. >
  11. <LTileLayer
  12. url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
  13. attribution='&amp;copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
  14. layer-type="base"
  15. name="OpenStreetMap"
  16. />
  17. <LMarker
  18. :lat-lng="position"
  19. :draggable="!readonly"
  20. @update:lat-lng="onPositionUpdate($event)"
  21. />
  22. </LMap>
  23. <v-btn
  24. v-if="searchButton && !readonly"
  25. prepend-icon="fas fa-location-dot"
  26. class="mt-3"
  27. @click="search()"
  28. >
  29. {{ $t('search_gps_button') }}
  30. </v-btn>
  31. <div v-if="!pending && gpsResponses.length > 0">
  32. <div
  33. v-for="(gpsResponse, key) in gpsResponses"
  34. :key="key"
  35. class="address_choices"
  36. @click="addressChoice(key)"
  37. >
  38. {{ gpsResponse['displayName'] }}
  39. <v-btn prepend-icon="fas fa-map-location" @click="addressChoice(key)"
  40. >Choisir</v-btn
  41. >
  42. </div>
  43. </div>
  44. </div>
  45. </template>
  46. <script setup lang="ts">
  47. import 'leaflet/dist/leaflet.css'
  48. import { LMap, LTileLayer, LMarker } from '@vue-leaflet/vue-leaflet'
  49. import {
  50. type ComputedRef,
  51. defineProps,
  52. type PropType,
  53. type Ref,
  54. ref,
  55. computed,
  56. } from 'vue'
  57. import { LatLng, type PointTuple } from 'leaflet'
  58. import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
  59. import UrlUtils from '~/services/utils/urlUtils'
  60. import type { AnyJson, CollectionResponsePromise } from '~/types/data'
  61. import Country from '~/models/Core/Country'
  62. import { useEntityManager } from '~/composables/data/useEntityManager'
  63. const props = defineProps({
  64. latitude: {
  65. type: Number as PropType<number | null>,
  66. required: true,
  67. },
  68. longitude: {
  69. type: Number as PropType<number | null>,
  70. required: true,
  71. },
  72. streetAddress: {
  73. type: String as PropType<string | null>,
  74. required: false,
  75. default: null,
  76. },
  77. streetAddressSecond: {
  78. type: String as PropType<string | null>,
  79. required: false,
  80. default: null,
  81. },
  82. streetAddressThird: {
  83. type: String as PropType<string | null>,
  84. required: false,
  85. default: null,
  86. },
  87. postalCode: {
  88. type: String as PropType<string | null>,
  89. required: false,
  90. default: null,
  91. },
  92. addressCity: {
  93. type: String as PropType<string | null>,
  94. required: false,
  95. default: null,
  96. },
  97. addressCountryId: {
  98. type: Number as PropType<number | null>,
  99. required: false,
  100. default: null,
  101. },
  102. searchButton: {
  103. type: Boolean,
  104. required: false,
  105. default: false,
  106. },
  107. readonly: {
  108. type: Boolean,
  109. required: false,
  110. default: false,
  111. },
  112. })
  113. const FRANCE_LATITUDE = 46.603354
  114. const FRANCE_LONGITUDE = 1.888334
  115. const { apiRequestService, pending } = useAp2iRequestService()
  116. const { em } = useEntityManager()
  117. const position: ComputedRef<PointTuple> = computed(() => {
  118. return [
  119. props.latitude || FRANCE_LATITUDE,
  120. props.longitude || FRANCE_LONGITUDE,
  121. ]
  122. })
  123. const zoom = computed({
  124. get() {
  125. return props.latitude && props.latitude != FRANCE_LATITUDE ? 12 : 5
  126. },
  127. set(newValue: number) {
  128. zoom.value = newValue
  129. },
  130. })
  131. const emit = defineEmits(['update:latitude', 'update:longitude'])
  132. const onPositionUpdate = (event: LatLng): void => {
  133. emit('update:latitude', event.lat)
  134. emit('update:longitude', event.lng)
  135. }
  136. const gpsResponses: Ref<Array<AnyJson>> = ref([])
  137. const search = async () => {
  138. gpsResponses.value = []
  139. const baseUrl = UrlUtils.join('api', 'gps-coordinate-searching')
  140. const query: AnyJson = {
  141. streetAddress: props.streetAddress,
  142. streetAddressSecond: props.streetAddressSecond,
  143. streetAddressThird: props.streetAddressThird,
  144. cp: props.postalCode,
  145. city: props.addressCity,
  146. }
  147. if (props.addressCountryId) {
  148. const country: Country = em.find(Country, props.addressCountryId)
  149. query['country'] = country?.name
  150. }
  151. const url = UrlUtils.addQuery(baseUrl, query)
  152. const responses: CollectionResponsePromise = await apiRequestService.get(url)
  153. if (responses['member'].length > 0) {
  154. onPositionUpdate(
  155. new LatLng(
  156. responses['member'][0]['latitude'],
  157. responses['member'][0]['longitude'],
  158. ),
  159. )
  160. if (responses['member'].length > 1) {
  161. zoom.value = 6
  162. gpsResponses.value = responses['member']
  163. } else {
  164. zoom.value = 12
  165. }
  166. }
  167. }
  168. const addressChoice = (key: number): void => {
  169. zoom.value = 12
  170. onPositionUpdate(
  171. new LatLng(
  172. gpsResponses.value[key]['latitude'] as number,
  173. gpsResponses.value[key]['longitude'] as number,
  174. ),
  175. )
  176. }
  177. </script>
  178. <style scoped lang="scss">
  179. .address_choices {
  180. cursor: pointer;
  181. width: 60%;
  182. display: flex;
  183. justify-content: space-between;
  184. align-items: center;
  185. padding: 0.75rem 1rem;
  186. border-radius: 0.5rem;
  187. margin-top: 0.5rem;
  188. background-color: #f9f9f9;
  189. transition: background-color 0.2s ease;
  190. &:hover {
  191. background-color: #eef3ff;
  192. }
  193. .v-btn {
  194. flex-shrink: 0;
  195. }
  196. }
  197. :deep(.v-skeleton-loader__image) {
  198. height: 350px;
  199. }
  200. :deep(.map_wrap) {
  201. height: 350px;
  202. }
  203. </style>